diff --git a/package.json b/package.json index 96b687d..9c2fefc 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,6 @@ "amazon-buddy": "^2.2.45", "date-fns": "^3.6.0", "framer-motion": "^11.1.7", - "neverthrow": "^6.2.1", "next": "14.2.3", "next-auth": "5.0.0-beta.9", "next-themes": "^0.3.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8dc1108..02f678b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -86,9 +86,6 @@ dependencies: framer-motion: specifier: ^11.1.7 version: 11.1.7(react-dom@18.3.1)(react@18.3.1) - neverthrow: - specifier: ^6.2.1 - version: 6.2.1 next: specifier: 14.2.3 version: 14.2.3(@babel/core@7.24.3)(@playwright/test@1.43.1)(react-dom@18.3.1)(react@18.3.1) @@ -11546,10 +11543,6 @@ packages: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} dev: true - /neverthrow@6.2.1: - resolution: {integrity: sha512-amlnNvPXmiUOmNsDfUngNWPdZYOmCUGExQy/kuPCmLbhrXVXIYhY4bnE4D3dWl7OMhSEr9NndUrl4aKGFhFIQg==} - dev: false - /next-auth@5.0.0-beta.9(next@14.2.3)(nodemailer@6.9.13)(react@18.3.1): resolution: {integrity: sha512-BWFiwJ/wzfxWpHnGpAoFsXHSlVofWgFns6tjtIGeDrXfEf3D+afnBpmzCNyek2RNYDVgMHi8Q5uXzFoNBd2l5g==} peerDependencies: diff --git a/src/core/book/application/__tests__/create-book.use-case.spec.ts b/src/core/book/application/__tests__/create-book.use-case.spec.ts index fdb9ccf..d586eb1 100644 --- a/src/core/book/application/__tests__/create-book.use-case.spec.ts +++ b/src/core/book/application/__tests__/create-book.use-case.spec.ts @@ -3,7 +3,6 @@ import { describe, expect, it } from 'vitest' import { ApplicationError } from '@/core/common/domain/errors/application-error' import { container } from '@/lib/container' import { prisma } from '@/lib/prisma/prisma' -import { unexpected } from '@/lib/utils/unexpected' import { bookRequestExamples } from '@/tests/examples/books-request.examples' import { createAvailableBook } from '@/tests/examples/factories' @@ -13,21 +12,16 @@ describe('CreateBookUseCase', () => { const command = bookRequestExamples.create() // Act - const result = await container.createBook.with(command) + await container.createBook.with(command) // Assert - result.match( - async () => { - const savedBook = await prisma.book.findFirst({ - where: { - id: command.id, - }, - }) - expect(savedBook?.version).toEqual(0) - expect(savedBook?.state).toEqual('AVAILABLE') + const savedBook = await prisma.book.findFirst({ + where: { + id: command.id, }, - (error) => unexpected.error(error), - ) + }) + expect(savedBook?.version).toEqual(0) + expect(savedBook?.state).toEqual('AVAILABLE') }) it('should rejects to create a book with the same id', async () => { @@ -39,14 +33,9 @@ describe('CreateBookUseCase', () => { } // Act - const result = await container.createBook.with(command) + const result = async () => await container.createBook.with(command) // Assert - result.match( - (success) => unexpected.success(success), - (error) => { - expect(error).toBeInstanceOf(ApplicationError) - }, - ) + expect(result).rejects.toThrowError(ApplicationError) }) }) diff --git a/src/core/book/application/__tests__/edit-book.use-case.spec.ts b/src/core/book/application/__tests__/edit-book.use-case.spec.ts index 8c66a16..e0238dc 100644 --- a/src/core/book/application/__tests__/edit-book.use-case.spec.ts +++ b/src/core/book/application/__tests__/edit-book.use-case.spec.ts @@ -1,10 +1,8 @@ import { describe, expect, it } from 'vitest' import { EditBookRequest } from '@/core/book/dto/requests/edit-book.request' -import { NotFoundError } from '@/core/common/domain/errors/application/not-found-error' import { container } from '@/lib/container' import { prisma } from '@/lib/prisma/prisma' -import { unexpected } from '@/lib/utils/unexpected' import { BooksExamples } from '@/tests/examples/books.examples' import { createAvailableBook } from '@/tests/examples/factories' @@ -20,21 +18,16 @@ describe('EditBookUseCase', () => { }) // Act - const result = await container.editBook.with(command) + await container.editBook.with(command) // Assert - result.match( - async () => { - const savedBook = await prisma.book.findFirst({ - where: { - id: command.id, - }, - }) - expect(savedBook?.version).toEqual(1) - expect(savedBook?.title).toEqual(command.title) + const savedBook = await prisma.book.findFirst({ + where: { + id: command.id, }, - (error) => unexpected.error(error), - ) + }) + expect(savedBook?.version).toEqual(1) + expect(savedBook?.title).toEqual(command.title) }) it('should returns an error if book does not exists', async () => { @@ -48,14 +41,9 @@ describe('EditBookUseCase', () => { }) // Act - const result = await container.editBook.with(command) + const result = async () => await container.editBook.with(command) // Assert - result.match( - (_ok) => unexpected.success(_ok), - (error) => { - expect(error).toBeInstanceOf(NotFoundError) - }, - ) + expect(result).rejects.toThrowError() }) }) diff --git a/src/core/book/application/__tests__/loan-book.use-case.spec.ts b/src/core/book/application/__tests__/loan-book.use-case.spec.ts index 470d8c9..9baa057 100644 --- a/src/core/book/application/__tests__/loan-book.use-case.spec.ts +++ b/src/core/book/application/__tests__/loan-book.use-case.spec.ts @@ -1,10 +1,8 @@ import { describe, expect, it } from 'vitest' import { LoanBookRequest } from '@/core/book/dto/requests/loan-book.request' -import { ApplicationError } from '@/core/common/domain/errors/application-error' import { container } from '@/lib/container' import { prisma } from '@/lib/prisma/prisma' -import { unexpected } from '@/lib/utils/unexpected' import { createAvailableBook, createLoan, @@ -23,15 +21,10 @@ describe('Loan book', () => { }) // Act - const result = container.loanBook.with(request) + await container.loanBook.with(request) // Assert - await result.match( - async () => { - expect(await prisma.loan.count()).toBe(1) - }, - (error) => unexpected.error(error), - ) + expect(await prisma.loan.count()).toBe(1) }) it('should not loan an unavailable book to a user', async () => { @@ -45,14 +38,9 @@ describe('Loan book', () => { }) // Act - const result = container.loanBook.with(request) + const result = async () => container.loanBook.with(request) // Assert - await result.match( - (_ok) => unexpected.success(_ok), - (_error) => { - expect(_error).instanceof(ApplicationError) - }, - ) + expect(result).rejects.toThrowError() }) }) diff --git a/src/core/book/application/__tests__/return-book.use-case.spec.ts b/src/core/book/application/__tests__/return-book.use-case.spec.ts index 2e5a243..e310d2f 100644 --- a/src/core/book/application/__tests__/return-book.use-case.spec.ts +++ b/src/core/book/application/__tests__/return-book.use-case.spec.ts @@ -3,7 +3,6 @@ import { describe, expect, it } from 'vitest' import { ReturnBookRequest } from '@/core/book/dto/requests/return-book.request' import { container } from '@/lib/container' import { prisma } from '@/lib/prisma/prisma' -import { unexpected } from '@/lib/utils/unexpected' import { createLoan, createLoanedBook, @@ -21,14 +20,9 @@ describe('Return book', () => { }) // Act - const result = container.returnBook.with(request) + await container.returnBook.with(request) // Assert - await result.match( - async () => { - expect(await prisma.loan.count()).toBe(0) - }, - (error) => unexpected.error(error), - ) + expect(await prisma.loan.count()).toBe(0) }) }) diff --git a/src/core/book/application/create-book.use-case.ts b/src/core/book/application/create-book.use-case.ts index f82945f..d0f3ccc 100644 --- a/src/core/book/application/create-book.use-case.ts +++ b/src/core/book/application/create-book.use-case.ts @@ -6,8 +6,8 @@ export class CreateBookUseCase { constructor(private readonly books: Books) {} async with(command: CreateBookRequest) { - return BookFactory.create(command).asyncAndThen((book) => - this.books.save(book), - ) + const book = BookFactory.create(command) + + return this.books.save(book) } } diff --git a/src/core/book/application/edit-book.use-case.ts b/src/core/book/application/edit-book.use-case.ts index 35ebf0b..e458985 100644 --- a/src/core/book/application/edit-book.use-case.ts +++ b/src/core/book/application/edit-book.use-case.ts @@ -1,11 +1,6 @@ -import { okAsync, Result, ResultAsync } from 'neverthrow' - import { Book } from '@/core/book/domain/model/book.entity' import { Books } from '@/core/book/domain/services/books.repository' import { EditBookRequest } from '@/core/book/dto/requests/edit-book.request' -import { NotFoundError } from '@/core/common/domain/errors/application/not-found-error' -import { ApplicationError } from '@/core/common/domain/errors/application-error' -import { DomainError } from '@/core/common/domain/errors/domain-error' import { BookId } from '@/core/common/domain/value-objects/book-id' import { FullNames } from '@/core/common/domain/value-objects/fullnames' import { Image } from '@/core/common/domain/value-objects/image' @@ -14,34 +9,33 @@ import { Title } from '@/core/common/domain/value-objects/title' export class EditBookUseCase { constructor(private readonly books: Books) {} - async with(command: EditBookRequest) { - return BookId.create(command.id) - .asyncAndThen((bookId) => this.findBook(bookId)) - .andThen((book) => this.updateBook(book, command)) + async with(command: EditBookRequest): Promise { + const bookId = BookId.create(command.id) + const book = await this.findBook(bookId) + + return this.updateBook(book, command) } - private findBook(bookId: BookId): ResultAsync { - return ( - this.books.findAvailable(bookId) as ResultAsync - ).orElse(() => this.books.findLoaned(bookId)) + private async findBook(bookId: BookId): Promise { + try { + return this.books.findAvailable(bookId) + } catch { + return this.books.findLoaned(bookId) + } } - private updateBook( + private async updateBook( book: Book, command: EditBookRequest, - ): ResultAsync { - return Result.combine([ - Title.create(command.title), - FullNames.create(command.authors), - Image.create(command.image), - ]) - .asyncAndThen(([title, authors, image]) => { - book.title = title - book.image = image - book.authors = authors + ): Promise { + const authors = FullNames.create(command.authors) + const image = Image.create(command.image) + const title = Title.create(command.title) + + book.authors = authors + book.image = image + book.title = title - return okAsync(book) - }) - .andThen((_book) => this.books.save(_book)) + return this.books.save(book) } } diff --git a/src/core/book/application/loan-book-use.case.ts b/src/core/book/application/loan-book-use.case.ts index 45a4024..74cdac2 100644 --- a/src/core/book/application/loan-book-use.case.ts +++ b/src/core/book/application/loan-book-use.case.ts @@ -11,20 +11,22 @@ export class LoanBookUseCase { private readonly loanBookService: LoanBookService, ) {} - with(command: LoanBookRequest) { - return this.findAvailableBook(command.bookId) // - .andThen((book) => this.loanBook(book, command.userId)) + async with(command: LoanBookRequest) { + const book = await this.findAvailableBook(command.bookId) + + return this.loanBook(book, command.userId) } - private findAvailableBook(bookId: string) { - return BookId.create(bookId).asyncAndThen((_bookId) => - this.books.findAvailable(_bookId), - ) + private async findAvailableBook(bookId: string) { + const _bookId = BookId.create(bookId) + + return this.books.findAvailable(_bookId) } - private loanBook(book: AvailableBook, userId: string) { - return UserId.create(userId) - .asyncAndThen((_email) => book.loanTo(_email, this.loanBookService)) - .andThen(() => this.books.save(book)) + private async loanBook(book: AvailableBook, userId: string) { + const _userId = UserId.create(userId) + await book.loanTo(_userId, this.loanBookService) + + return this.books.save(book) } } diff --git a/src/core/book/application/return-book.use-case.ts b/src/core/book/application/return-book.use-case.ts index 229f5c7..a9da515 100644 --- a/src/core/book/application/return-book.use-case.ts +++ b/src/core/book/application/return-book.use-case.ts @@ -10,24 +10,20 @@ export class ReturnBookUseCase { private readonly returnBookService: ReturnBookService, ) {} - with(command: ReturnBookRequest) { - return this.findLoanedBook(command.bookId) // - .andThen((book) => this.returnBook(book)) - } + async with(command: ReturnBookRequest) { + const book = await this.findLoanedBook(command.bookId) // - private findLoanedBook(bookId: string) { - return BookId.create(bookId).asyncAndThen((_bookId) => - this.books.findLoaned(_bookId), - ) + return this.returnBook(book) } - private returnBook(book: LoanedBook) { - return book - .doAvailable(this.returnBookService) - .andThen(() => this.books.save(book)) + private async findLoanedBook(bookId: string) { + const _bookId = BookId.create(bookId) + + return this.books.findLoaned(_bookId) } -} -export function add(...arguments_: number[]) { - return arguments_.reduce((a, b) => a + b, 0) + private async returnBook(book: LoanedBook) { + await book.doAvailable(this.returnBookService) + return this.books.save(book) + } } diff --git a/src/core/book/domain/model/available-book.entity.ts b/src/core/book/domain/model/available-book.entity.ts index d4e9f7b..54c6314 100644 --- a/src/core/book/domain/model/available-book.entity.ts +++ b/src/core/book/domain/model/available-book.entity.ts @@ -1,20 +1,14 @@ -import { ResultAsync } from 'neverthrow' - import { Book, BookState } from '@/core/book/domain/model/book.entity' -import { ApplicationError } from '@/core/common/domain/errors/application-error' import { UserId } from '@/core/common/domain/value-objects/user-id' -import { ignore } from '@/core/common/utils/ignore' import { LoanBookService } from '@/core/loan/domain/services/loan-book.service' export class AvailableBook extends Book { - loanTo( + async loanTo( userId: UserId, loanBookService: LoanBookService, - ): ResultAsync { + ): Promise { this._state = BookState.LOANED - return loanBookService.with(this, userId).andThen(() => { - return ignore() - }) + return loanBookService.with(this, userId) } } diff --git a/src/core/book/domain/model/book.factory.ts b/src/core/book/domain/model/book.factory.ts index af54a62..130a5bf 100644 --- a/src/core/book/domain/model/book.factory.ts +++ b/src/core/book/domain/model/book.factory.ts @@ -1,29 +1,17 @@ -import { ok, Result, safeTry } from 'neverthrow' - import { AvailableBook } from '@/core/book/domain/model/available-book.entity' import { BookResponse } from '@/core/book/dto/responses/book.response' -import { DomainError } from '@/core/common/domain/errors/domain-error' import { BookId } from '@/core/common/domain/value-objects/book-id' import { FullNames } from '@/core/common/domain/value-objects/fullnames' import { Image } from '@/core/common/domain/value-objects/image' import { Title } from '@/core/common/domain/value-objects/title' export const BookFactory = { - create: (bookResponse: BookResponse): Result => - safeTry(function* () { - const bookId = yield* BookId.create(bookResponse.id) - .mapErr((error) => error) - .safeUnwrap() - const title = yield* Title.create(bookResponse.title) - .mapErr((error) => error) - .safeUnwrap() - const authors = yield* FullNames.create(bookResponse.authors) - .mapErr((error) => error) - .safeUnwrap() - const image = yield* Image.create(bookResponse.image) - .mapErr((error) => error) - .safeUnwrap() + create: (bookResponse: BookResponse): AvailableBook => { + const bookId = BookId.create(bookResponse.id) + const title = Title.create(bookResponse.title) + const authors = FullNames.create(bookResponse.authors) + const image = Image.create(bookResponse.image) - return ok(new AvailableBook(bookId, title, authors, image)) - }), + return new AvailableBook(bookId, title, authors, image) + }, } diff --git a/src/core/book/domain/model/loaned-book.entity.ts b/src/core/book/domain/model/loaned-book.entity.ts index 473b6bc..c9826ea 100644 --- a/src/core/book/domain/model/loaned-book.entity.ts +++ b/src/core/book/domain/model/loaned-book.entity.ts @@ -1,12 +1,8 @@ -import { ResultAsync } from 'neverthrow' - import { Book, BookState } from '@/core/book/domain/model/book.entity' -import { ApplicationError } from '@/core/common/domain/errors/application-error' import { BookId } from '@/core/common/domain/value-objects/book-id' import { FullNames } from '@/core/common/domain/value-objects/fullnames' import { Image } from '@/core/common/domain/value-objects/image' import { Title } from '@/core/common/domain/value-objects/title' -import { ignore } from '@/core/common/utils/ignore' import { Loan } from '@/core/loan/domain/model/loan.entity' import { ReturnBookService } from '@/core/loan/domain/services/return-book.service' @@ -21,11 +17,9 @@ export class LoanedBook extends Book { super(_id, _title, _authors, _image, BookState.LOANED) } - doAvailable( - returnBookService: ReturnBookService, - ): ResultAsync { + async doAvailable(returnBookService: ReturnBookService): Promise { this._state = BookState.AVAILABLE - return returnBookService.with(this).andThen(ignore) + return returnBookService.with(this) } } diff --git a/src/core/book/domain/services/books.repository.ts b/src/core/book/domain/services/books.repository.ts index 22891da..37ec7fd 100644 --- a/src/core/book/domain/services/books.repository.ts +++ b/src/core/book/domain/services/books.repository.ts @@ -1,14 +1,10 @@ -import { ResultAsync } from 'neverthrow' - import { AvailableBook } from '@/core/book/domain/model/available-book.entity' import { Book } from '@/core/book/domain/model/book.entity' import { LoanedBook } from '@/core/book/domain/model/loaned-book.entity' -import { NotFoundError } from '@/core/common/domain/errors/application/not-found-error' -import { ApplicationError } from '@/core/common/domain/errors/application-error' import { BookId } from '@/core/common/domain/value-objects/book-id' export interface Books { - findAvailable(id: BookId): ResultAsync - findLoaned(id: BookId): ResultAsync - save(book: Book): ResultAsync + findAvailable(id: BookId): Promise + findLoaned(id: BookId): Promise + save(book: Book): Promise } diff --git a/src/core/book/infrastructure/actions/find-book.ts b/src/core/book/infrastructure/actions/find-book.ts index 6d131ab..dd6b72f 100644 --- a/src/core/book/infrastructure/actions/find-book.ts +++ b/src/core/book/infrastructure/actions/find-book.ts @@ -9,7 +9,13 @@ export async function findBook( bookId: string, ): Promise { const getCachedBook = cache( - async (id: string) => container.findBook.with(id).unwrapOr(undefined), + async (id: string) => { + try { + return container.findBook.with(id) + } catch { + return undefined + } + }, [`book`], { tags: ['books', `book-${bookId}`], diff --git a/src/core/book/infrastructure/actions/find-books.ts b/src/core/book/infrastructure/actions/find-books.ts index 3a58f12..94699db 100644 --- a/src/core/book/infrastructure/actions/find-books.ts +++ b/src/core/book/infrastructure/actions/find-books.ts @@ -7,7 +7,13 @@ import { container } from '@/lib/container' export async function findBooks(): Promise { const getCachedBooks = cache( - async () => container.findBooks.with().unwrapOr([]), + async () => { + try { + return await container.findBooks.with() + } catch { + return [] + } + }, ['find-books'], { tags: ['books'], diff --git a/src/core/book/infrastructure/actions/search-books.ts b/src/core/book/infrastructure/actions/search-books.ts index 42228cc..8844fc9 100644 --- a/src/core/book/infrastructure/actions/search-books.ts +++ b/src/core/book/infrastructure/actions/search-books.ts @@ -14,7 +14,13 @@ export async function searchBooks( } const getCachedBooks = cache( - async (_request) => container.findBooks.with(_request).unwrapOr([]), + async (_request) => { + try { + return await container.findBooks.with(_request) + } catch { + return [] + } + }, ['search-books'], { tags: ['books'], diff --git a/src/core/book/infrastructure/persistence/book.publisher.ts b/src/core/book/infrastructure/persistence/book.publisher.ts index f834078..970aa45 100644 --- a/src/core/book/infrastructure/persistence/book.publisher.ts +++ b/src/core/book/infrastructure/persistence/book.publisher.ts @@ -1,5 +1,4 @@ import { PrismaClient } from '@prisma/client' -import { ResultAsync } from 'neverthrow' import { Book } from '@/core/book/domain/model/book.entity' import { BookDataMapper } from '@/core/book/infrastructure/persistence/book.data-mapper' @@ -11,29 +10,33 @@ export class BookPublisher extends Publisher { super() } - create(book: Book): ResultAsync { + async create(book: Book): Promise { const data = BookDataMapper.toPrisma(book) - return ResultAsync.fromPromise( - this.prisma.book.create({ + try { + const _book = await this.prisma.book.create({ data, - }), - (error: unknown) => new ApplicationError((error as Error).toString()), - ).andThen(this.checkVersion(0)) + }) + this.checkVersion(0)(_book) + } catch (error) { + throw new ApplicationError((error as Error).toString()) + } } - update(book: Book, version: number): ResultAsync { + async update(book: Book, version: number): Promise { const { id, ...data } = BookDataMapper.toPrisma(book) - return ResultAsync.fromPromise( - this.prisma.book.update({ + try { + const _book = await this.prisma.book.update({ data, where: { id, version, }, - }), - (error: unknown) => new ApplicationError((error as Error).toString()), - ).andThen(this.checkVersion(version)) + }) + this.checkVersion(version)(_book) + } catch (error) { + throw new ApplicationError((error as Error).toString()) + } } } diff --git a/src/core/book/infrastructure/queries/find-all-books.query.ts b/src/core/book/infrastructure/queries/find-all-books.query.ts index 2ded175..3a0420d 100644 --- a/src/core/book/infrastructure/queries/find-all-books.query.ts +++ b/src/core/book/infrastructure/queries/find-all-books.query.ts @@ -1,43 +1,39 @@ import { PrismaClient } from '@prisma/client' -import { okAsync, ResultAsync } from 'neverthrow' import { SearchBookRequest } from '@/core/book/dto/requests/search-book.requests' import { BookResponse } from '@/core/book/dto/responses/book.response' import { BookType } from '@/core/book/infrastructure/persistence/book.type' -import { ApplicationError } from '@/core/common/domain/errors/application-error' export class FindAllBooksQuery { constructor(private readonly prisma: PrismaClient) {} - with( - request?: SearchBookRequest, - ): ResultAsync { - return ResultAsync.fromSafePromise( - this.prisma.book.findMany({ - include: { - loan: { - include: { - user: true, - }, + async with(request?: SearchBookRequest): Promise { + const books = await this.prisma.book.findMany({ + include: { + loan: { + include: { + user: true, }, }, - orderBy: { - title: 'asc', - }, - ...(request - ? { - where: { - title: { - search: request?.terms.join(' & '), - }, + }, + orderBy: { + title: 'asc', + }, + ...(request + ? { + where: { + title: { + search: request?.terms.join(' & '), }, - } - : {}), - }), - ).andThen((books) => this.mapToBookResponse(books)) + }, + } + : {}), + }) + + return this.mapToBookResponse(books) } private mapToBookResponse(books: BookType[]) { - return okAsync(books.map((book) => BookResponse.fromType(book))) + return books.map((book) => BookResponse.fromType(book)) } } diff --git a/src/core/book/infrastructure/queries/find-book.query.ts b/src/core/book/infrastructure/queries/find-book.query.ts index a67196e..7e8b454 100644 --- a/src/core/book/infrastructure/queries/find-book.query.ts +++ b/src/core/book/infrastructure/queries/find-book.query.ts @@ -1,5 +1,4 @@ import { PrismaClient } from '@prisma/client' -import { okAsync, ResultAsync } from 'neverthrow' import { BookResponse } from '@/core/book/dto/responses/book.response' import { BookType } from '@/core/book/infrastructure/persistence/book.type' @@ -8,9 +7,9 @@ import { ApplicationError } from '@/core/common/domain/errors/application-error' export class FindBookQuery { constructor(private readonly prisma: PrismaClient) {} - with(bookId: string): ResultAsync { - return ResultAsync.fromPromise( - this.prisma.book.findUniqueOrThrow({ + async with(bookId: string): Promise { + try { + const book = await this.prisma.book.findUniqueOrThrow({ include: { loan: { include: { @@ -21,12 +20,15 @@ export class FindBookQuery { where: { id: bookId, }, - }), - (error: unknown) => new ApplicationError((error as Error).toString()), - ).andThen((book) => this.mapToBookResponse(book)) + }) + + return this.mapToBookResponse(book) + } catch (error) { + throw new ApplicationError((error as Error).toString()) + } } private mapToBookResponse(book: BookType) { - return okAsync(BookResponse.fromType(book)) + return BookResponse.fromType(book) } } diff --git a/src/core/book/infrastructure/services/books-prisma.repository.ts b/src/core/book/infrastructure/services/books-prisma.repository.ts index 5378d7f..fcee0d8 100644 --- a/src/core/book/infrastructure/services/books-prisma.repository.ts +++ b/src/core/book/infrastructure/services/books-prisma.repository.ts @@ -1,5 +1,4 @@ import { BookState, PrismaClient } from '@prisma/client' -import { okAsync, ResultAsync } from 'neverthrow' import { AvailableBook } from '@/core/book/domain/model/available-book.entity' import { Book } from '@/core/book/domain/model/book.entity' @@ -9,7 +8,6 @@ import { BookDataMapper } from '@/core/book/infrastructure/persistence/book.data import { BookPublisher } from '@/core/book/infrastructure/persistence/book.publisher' import { BookType } from '@/core/book/infrastructure/persistence/book.type' import { NotFoundError } from '@/core/common/domain/errors/application/not-found-error' -import { ApplicationError } from '@/core/common/domain/errors/application-error' import { BookId } from '@/core/common/domain/value-objects/book-id' export class BooksPrisma implements Books { @@ -19,24 +17,21 @@ export class BooksPrisma implements Books { this.publisher = new BookPublisher(prisma) } - findAvailable(id: BookId): ResultAsync { - return this.ofId(id, BookState.AVAILABLE).andThen((book) => - okAsync(BookDataMapper.toAvailableBook(book)), - ) + async findAvailable(id: BookId): Promise { + const book = await this.ofId(id, BookState.AVAILABLE) + + return BookDataMapper.toAvailableBook(book) } - findLoaned(id: BookId): ResultAsync { - return this.ofId(id, BookState.LOANED).andThen((book) => - okAsync(BookDataMapper.toLoanedBook(book)), - ) + async findLoaned(id: BookId): Promise { + const book = await this.ofId(id, BookState.LOANED) + + return BookDataMapper.toLoanedBook(book) } - private ofId( - id: BookId, - state: BookState, - ): ResultAsync { - return ResultAsync.fromPromise( - this.prisma.book.findUniqueOrThrow({ + private async ofId(id: BookId, state: BookState): Promise { + try { + return this.prisma.book.findUniqueOrThrow({ include: { loan: { include: { @@ -48,12 +43,13 @@ export class BooksPrisma implements Books { id: id.value, state, }, - }), - () => NotFoundError.withId(id), - ) + }) + } catch { + throw NotFoundError.withId(id) + } } - save(book: Book): ResultAsync { + async save(book: Book): Promise { return this.publisher.mergeObjectContext(book).commit() } } diff --git a/src/core/common/domain/errors/domain-error.ts b/src/core/common/domain/errors/domain-error.ts index e528514..bf2dec8 100644 --- a/src/core/common/domain/errors/domain-error.ts +++ b/src/core/common/domain/errors/domain-error.ts @@ -1,9 +1,7 @@ -import { Err, err } from 'neverthrow' - type Constructor = { new (value: string): T } export class DomainError extends Error { - static cause(this: Constructor, message: string): Err { - return err(new this(message)) + static cause(this: Constructor, message: string): T { + return new this(message) } } diff --git a/src/core/common/domain/model/aggregate-root.ts b/src/core/common/domain/model/aggregate-root.ts index 6989cd2..ddb2bc4 100644 --- a/src/core/common/domain/model/aggregate-root.ts +++ b/src/core/common/domain/model/aggregate-root.ts @@ -1,5 +1,3 @@ -import { errAsync, ResultAsync } from 'neverthrow' - import { ApplicationError } from '@/core/common/domain/errors/application-error' const VERSION = Symbol() @@ -25,17 +23,15 @@ export abstract class AggregateRoot { return this[VERSION] } - commit(): ResultAsync { + async commit(): Promise { this[VERSION] += 1 return this.publish(this) } - publish(instance: AggregateRoot): ResultAsync { - return errAsync( - new ApplicationError( - `Not implemented publish method for ${instance.constructor.name} aggregate`, - ), + async publish(instance: AggregateRoot): Promise { + throw new ApplicationError( + `Not implemented publish method for ${instance.constructor.name} aggregate`, ) } } diff --git a/src/core/common/domain/publisher/publisher.ts b/src/core/common/domain/publisher/publisher.ts index 7a8c121..0b03760 100644 --- a/src/core/common/domain/publisher/publisher.ts +++ b/src/core/common/domain/publisher/publisher.ts @@ -1,15 +1,10 @@ -import { errAsync, okAsync, ResultAsync } from 'neverthrow' - import { InvalidVersionError } from '@/core/common/domain/errors/application/invalid-version-error' import { AggregateRoot } from '@/core/common/domain/model/aggregate-root' export abstract class Publisher { - protected abstract create(instance: T): ResultAsync + protected abstract create(instance: T): Promise - protected abstract update( - instance: T, - version: number, - ): ResultAsync + protected abstract update(instance: T, version: number): Promise mergeObjectContext(object: T): T { object.publish = (instance: T) => { @@ -24,9 +19,10 @@ export abstract class Publisher { } protected checkVersion(version: number) { - return (result: unknown | undefined) => - result - ? okAsync(undefined) - : errAsync(InvalidVersionError.withVersion(version)) + return (result: unknown | undefined) => { + if (!result) { + throw InvalidVersionError.withVersion(version) + } + } } } diff --git a/src/core/common/domain/value-objects/__tests__/email.spec.ts b/src/core/common/domain/value-objects/__tests__/email.spec.ts index 21af080..5030975 100644 --- a/src/core/common/domain/value-objects/__tests__/email.spec.ts +++ b/src/core/common/domain/value-objects/__tests__/email.spec.ts @@ -1,8 +1,6 @@ import { describe, expect, it } from 'vitest' -import { DomainError } from '@/core/common/domain/errors/domain-error' import { Email } from '@/core/common/domain/value-objects/email' -import { unexpected } from '@/lib/utils/unexpected' describe('Email', () => { describe('create', () => { @@ -11,18 +9,11 @@ describe('Email', () => { const validEmail = 'john.doe@example.com' // Act - const result = Email.create(validEmail) + const email = Email.create(validEmail) // Assert - result.match( - (email) => { - expect(email).toBeInstanceOf(Email) - expect(email.value).toBe(validEmail) - }, - (error) => { - unexpected.error(error) - }, - ) + expect(email).toBeInstanceOf(Email) + expect(email.value).toBe(validEmail) }) it('should return an EmailError for an invalid email format', () => { @@ -30,17 +21,10 @@ describe('Email', () => { const invalidEmail = 'invalid-email' // Invalid format // Act - const result = Email.create(invalidEmail) + const result = () => Email.create(invalidEmail) // Assert - result.match( - (email) => { - unexpected.success(email) - }, - (error) => { - expect(error).toBeInstanceOf(DomainError) - }, - ) + expect(result).toThrowError() }) it('should return an EmailError for an empty email', () => { @@ -48,17 +32,10 @@ describe('Email', () => { const emptyEmail = '' // Act - const result = Email.create(emptyEmail) + const result = () => Email.create(emptyEmail) // Assert - result.match( - (email) => { - unexpected.success(email) - }, - (error) => { - expect(error).toBeInstanceOf(DomainError) - }, - ) + expect(result).toThrowError() }) }) }) diff --git a/src/core/common/domain/value-objects/__tests__/fullname.spec.ts b/src/core/common/domain/value-objects/__tests__/fullname.spec.ts index 75c03f0..d0231c9 100644 --- a/src/core/common/domain/value-objects/__tests__/fullname.spec.ts +++ b/src/core/common/domain/value-objects/__tests__/fullname.spec.ts @@ -1,8 +1,6 @@ import { describe, expect, it } from 'vitest' -import { DomainError } from '@/core/common/domain/errors/domain-error' import { FullName } from '@/core/common/domain/value-objects/fullname' -import { unexpected } from '@/lib/utils/unexpected' describe('FullName', () => { describe('create', () => { @@ -11,18 +9,11 @@ describe('FullName', () => { const validName = 'John Doe' // Act - const result = FullName.create(validName) + const fullName = FullName.create(validName) // Assert - result.match( - (fullName) => { - expect(fullName).toBeInstanceOf(FullName) - expect(fullName.value).toBe(validName) - }, - (error) => { - unexpected.error(error) - }, - ) + expect(fullName).toBeInstanceOf(FullName) + expect(fullName.value).toBe(validName) }) it('should return a FullNameError for an long name', () => { @@ -30,17 +21,10 @@ describe('FullName', () => { const invalidName = 'A'.repeat(65) // Act - const result = FullName.create(invalidName) + const result = () => FullName.create(invalidName) // Assert - result.match( - (fullName) => { - unexpected.success(fullName) - }, - (error) => { - expect(error).toBeInstanceOf(DomainError) - }, - ) + expect(result).toThrowError() }) it('should return a FullNameError for an empty name', () => { @@ -48,17 +32,10 @@ describe('FullName', () => { const emptyName = '' // Act - const result = FullName.create(emptyName) + const result = () => FullName.create(emptyName) // Assert - result.match( - (fullName) => { - unexpected.success(fullName) - }, - (error) => { - expect(error).toBeInstanceOf(DomainError) - }, - ) + expect(result).toThrowError() }) }) }) diff --git a/src/core/common/domain/value-objects/__tests__/fullnames.spec.ts b/src/core/common/domain/value-objects/__tests__/fullnames.spec.ts index 219a186..57e3a18 100644 --- a/src/core/common/domain/value-objects/__tests__/fullnames.spec.ts +++ b/src/core/common/domain/value-objects/__tests__/fullnames.spec.ts @@ -1,9 +1,7 @@ import { describe, expect, it } from 'vitest' -import { DomainError } from '@/core/common/domain/errors/domain-error' import { FullName } from '@/core/common/domain/value-objects/fullname' import { FullNames } from '@/core/common/domain/value-objects/fullnames' -import { unexpected } from '@/lib/utils/unexpected' describe('FullNames', () => { describe('create', () => { @@ -12,17 +10,12 @@ describe('FullNames', () => { const validFullNamesStrings = ['John Doe', 'Jane Doe'] // Act - const result = FullNames.create(validFullNamesStrings) + const fullNames = FullNames.create(validFullNamesStrings) // Assert - result.match( - (fullNames) => { - expect(fullNames).toBeInstanceOf(FullNames) - expect(fullNames.map((fullname) => fullname.value)).toEqual( - validFullNamesStrings, - ) - }, - (error) => unexpected.error(error), + expect(fullNames).toBeInstanceOf(FullNames) + expect(fullNames.map((fullname) => fullname.value)).toEqual( + validFullNamesStrings, ) }) @@ -31,13 +24,10 @@ describe('FullNames', () => { const invalidFullNamesStrings = ['John Doe', '', 'Jane Doe'] // Act - const result = FullNames.create(invalidFullNamesStrings) + const result = () => FullNames.create(invalidFullNamesStrings) // Assert - result.match( - (fullNames) => unexpected.success(fullNames), - (error) => expect(error).toBeInstanceOf(DomainError), - ) + expect(result).toThrowError() }) }) diff --git a/src/core/common/domain/value-objects/__tests__/image.spec.ts b/src/core/common/domain/value-objects/__tests__/image.spec.ts index 36a3bed..6fc2470 100644 --- a/src/core/common/domain/value-objects/__tests__/image.spec.ts +++ b/src/core/common/domain/value-objects/__tests__/image.spec.ts @@ -1,8 +1,6 @@ import { describe, expect, it } from 'vitest' -import { DomainError } from '@/core/common/domain/errors/domain-error' import { Image } from '@/core/common/domain/value-objects/image' -import { unexpected } from '@/lib/utils/unexpected' describe('Image', () => { describe('create', () => { @@ -11,18 +9,11 @@ describe('Image', () => { const validUrl = 'https://example.com/image.jpg' // Act - const result = Image.create(validUrl) + const image = Image.create(validUrl) // Assert - result.match( - (image) => { - expect(image).toBeInstanceOf(Image) - expect(image.value).toBe(validUrl) - }, - (error) => { - unexpected.error(error) - }, - ) + expect(image).toBeInstanceOf(Image) + expect(image.value).toBe(validUrl) }) it('should return an DomainError for an invalid URL', () => { @@ -30,17 +21,10 @@ describe('Image', () => { const invalidUrl = 'invalid-url' // Invalid format // Act - const result = Image.create(invalidUrl) + const result = () => Image.create(invalidUrl) // Assert - result.match( - (image) => { - unexpected.success(image) - }, - (error) => { - expect(error).toBeInstanceOf(DomainError) - }, - ) + expect(result).toThrowError() }) it('should return an DomainError for an empty URL', () => { @@ -48,17 +32,10 @@ describe('Image', () => { const emptyUrl = '' // Act - const result = Image.create(emptyUrl) + const result = () => Image.create(emptyUrl) // Assert - result.match( - (image) => { - unexpected.success(image) - }, - (error) => { - expect(error).toBeInstanceOf(DomainError) - }, - ) + expect(result).toThrowError() }) }) }) diff --git a/src/core/common/domain/value-objects/__tests__/role.spec.ts b/src/core/common/domain/value-objects/__tests__/role.spec.ts index 3544619..51308d3 100644 --- a/src/core/common/domain/value-objects/__tests__/role.spec.ts +++ b/src/core/common/domain/value-objects/__tests__/role.spec.ts @@ -1,8 +1,6 @@ import { describe, expect, it } from 'vitest' -import { DomainError } from '@/core/common/domain/errors/domain-error' import { Role } from '@/core/common/domain/value-objects/role' -import { unexpected } from '@/lib/utils/unexpected' describe('Role', () => { describe('create', () => { @@ -11,18 +9,11 @@ describe('Role', () => { const validRole = 'ROLE_USER' // Act - const result = Role.create(validRole) + const role = Role.create(validRole) // Assert - result.match( - (role) => { - expect(role).toBeInstanceOf(Role) - expect(role.value).toBe(validRole) - }, - (error) => { - unexpected.error(error) - }, - ) + expect(role).toBeInstanceOf(Role) + expect(role.value).toBe(validRole) }) it('should return a DomainError for an invalid role', () => { @@ -30,17 +21,10 @@ describe('Role', () => { const invalidRole = 'USER_ROLE' // Invalid format // Act - const result = Role.create(invalidRole) + const result = () => Role.create(invalidRole) // Assert - result.match( - (role) => { - unexpected.success(role) - }, - (error) => { - expect(error).toBeInstanceOf(DomainError) - }, - ) + expect(result).toThrowError() }) it('should return a DomainError for an empty role', () => { @@ -48,17 +32,10 @@ describe('Role', () => { const emptyRole = '' // Act - const result = Role.create(emptyRole) + const result = () => Role.create(emptyRole) // Assert - result.match( - (role) => { - unexpected.success(role) - }, - (error) => { - expect(error).toBeInstanceOf(DomainError) - }, - ) + expect(result).toThrowError() }) }) }) diff --git a/src/core/common/domain/value-objects/__tests__/roles.spec.ts b/src/core/common/domain/value-objects/__tests__/roles.spec.ts index 9022ba4..828bc1b 100644 --- a/src/core/common/domain/value-objects/__tests__/roles.spec.ts +++ b/src/core/common/domain/value-objects/__tests__/roles.spec.ts @@ -1,9 +1,7 @@ import { describe, expect, it } from 'vitest' -import { DomainError } from '@/core/common/domain/errors/domain-error' import { Role } from '@/core/common/domain/value-objects/role' import { Roles } from '@/core/common/domain/value-objects/roles' -import { unexpected } from '@/lib/utils/unexpected' describe('Roles', () => { describe('create', () => { @@ -12,18 +10,11 @@ describe('Roles', () => { const validRoleStrings = ['ROLE_USER', 'ROLE_ADMIN'] // Act - const result = Roles.create(validRoleStrings) + const roles = Roles.create(validRoleStrings) // Assert - result.match( - (roles) => { - expect(roles).toBeInstanceOf(Roles) - expect(roles.has(new Role('ROLE_USER'))).toBeTruthy() - }, - (error) => { - unexpected.error(error) - }, - ) + expect(roles).toBeInstanceOf(Roles) + expect(roles.has(new Role('ROLE_USER'))).toBeTruthy() }) it('should return an error for invalid roles', () => { @@ -31,17 +22,10 @@ describe('Roles', () => { const invalidRoleStrings = ['ROLE_USER', 'INVALID_ROLE', 'ROLE_ADMIN'] // Act - const result = Roles.create(invalidRoleStrings) + const result = () => Roles.create(invalidRoleStrings) // Assert - result.match( - (roles) => { - unexpected.success(roles) - }, - (error) => { - expect(error).toBeInstanceOf(DomainError) - }, - ) + expect(result).toThrowError() }) }) diff --git a/src/core/common/domain/value-objects/__tests__/title.spec.ts b/src/core/common/domain/value-objects/__tests__/title.spec.ts index 9345fa1..36bcee7 100644 --- a/src/core/common/domain/value-objects/__tests__/title.spec.ts +++ b/src/core/common/domain/value-objects/__tests__/title.spec.ts @@ -1,8 +1,6 @@ import { describe, expect, it } from 'vitest' -import { DomainError } from '@/core/common/domain/errors/domain-error' import { Title } from '@/core/common/domain/value-objects/title' -import { unexpected } from '@/lib/utils/unexpected' describe('Title', () => { describe('create', () => { @@ -11,18 +9,11 @@ describe('Title', () => { const validTitle = 'A book title' // Act - const result = Title.create(validTitle) + const fullName = Title.create(validTitle) // Assert - result.match( - (fullName) => { - expect(fullName).toBeInstanceOf(Title) - expect(fullName.value).toBe(validTitle) - }, - (error) => { - unexpected.error(error) - }, - ) + expect(fullName).toBeInstanceOf(Title) + expect(fullName.value).toBe(validTitle) }) it('should return a DomainError for an invalid title', () => { @@ -30,17 +21,10 @@ describe('Title', () => { const invalidTitle = 'Jo' // Name length is less than 3 // Act - const result = Title.create(invalidTitle) + const result = () => Title.create(invalidTitle) // Assert - result.match( - (fullName) => { - unexpected.success(fullName) - }, - (error) => { - expect(error).toBeInstanceOf(DomainError) - }, - ) + expect(result).toThrowError() }) it('should return a DomainError for an empty name', () => { @@ -48,17 +32,10 @@ describe('Title', () => { const emptyName = '' // Act - const result = Title.create(emptyName) + const result = () => Title.create(emptyName) // Assert - result.match( - (fullName) => { - unexpected.success(fullName) - }, - (error) => { - expect(error).toBeInstanceOf(DomainError) - }, - ) + expect(result).toThrowError() }) }) }) diff --git a/src/core/common/domain/value-objects/email.ts b/src/core/common/domain/value-objects/email.ts index e473822..7e3ee81 100644 --- a/src/core/common/domain/value-objects/email.ts +++ b/src/core/common/domain/value-objects/email.ts @@ -1,17 +1,15 @@ -import { ok, Result } from 'neverthrow' - import { DomainError } from '@/core/common/domain/errors/domain-error' export class Email { constructor(public readonly value: string) {} - public static create(email: string): Result { + public static create(email: string): Email { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ if (!email || !emailRegex.test(email)) { - return DomainError.cause('El correo no es válido.') + throw DomainError.cause('El correo no es válido.') } - return ok(new Email(email)) + return new Email(email) } } diff --git a/src/core/common/domain/value-objects/fullname.ts b/src/core/common/domain/value-objects/fullname.ts index f5786bb..26206e0 100644 --- a/src/core/common/domain/value-objects/fullname.ts +++ b/src/core/common/domain/value-objects/fullname.ts @@ -1,19 +1,17 @@ -import { ok, Result } from 'neverthrow' - import { DomainError } from '@/core/common/domain/errors/domain-error' export class FullName { constructor(public readonly value: string) {} - public static create(author: string): Result { + public static create(author: string): FullName { if (!author || author.length === 0) { - return DomainError.cause('El nombre es demasiado corto.') + throw DomainError.cause('El nombre es demasiado corto.') } if (author.length > 64) { - return DomainError.cause('El nombre es demasiado largo.') + throw DomainError.cause('El nombre es demasiado largo.') } - return ok(new FullName(author)) + return new FullName(author) } } diff --git a/src/core/common/domain/value-objects/fullnames.ts b/src/core/common/domain/value-objects/fullnames.ts index 1f89e4d..231936a 100644 --- a/src/core/common/domain/value-objects/fullnames.ts +++ b/src/core/common/domain/value-objects/fullnames.ts @@ -1,6 +1,3 @@ -import { err, ok, Result } from 'neverthrow' - -import { DomainError } from '@/core/common/domain/errors/domain-error' import { FullName } from '@/core/common/domain/value-objects/fullname' export class FullNames { @@ -10,15 +7,10 @@ export class FullNames { this._fullNames = fullNames } - static create( - fullNames: string[] | readonly string[], - ): Result { - return Result.combine( - fullNames.map((fullName) => FullName.create(fullName)), - ).match>( - (_fullNames) => ok(new FullNames(_fullNames)), - (_error) => err(_error), - ) + static create(fullNames: string[] | readonly string[]): FullNames { + const _fullNames = fullNames.map((fullName) => FullName.create(fullName)) + + return new FullNames(_fullNames) } map( diff --git a/src/core/common/domain/value-objects/id.spec.ts b/src/core/common/domain/value-objects/id.spec.ts index 49a49f0..c5cce30 100644 --- a/src/core/common/domain/value-objects/id.spec.ts +++ b/src/core/common/domain/value-objects/id.spec.ts @@ -2,7 +2,6 @@ import { ulid } from 'ulid' import { describe, expect, it } from 'vitest' import { Id } from '@/core/common/domain/value-objects/id' -import { unexpected } from '@/lib/utils/unexpected' describe('Id', () => { describe('create', () => { @@ -14,12 +13,7 @@ describe('Id', () => { const result = Id.create(validValue) // Assert - result.match( - (value) => { - expect(value).toBeDefined() - }, - (error) => unexpected.error(error), - ) + expect(result.value).toBeDefined() }) it('should return an error for invalid value', () => { @@ -27,15 +21,10 @@ describe('Id', () => { const invalidValue = 'invalid-value' // Act - const result = Id.create(invalidValue) + const result = () => Id.create(invalidValue) // Assert - result.match( - (success) => unexpected.success(success), - (error) => { - expect(error).toBeDefined() - }, - ) + expect(result).toThrowError() }) it('should return an error for empty value', () => { @@ -43,15 +32,10 @@ describe('Id', () => { const emptyValue = '' // Act - const result = Id.create(emptyValue) + const result = () => Id.create(emptyValue) // Assert - result.match( - (success) => unexpected.success(success), - (error) => { - expect(error).toBeDefined() - }, - ) + expect(result).toThrowError() }) }) diff --git a/src/core/common/domain/value-objects/id.ts b/src/core/common/domain/value-objects/id.ts index c32e5fe..070c8f1 100644 --- a/src/core/common/domain/value-objects/id.ts +++ b/src/core/common/domain/value-objects/id.ts @@ -1,4 +1,3 @@ -import { ok, Result } from 'neverthrow' import { ulid } from 'ulid' import { DomainError } from '@/core/common/domain/errors/domain-error' @@ -11,15 +10,12 @@ export class Id extends ValueObject { super(value) } - static create( - this: Constructor, - value: string, - ): Result { + static create(this: Constructor, value: string): T { if (!value || !/^[\dA-Za-z]{20,30}$/.test(value)) { - return DomainError.cause('invalid_id_format') + throw DomainError.cause('invalid_id_format') } - return ok(new this(value)) + return new this(value) } static generate(this: Constructor): T { diff --git a/src/core/common/domain/value-objects/image.ts b/src/core/common/domain/value-objects/image.ts index 528aea3..2ccff0c 100644 --- a/src/core/common/domain/value-objects/image.ts +++ b/src/core/common/domain/value-objects/image.ts @@ -1,17 +1,15 @@ -import { ok, Result } from 'neverthrow' - import { DomainError } from '@/core/common/domain/errors/domain-error' export class Image { constructor(public readonly value: string) {} - static create(name: string): Result { + static create(name: string): Image { try { new URL(name) } catch { - return DomainError.cause('La URL de la imagen no es válida.') + throw DomainError.cause('La URL de la imagen no es válida.') } - return ok(new Image(name)) + return new Image(name) } } diff --git a/src/core/common/domain/value-objects/role.ts b/src/core/common/domain/value-objects/role.ts index 352b13c..841de8a 100644 --- a/src/core/common/domain/value-objects/role.ts +++ b/src/core/common/domain/value-objects/role.ts @@ -1,5 +1,3 @@ -import { ok, Result } from 'neverthrow' - import { DomainError } from '@/core/common/domain/errors/domain-error' import { ValueObject } from '@/core/common/domain/model/value-object' @@ -8,11 +6,11 @@ export class Role extends ValueObject { super(value) } - static create(role: string): Result { + static create(role: string): Role { if (!role.startsWith('ROLE_')) { - return DomainError.cause('invalid_role_prefix') + throw DomainError.cause('invalid_role_prefix') } - return ok(new Role(role)) + return new Role(role) } } diff --git a/src/core/common/domain/value-objects/roles.ts b/src/core/common/domain/value-objects/roles.ts index 1c1c5e3..0c435d8 100644 --- a/src/core/common/domain/value-objects/roles.ts +++ b/src/core/common/domain/value-objects/roles.ts @@ -1,6 +1,3 @@ -import { err, ok, Result } from 'neverthrow' - -import { DomainError } from '@/core/common/domain/errors/domain-error' import { Role } from '@/core/common/domain/value-objects/role' export class Roles { @@ -10,15 +7,10 @@ export class Roles { this._roles = roles } - static create( - roles: string[] | readonly string[], - ): Result { - return Result.combine(roles.map((role) => Role.create(role))).match< - Result - >( - (_roles) => ok(new Roles(_roles)), - (_error) => err(_error), - ) + static create(roles: string[] | readonly string[]): Roles { + const _roles = roles.map((role) => Role.create(role)) + + return new Roles(_roles) } add(other: Role): Roles { diff --git a/src/core/common/domain/value-objects/title.ts b/src/core/common/domain/value-objects/title.ts index 601cde3..4141ace 100644 --- a/src/core/common/domain/value-objects/title.ts +++ b/src/core/common/domain/value-objects/title.ts @@ -1,15 +1,13 @@ -import { ok, Result } from 'neverthrow' - import { DomainError } from '@/core/common/domain/errors/domain-error' export class Title { constructor(public readonly value: string) {} - public static create(author: string): Result { + public static create(author: string): Title { if (!author || author.length < 3) { - return DomainError.cause('El título es demasiado corto') + throw DomainError.cause('El título es demasiado corto') } - return ok(new Title(author)) + return new Title(author) } } diff --git a/src/core/common/utils/ignore.ts b/src/core/common/utils/ignore.ts deleted file mode 100644 index efe9de9..0000000 --- a/src/core/common/utils/ignore.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { ok } from 'neverthrow' - -const ignore = () => { - return ok(undefined) -} - -export { ignore } diff --git a/src/core/loan/domain/model/loan.factory.ts b/src/core/loan/domain/model/loan.factory.ts index a83ef1d..56a22c8 100644 --- a/src/core/loan/domain/model/loan.factory.ts +++ b/src/core/loan/domain/model/loan.factory.ts @@ -1,6 +1,3 @@ -import { ok, Result, safeTry } from 'neverthrow' - -import { DomainError } from '@/core/common/domain/errors/domain-error' import { BookId } from '@/core/common/domain/value-objects/book-id' import { LoanId } from '@/core/common/domain/value-objects/loan-id' import { UserId } from '@/core/common/domain/value-objects/user-id' @@ -12,21 +9,14 @@ const LoanFactory = { id: string startsAt: Date userId: string - }): Result => - safeTry(function* () { - const loanId = yield* LoanId.create(loan.id) - .mapErr((error) => error) - .safeUnwrap() - const bookId = yield* BookId.create(loan.bookId) - .mapErr((error) => error) - .safeUnwrap() - const userId = yield* UserId.create(loan.userId) - .mapErr((error) => error) - .safeUnwrap() - const startsAt = new Date(loan.startsAt) + }): Loan => { + const loanId = LoanId.create(loan.id) + const bookId = BookId.create(loan.bookId) + const userId = UserId.create(loan.userId) + const startsAt = new Date(loan.startsAt) - return ok(new Loan(loanId, bookId, userId, startsAt)) - }), + return new Loan(loanId, bookId, userId, startsAt) + }, } export { LoanFactory } diff --git a/src/core/loan/domain/services/loan-book.service.ts b/src/core/loan/domain/services/loan-book.service.ts index 4761daf..1a3b3ff 100644 --- a/src/core/loan/domain/services/loan-book.service.ts +++ b/src/core/loan/domain/services/loan-book.service.ts @@ -1,8 +1,4 @@ -import { ResultAsync } from 'neverthrow' - import { AvailableBook } from '@/core/book/domain/model/available-book.entity' -import { ApplicationError } from '@/core/common/domain/errors/application-error' -import { DomainError } from '@/core/common/domain/errors/domain-error' import { LoanId } from '@/core/common/domain/value-objects/loan-id' import { UserId } from '@/core/common/domain/value-objects/user-id' import { LoanFactory } from '@/core/loan/domain/model/loan.factory' @@ -11,17 +7,16 @@ import { Loans } from '@/core/loan/domain/services/loans.repository' export class LoanBookService { constructor(private readonly loans: Loans) {} - with( - book: AvailableBook, - userId: UserId, - ): ResultAsync { + async with(book: AvailableBook, userId: UserId): Promise { const loanId = LoanId.generate() - return LoanFactory.create({ + const loan = LoanFactory.create({ bookId: book.id.value, id: loanId.value, startsAt: new Date(), userId: userId.value, - }).asyncAndThen((loan) => this.loans.save(loan)) + }) + + return this.loans.save(loan) } } diff --git a/src/core/loan/domain/services/loans.repository.ts b/src/core/loan/domain/services/loans.repository.ts index ab802b0..6568a71 100644 --- a/src/core/loan/domain/services/loans.repository.ts +++ b/src/core/loan/domain/services/loans.repository.ts @@ -1,13 +1,9 @@ -import { ResultAsync } from 'neverthrow' - -import { NotFoundError } from '@/core/common/domain/errors/application/not-found-error' -import { ApplicationError } from '@/core/common/domain/errors/application-error' import { BookId } from '@/core/common/domain/value-objects/book-id' import { LoanId } from '@/core/common/domain/value-objects/loan-id' import { Loan } from '@/core/loan/domain/model/loan.entity' export interface Loans { - ofBook(bookId: BookId): ResultAsync - remove(id: LoanId): ResultAsync - save(loan: Loan): ResultAsync + ofBook(bookId: BookId): Promise + remove(id: LoanId): Promise + save(loan: Loan): Promise } diff --git a/src/core/loan/domain/services/return-book.service.ts b/src/core/loan/domain/services/return-book.service.ts index d00111a..6234ce8 100644 --- a/src/core/loan/domain/services/return-book.service.ts +++ b/src/core/loan/domain/services/return-book.service.ts @@ -1,15 +1,14 @@ -import { ResultAsync } from 'neverthrow' - import { LoanedBook } from '@/core/book/domain/model/loaned-book.entity' -import { ApplicationError } from '@/core/common/domain/errors/application-error' import { Loan } from '@/core/loan/domain/model/loan.entity' import { Loans } from '@/core/loan/domain/services/loans.repository' export class ReturnBookService { constructor(private readonly loans: Loans) {} - with(book: LoanedBook): ResultAsync { - return this.loans.ofBook(book.id).andThen((loan) => this.returnBook(loan)) + async with(book: LoanedBook): Promise { + const loan = await this.loans.ofBook(book.id) + + return this.returnBook(loan) } private returnBook(loan: Loan) { diff --git a/src/core/loan/infrastructure/actions/get-historical-loans.ts b/src/core/loan/infrastructure/actions/get-historical-loans.ts index 4c7ba38..b97616d 100644 --- a/src/core/loan/infrastructure/actions/get-historical-loans.ts +++ b/src/core/loan/infrastructure/actions/get-historical-loans.ts @@ -9,7 +9,13 @@ export async function getHistoricalLoans( bookId: string, ): Promise { const getCachedLoans = cache( - async (id: string) => container.getHistoricalLoans.with(id).unwrapOr([]), + async (id: string) => { + try { + return await container.getHistoricalLoans.with(id) + } catch { + return [] + } + }, ['historical-loans'], { tags: [`book-${bookId}`, 'books'], diff --git a/src/core/loan/infrastructure/actions/loan-actions.ts b/src/core/loan/infrastructure/actions/loan-actions.ts index 14593d8..70a8442 100644 --- a/src/core/loan/infrastructure/actions/loan-actions.ts +++ b/src/core/loan/infrastructure/actions/loan-actions.ts @@ -5,6 +5,7 @@ import { z } from 'zod' import { LoanBookRequest } from '@/core/book/dto/requests/loan-book.request' import { ReturnBookRequest } from '@/core/book/dto/requests/return-book.request' +import { ApplicationError } from '@/core/common/domain/errors/application-error' import { me } from '@/core/user/infrastructure/actions/me' import { container } from '@/lib/container' import { FormResponse } from '@/lib/zod/form-response' @@ -63,24 +64,25 @@ async function loanBookAction( userId: string, previousState: FormResponse, ) { - return container.loanBook - .with( + try { + await container.loanBook.with( LoanBookRequest.with({ bookId, userId, }), ) - .match( - () => { - revalidateTag('books') - return FormResponse.success( - previousState.data, - 'Libro marcado como prestado.', - ) - }, - (_error) => - FormResponse.custom(['general'], _error.message, previousState.data), + revalidateTag('books') + return FormResponse.success( + previousState.data, + 'Libro marcado como prestado.', ) + } catch (error) { + return FormResponse.custom( + ['general'], + (error as ApplicationError).message, + previousState.data, + ) + } } async function returnBookAction( @@ -88,17 +90,19 @@ async function returnBookAction( userId: string, previousState: FormResponse, ) { - return await container.returnBook - .with(ReturnBookRequest.with({ bookId })) - .match( - () => { - revalidateTag('books') - return FormResponse.success( - previousState.data, - 'Libro marcado como devuelto.', - ) - }, - (_error) => - FormResponse.custom(['general'], _error.message, previousState.data), + try { + await container.returnBook.with(ReturnBookRequest.with({ bookId })) + + revalidateTag('books') + return FormResponse.success( + previousState.data, + 'Libro marcado como devuelto.', + ) + } catch (error) { + return FormResponse.custom( + ['general'], + (error as ApplicationError).message, + previousState.data, ) + } } diff --git a/src/core/loan/infrastructure/persistence/loan.publisher.ts b/src/core/loan/infrastructure/persistence/loan.publisher.ts index c172045..b00550c 100644 --- a/src/core/loan/infrastructure/persistence/loan.publisher.ts +++ b/src/core/loan/infrastructure/persistence/loan.publisher.ts @@ -1,9 +1,7 @@ import { PrismaClient } from '@prisma/client' -import { ResultAsync } from 'neverthrow' import { ApplicationError } from '@/core/common/domain/errors/application-error' import { Publisher } from '@/core/common/domain/publisher/publisher' -import { ignore } from '@/core/common/utils/ignore' import { Loan } from '@/core/loan/domain/model/loan.entity' import { LoanDataMapper } from '@/core/loan/infrastructure/persistence/loan.data-mapper' @@ -12,29 +10,31 @@ export class LoanPublisher extends Publisher { super() } - create(loan: Loan): ResultAsync { + async create(loan: Loan): Promise { const data = LoanDataMapper.toPrisma(loan) - return ResultAsync.fromPromise( - this.prisma.loan.create({ + try { + await this.prisma.loan.create({ data, - }), - (error: unknown) => new ApplicationError((error as Error).toString()), - ).andThen(ignore) + }) + } catch (error) { + throw new ApplicationError((error as Error).toString()) + } } - update(loan: Loan, version: number): ResultAsync { + async update(loan: Loan, version: number): Promise { const { id, ...data } = LoanDataMapper.toPrisma(loan) - return ResultAsync.fromPromise( - this.prisma.loan.update({ + try { + await this.prisma.loan.update({ data, where: { id, version, }, - }), - (error: unknown) => new ApplicationError((error as Error).toString()), - ).andThen(ignore) + }) + } catch (error) { + throw new ApplicationError((error as Error).toString()) + } } } diff --git a/src/core/loan/infrastructure/queries/get-historical-loans.query.ts b/src/core/loan/infrastructure/queries/get-historical-loans.query.ts index 78c388a..f10176c 100644 --- a/src/core/loan/infrastructure/queries/get-historical-loans.query.ts +++ b/src/core/loan/infrastructure/queries/get-historical-loans.query.ts @@ -1,5 +1,4 @@ import { PrismaClient } from '@prisma/client' -import { okAsync, ResultAsync } from 'neverthrow' import { ApplicationError } from '@/core/common/domain/errors/application-error' import { HistoricalLoansResponse } from '@/core/loan/dto/responses/historical-loans.response' @@ -8,23 +7,24 @@ import { LoanRegistryType } from '@/core/loan/infrastructure/persistence/loan-re export class GetHistoricalLoansQuery { constructor(private readonly prisma: PrismaClient) {} - with( - bookId: string, - ): ResultAsync { - return ResultAsync.fromPromise( - this.prisma.loanRegistry.findMany({ + async with(bookId: string): Promise { + try { + const loans = await this.prisma.loanRegistry.findMany({ include: { user: true, }, where: { bookId, }, - }), - (error: unknown) => new ApplicationError((error as Error).toString()), - ).andThen((loans) => this.mapToLoanRegistryResponse(loans)) + }) + + return this.mapToLoanRegistryResponse(loans) + } catch (error) { + throw new ApplicationError((error as Error).toString()) + } } private mapToLoanRegistryResponse(loans: LoanRegistryType[]) { - return okAsync(loans.map((loan) => HistoricalLoansResponse.fromType(loan))) + return loans.map((loan) => HistoricalLoansResponse.fromType(loan)) } } diff --git a/src/core/loan/infrastructure/services/loans-prisma.repository.ts b/src/core/loan/infrastructure/services/loans-prisma.repository.ts index 9120b12..93f690d 100644 --- a/src/core/loan/infrastructure/services/loans-prisma.repository.ts +++ b/src/core/loan/infrastructure/services/loans-prisma.repository.ts @@ -1,11 +1,9 @@ import { Loan as LoanPrisma, PrismaClient } from '@prisma/client' -import { okAsync, ResultAsync } from 'neverthrow' import { NotFoundError } from '@/core/common/domain/errors/application/not-found-error' import { ApplicationError } from '@/core/common/domain/errors/application-error' import { BookId } from '@/core/common/domain/value-objects/book-id' import { LoanId } from '@/core/common/domain/value-objects/loan-id' -import { ignore } from '@/core/common/utils/ignore' import { Loan } from '@/core/loan/domain/model/loan.entity' import { Loans } from '@/core/loan/domain/services/loans.repository' import { LoanDataMapper } from '@/core/loan/infrastructure/persistence/loan.data-mapper' @@ -18,41 +16,48 @@ export class LoansPrisma implements Loans { this.publisher = new LoanPublisher(prisma) } - ofBook(bookId: BookId): ResultAsync { - return ResultAsync.fromPromise( - this.prisma.loan.findUniqueOrThrow({ + async ofBook(bookId: BookId): Promise { + try { + const loan = await this.prisma.loan.findUniqueOrThrow({ where: { bookId: bookId.value, }, - }), - () => NotFoundError.withId(bookId), - ).andThen((loan) => okAsync(LoanDataMapper.toModel(loan))) + }) + + return LoanDataMapper.toModel(loan) + } catch { + throw NotFoundError.withId(bookId) + } } - save(loan: Loan): ResultAsync { + async save(loan: Loan): Promise { return this.publisher.mergeObjectContext(loan).commit() } - remove(id: LoanId): ResultAsync { - return ResultAsync.fromPromise( - this.prisma.loan.delete({ + async remove(id: LoanId): Promise { + try { + const loan = await this.prisma.loan.delete({ where: { id: id.value, }, - }), - (error: unknown) => new ApplicationError((error as Error).toString()), - ).andThen((loan) => this.registerLoan(loan)) + }) + + return this.registerLoan(loan) + } catch (error) { + throw new ApplicationError((error as Error).toString()) + } } - private registerLoan(loan: LoanPrisma) { - return ResultAsync.fromPromise( - this.prisma.loanRegistry.create({ + private async registerLoan(loan: LoanPrisma) { + try { + await this.prisma.loanRegistry.create({ data: { ...loan, finishedAt: new Date(), }, - }), - (error: unknown) => new ApplicationError((error as Error).toString()), - ).andThen(ignore) + }) + } catch (error) { + throw new ApplicationError((error as Error).toString()) + } } } diff --git a/src/core/review/infrastructure/actions/get-review-stats.ts b/src/core/review/infrastructure/actions/get-review-stats.ts index 7fb9cbd..bf66ad5 100644 --- a/src/core/review/infrastructure/actions/get-review-stats.ts +++ b/src/core/review/infrastructure/actions/get-review-stats.ts @@ -6,10 +6,9 @@ import { container } from '@/lib/container' export async function getReviewStats( bookId: string, ): Promise { - const result = container.getReviewsStats.with(bookId) - - return result.match( - (reviewsStats) => reviewsStats, - () => [], - ) + try { + return await container.getReviewsStats.with(bookId) + } catch { + return [] + } } diff --git a/src/core/review/infrastructure/actions/get-reviews.ts b/src/core/review/infrastructure/actions/get-reviews.ts index d253c9e..d2492f5 100644 --- a/src/core/review/infrastructure/actions/get-reviews.ts +++ b/src/core/review/infrastructure/actions/get-reviews.ts @@ -4,13 +4,9 @@ import { ReviewResponse } from '@/core/review/dto/responses/review-response' import { container } from '@/lib/container' export async function getReviews(bookId: string): Promise { - const result = container.getReviews.with(bookId) - - return result.match( - (reviews) => reviews, - (error) => { - console.debug(error) - return [] - }, - ) + try { + return container.getReviews.with(bookId) + } catch { + return [] + } } diff --git a/src/core/review/infrastructure/queries/get-review-stats.query.spec.ts b/src/core/review/infrastructure/queries/get-review-stats.query.spec.ts index c06a984..af29526 100644 --- a/src/core/review/infrastructure/queries/get-review-stats.query.spec.ts +++ b/src/core/review/infrastructure/queries/get-review-stats.query.spec.ts @@ -5,7 +5,6 @@ import { Book } from '@/core/book/domain/model/book.entity' import { User } from '@/core/user/domain/model/user.entity' import { container } from '@/lib/container' import { prisma } from '@/lib/prisma/prisma' -import { unexpected } from '@/lib/utils/unexpected' import { createAvailableBook, createUser } from '@/tests/examples/factories' import { UsersExamples } from '@/tests/examples/users.examples' @@ -15,19 +14,14 @@ describe('GetReviewStatsQuery', () => { const book = await createAvailableBook() // Act - const result = await container.getReviewsStats.with(book.id.value) + const response = await container.getReviewsStats.with(book.id.value) // Assert - result.match( - (response) => { - expect(response).toContainEqual({ reviews: 0, score: 1 }) - expect(response).toContainEqual({ reviews: 0, score: 2 }) - expect(response).toContainEqual({ reviews: 0, score: 3 }) - expect(response).toContainEqual({ reviews: 0, score: 4 }) - expect(response).toContainEqual({ reviews: 0, score: 5 }) - }, - (error) => unexpected.error(error), - ) + expect(response).toContainEqual({ reviews: 0, score: 1 }) + expect(response).toContainEqual({ reviews: 0, score: 2 }) + expect(response).toContainEqual({ reviews: 0, score: 3 }) + expect(response).toContainEqual({ reviews: 0, score: 4 }) + expect(response).toContainEqual({ reviews: 0, score: 5 }) }) it('should return reviews stats', async () => { @@ -39,19 +33,14 @@ describe('GetReviewStatsQuery', () => { await createReview(book, user2, 1) // Act - const result = await container.getReviewsStats.with(book.id.value) + const response = await container.getReviewsStats.with(book.id.value) // Assert - result.match( - (response) => { - expect(response).toContainEqual({ reviews: 1, score: 1 }) - expect(response).toContainEqual({ reviews: 0, score: 2 }) - expect(response).toContainEqual({ reviews: 0, score: 3 }) - expect(response).toContainEqual({ reviews: 0, score: 4 }) - expect(response).toContainEqual({ reviews: 1, score: 5 }) - }, - (error) => unexpected.error(error), - ) + expect(response).toContainEqual({ reviews: 1, score: 1 }) + expect(response).toContainEqual({ reviews: 0, score: 2 }) + expect(response).toContainEqual({ reviews: 0, score: 3 }) + expect(response).toContainEqual({ reviews: 0, score: 4 }) + expect(response).toContainEqual({ reviews: 1, score: 5 }) }) }) diff --git a/src/core/review/infrastructure/queries/get-review-stats.query.ts b/src/core/review/infrastructure/queries/get-review-stats.query.ts index 4f31b97..de07de5 100644 --- a/src/core/review/infrastructure/queries/get-review-stats.query.ts +++ b/src/core/review/infrastructure/queries/get-review-stats.query.ts @@ -1,5 +1,4 @@ import { PrismaClient } from '@prisma/client' -import { okAsync, ResultAsync } from 'neverthrow' import { ApplicationError } from '@/core/common/domain/errors/application-error' import { ReviewStatsResponse } from '@/core/review/dto/responses/review-stats.response' @@ -7,24 +6,26 @@ import { ReviewStatsResponse } from '@/core/review/dto/responses/review-stats.re export class GetReviewStatsQuery { constructor(private readonly prisma: PrismaClient) {} - with(bookId: string): ResultAsync { - return ResultAsync.fromPromise( - this.prisma.$queryRaw<{ reviews: number; score: number }[]>` + async with(bookId: string): Promise { + try { + const stats = await this.prisma.$queryRaw< + { reviews: number; score: number }[] + >` SELECT scoreId as score, COUNT(r."score")::integer as reviews FROM generate_series(1,5) as scoreId LEFT JOIN "Review" r on scoreId = r."score" and r."bookId" = ${bookId} GROUP BY scoreId ORDER BY scoreId DESC - `, - (error: unknown) => new ApplicationError((error as Error).toString()), - ).andThen((stats) => this.mapToReviewStatsResponse(stats)) + ` + return this.mapToReviewStatsResponse(stats) + } catch (error) { + throw new ApplicationError((error as Error).toString()) + } } private mapToReviewStatsResponse(reviewsStats: ReviewStatsResponse[]) { - return okAsync( - reviewsStats.map((reviewStats) => - ReviewStatsResponse.fromRawQuery(reviewStats), - ), + return reviewsStats.map((reviewStats) => + ReviewStatsResponse.fromRawQuery(reviewStats), ) } } diff --git a/src/core/review/infrastructure/queries/get-reviews.query.spec.ts b/src/core/review/infrastructure/queries/get-reviews.query.spec.ts index ee605b3..ae4de94 100644 --- a/src/core/review/infrastructure/queries/get-reviews.query.spec.ts +++ b/src/core/review/infrastructure/queries/get-reviews.query.spec.ts @@ -5,7 +5,6 @@ import { Book } from '@/core/book/domain/model/book.entity' import { User } from '@/core/user/domain/model/user.entity' import { container } from '@/lib/container' import { prisma } from '@/lib/prisma/prisma' -import { unexpected } from '@/lib/utils/unexpected' import { createAvailableBook, createUser } from '@/tests/examples/factories' import { UsersExamples } from '@/tests/examples/users.examples' @@ -15,15 +14,10 @@ describe('GetReviewsQuery', () => { const book = await createAvailableBook() // Act - const result = await container.getReviews.with(book.id.value) + const response = await container.getReviews.with(book.id.value) // Assert - result.match( - (response) => { - expect(response).toStrictEqual([]) - }, - (error) => unexpected.error(error), - ) + expect(response).toStrictEqual([]) }) it('should return published book reviews', async () => { @@ -33,22 +27,17 @@ describe('GetReviewsQuery', () => { await createReview(book, user1, 5) // Act - const result = await container.getReviews.with(book.id.value) + const response = await container.getReviews.with(book.id.value) // Assert - result.match( - (response) => { - expect(response).toHaveLength(1) - expect(response).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - description: 'description', - title: 'title', - }), - ]), - ) - }, - (error) => unexpected.error(error), + expect(response).toHaveLength(1) + expect(response).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + description: 'description', + title: 'title', + }), + ]), ) }) }) diff --git a/src/core/review/infrastructure/queries/get-reviews.query.ts b/src/core/review/infrastructure/queries/get-reviews.query.ts index 70a5cc4..7b9264d 100644 --- a/src/core/review/infrastructure/queries/get-reviews.query.ts +++ b/src/core/review/infrastructure/queries/get-reviews.query.ts @@ -1,5 +1,4 @@ import { PrismaClient } from '@prisma/client' -import { okAsync, ResultAsync } from 'neverthrow' import { ApplicationError } from '@/core/common/domain/errors/application-error' import { ReviewResponse } from '@/core/review/dto/responses/review-response' @@ -8,9 +7,9 @@ import { ReviewType } from '@/core/review/infrastructure/persistence/review.type export class GetReviewsQuery { constructor(private readonly prisma: PrismaClient) {} - with(bookId: string): ResultAsync { - return ResultAsync.fromPromise( - this.prisma.review.findMany({ + async with(bookId: string): Promise { + try { + const reviews = await this.prisma.review.findMany({ include: { user: true, }, @@ -20,12 +19,15 @@ export class GetReviewsQuery { where: { bookId: bookId, }, - }), - (error: unknown) => new ApplicationError((error as Error).toString()), - ).andThen((reviews) => this.mapToReviewResponse(reviews)) + }) + + return this.mapToReviewResponse(reviews) + } catch (error) { + throw new ApplicationError((error as Error).toString()) + } } private mapToReviewResponse(reviews: ReviewType[]) { - return okAsync(reviews.map((review) => ReviewResponse.fromType(review))) + return reviews.map((review) => ReviewResponse.fromType(review)) } } diff --git a/src/core/user/application/__tests__/enable-user.use-case.spec.ts b/src/core/user/application/__tests__/enable-user.use-case.spec.ts index 5d95791..82dcc8a 100644 --- a/src/core/user/application/__tests__/enable-user.use-case.spec.ts +++ b/src/core/user/application/__tests__/enable-user.use-case.spec.ts @@ -3,7 +3,6 @@ import { describe, expect, it } from 'vitest' import { EnableUserRequest } from '@/core/user/dto/requests/enable-user.request' import { container } from '@/lib/container' import { prisma } from '@/lib/prisma/prisma' -import { unexpected } from '@/lib/utils/unexpected' import { createUser } from '@/tests/examples/factories' import { UsersExamples } from '@/tests/examples/users.examples' @@ -17,18 +16,13 @@ describe('EnableUserUseCase', () => { }) // Act - const result = await container.enableUser.with(request) + await container.enableUser.with(request) // Assert - result.match( - async () => { - const updatedUser = await prisma.user.findFirst({ - where: { id: user.id.value }, - }) - expect(updatedUser?.roles.includes('ROLE_MEMBER')).toBeTruthy() - }, - (error) => unexpected.error(error), - ) + const updatedUser = await prisma.user.findFirst({ + where: { id: user.id.value }, + }) + expect(updatedUser?.roles.includes('ROLE_MEMBER')).toBeTruthy() }) it('should disable a user', async () => { @@ -40,17 +34,12 @@ describe('EnableUserUseCase', () => { }) // Act - const result = await container.enableUser.with(request) + await container.enableUser.with(request) // Assert - result.match( - async () => { - const updatedUser = await prisma.user.findFirst({ - where: { id: user.id.value }, - }) - expect(updatedUser?.roles.includes('ROLE_MEMBER')).toBeFalsy() - }, - (error) => unexpected.error(error), - ) + const updatedUser = await prisma.user.findFirst({ + where: { id: user.id.value }, + }) + expect(updatedUser?.roles.includes('ROLE_MEMBER')).toBeFalsy() }) }) diff --git a/src/core/user/application/__tests__/find-user.use-case.spec.ts b/src/core/user/application/__tests__/find-user.use-case.spec.ts index ec91a43..6afdecb 100644 --- a/src/core/user/application/__tests__/find-user.use-case.spec.ts +++ b/src/core/user/application/__tests__/find-user.use-case.spec.ts @@ -1,9 +1,7 @@ import { describe, expect, it } from 'vitest' -import { NotFoundError } from '@/core/common/domain/errors/application/not-found-error' import { FindUserRequest } from '@/core/user/dto/requests/find-user.request' import { container } from '@/lib/container' -import { unexpected } from '@/lib/utils/unexpected' import { createUser } from '@/tests/examples/factories' import { UsersExamples } from '@/tests/examples/users.examples' @@ -16,21 +14,16 @@ describe('FindUserUseCase', () => { }) // Act - const result = await container.findUser.with(request) + const _user = await container.findUser.with(request) // Assert - result.match( - (_user) => { - expect(_user).toEqual({ - email: user.email.value, - id: user.id.value, - image: user.image.value, - name: user.name.value, - roles: user.roles.map((role) => role.value), - }) - }, - (error) => unexpected.error(error), - ) + expect(_user).toEqual({ + email: user.email.value, + id: user.id.value, + image: user.image.value, + name: user.name.value, + roles: user.roles.map((role) => role.value), + }) }) it('should handle not finding a user by email', async () => { @@ -40,14 +33,9 @@ describe('FindUserUseCase', () => { }) // Act - const result = await container.findUser.with(request) + const result = async () => await container.findUser.with(request) // Assert - result.match( - (_user) => unexpected.success(_user), - (error) => { - expect(error).toBeInstanceOf(NotFoundError) - }, - ) + expect(result).rejects.toThrowError() }) }) diff --git a/src/core/user/application/__tests__/update-user.use-case.spec.ts b/src/core/user/application/__tests__/update-user.use-case.spec.ts index 8c73190..6995bcd 100644 --- a/src/core/user/application/__tests__/update-user.use-case.spec.ts +++ b/src/core/user/application/__tests__/update-user.use-case.spec.ts @@ -1,12 +1,10 @@ import { describe, expect, it } from 'vitest' -import { NotFoundError } from '@/core/common/domain/errors/application/not-found-error' import { UpdateUserRequest } from '@/core/user/dto/requests/update-user.request' import { UserResponse } from '@/core/user/dto/responses/user.response' import { container } from '@/lib/container' import { prisma } from '@/lib/prisma/prisma' import { gravatar } from '@/lib/utils/gravatar' -import { unexpected } from '@/lib/utils/unexpected' import { createUser } from '@/tests/examples/factories' import { UsersExamples } from '@/tests/examples/users.examples' @@ -20,20 +18,15 @@ describe('UpdateUserUseCase', () => { }) // Act - const result = await container.updateUser.with(request) + await container.updateUser.with(request) // Assert - result.match( - async () => { - const updatedUser = await prisma.user.findFirst({ - where: { - id: user.id.value, - }, - }) - expect(updatedUser?.name).toEqual('Updated User') + const updatedUser = await prisma.user.findFirst({ + where: { + id: user.id.value, }, - (error) => unexpected.error(error), - ) + }) + expect(updatedUser?.name).toEqual('Updated User') }) it('should handle updating a non-existent user', async () => { @@ -45,14 +38,9 @@ describe('UpdateUserUseCase', () => { }) // Act - const result = await container.updateUser.with(request) + const result = async () => await container.updateUser.with(request) // Assert - result.match( - (_user) => unexpected.success(_user), - (error) => { - expect(error).toBeInstanceOf(NotFoundError) - }, - ) + expect(result).rejects.toThrowError() }) }) diff --git a/src/core/user/application/enable-user.use-case.ts b/src/core/user/application/enable-user.use-case.ts index ddee598..db1f2ac 100644 --- a/src/core/user/application/enable-user.use-case.ts +++ b/src/core/user/application/enable-user.use-case.ts @@ -1,5 +1,3 @@ -import { ok } from 'neverthrow' - import { Email } from '@/core/common/domain/value-objects/email' import { Role } from '@/core/common/domain/value-objects/role' import { User } from '@/core/user/domain/model/user.entity' @@ -9,11 +7,11 @@ import { EnableUserRequest } from '@/core/user/dto/requests/enable-user.request' export class EnableUserUseCase { constructor(private readonly users: Users) {} - async with(command: EnableUserRequest) { - return Email.create(command.email) - .asyncAndThen((email) => this.users.findByEmail(email)) - .andThen((user) => this.enableUser(user, command.enable)) - .andThen((user) => this.users.save(user)) + async with(command: EnableUserRequest): Promise { + const email = Email.create(command.email) + const user = await this.users.findByEmail(email) + this.enableUser(user, command.enable) + await this.users.save(user) } private enableUser(user: User, enable: boolean) { @@ -22,6 +20,6 @@ export class EnableUserUseCase { ? user.roles.add(memberRole) : user.roles.remove(memberRole) - return ok(user) + return user } } diff --git a/src/core/user/application/find-user.use-case.ts b/src/core/user/application/find-user.use-case.ts index e991154..9d00f62 100644 --- a/src/core/user/application/find-user.use-case.ts +++ b/src/core/user/application/find-user.use-case.ts @@ -1,7 +1,3 @@ -import { ok, Result } from 'neverthrow' - -import { NotFoundError } from '@/core/common/domain/errors/application/not-found-error' -import { DomainError } from '@/core/common/domain/errors/domain-error' import { Email } from '@/core/common/domain/value-objects/email' import { Users } from '@/core/user/domain/services/users.repository' import { FindUserRequest } from '@/core/user/dto/requests/find-user.request' @@ -10,19 +6,16 @@ import { UserResponse } from '@/core/user/dto/responses/user.response' export class FindUserUseCase { constructor(private readonly users: Users) {} - async with( - command: FindUserRequest, - ): Promise> { - return Email.create(command.email) - .asyncAndThen((email) => this.users.findByEmail(email)) - .andThen((user) => - ok({ - email: user.email.value, - id: user.id.value, - image: user.image.value, - name: user.name.value, - roles: user.roles.map((role) => role.value), - } as UserResponse), - ) + async with(command: FindUserRequest): Promise { + const email = Email.create(command.email) + const user = await this.users.findByEmail(email) + + return { + email: user.email.value, + id: user.id.value, + image: user.image.value, + name: user.name.value, + roles: user.roles.map((role) => role.value), + } as UserResponse } } diff --git a/src/core/user/application/update-setting.use-case.ts b/src/core/user/application/update-setting.use-case.ts index 3d21a94..6aeeb13 100644 --- a/src/core/user/application/update-setting.use-case.ts +++ b/src/core/user/application/update-setting.use-case.ts @@ -1,8 +1,4 @@ -import { err, ok, Result } from 'neverthrow' - -import { NotFoundError } from '@/core/common/domain/errors/application/not-found-error' import { ApplicationError } from '@/core/common/domain/errors/application-error' -import { DomainError } from '@/core/common/domain/errors/domain-error' import { Email } from '@/core/common/domain/value-objects/email' import { FullName } from '@/core/common/domain/value-objects/fullname' import { User } from '@/core/user/domain/model/user.entity' @@ -12,13 +8,11 @@ import { UpdateSettingRequest } from '@/core/user/dto/requests/update-setting.re export class UpdateSettingUseCase { constructor(private readonly users: Users) {} - async with( - command: UpdateSettingRequest, - ): Promise> { - return Email.create(command.email) - .asyncAndThen((email) => this.users.findByEmail(email)) - .andThen((user) => this.updateUser(user, command)) - .andThen((user) => this.users.save(user)) + async with(command: UpdateSettingRequest): Promise { + const email = Email.create(command.email) + const user = await this.users.findByEmail(email) + + await this.users.save(this.updateUser(user, command)) } private updateUser(user: User, command: UpdateSettingRequest) { @@ -27,20 +21,15 @@ export class UpdateSettingUseCase { return this.updateFullName(command, user) } default: { - return err( - new ApplicationError( - `Field ${command.field} does not exists in User entity`, - ), + throw new ApplicationError( + `Field ${command.field} does not exists in User entity`, ) } } } private updateFullName(command: UpdateSettingRequest, user: User) { - return FullName.create(command.value).andThen((fullName) => { - user.name = fullName - - return ok(user) - }) + user.name = FullName.create(command.value) + return user } } diff --git a/src/core/user/application/update-user.use-case.ts b/src/core/user/application/update-user.use-case.ts index 74047a2..f276c9c 100644 --- a/src/core/user/application/update-user.use-case.ts +++ b/src/core/user/application/update-user.use-case.ts @@ -1,7 +1,3 @@ -import { ok, Result } from 'neverthrow' - -import { NotFoundError } from '@/core/common/domain/errors/application/not-found-error' -import { DomainError } from '@/core/common/domain/errors/domain-error' import { Email } from '@/core/common/domain/value-objects/email' import { FullName } from '@/core/common/domain/value-objects/fullname' import { User } from '@/core/user/domain/model/user.entity' @@ -11,20 +7,16 @@ import { UpdateUserRequest } from '@/core/user/dto/requests/update-user.request' export class UpdateUserUseCase { constructor(private readonly users: Users) {} - async with( - command: UpdateUserRequest, - ): Promise> { - return Email.create(command.email) - .asyncAndThen((email) => this.users.findByEmail(email)) - .andThen((user) => this.updateUser(user, command)) - .andThen((user) => this.users.save(user)) + async with(command: UpdateUserRequest): Promise { + const email = Email.create(command.email) + const user = await this.users.findByEmail(email) + + await this.users.save(this.updateUser(user, command)) } private updateUser(user: User, command: UpdateUserRequest) { - return FullName.create(command.name).andThen((fullName) => { - user.name = fullName + user.name = FullName.create(command.name) - return ok(user) - }) + return user } } diff --git a/src/core/user/domain/model/__tests__/user.spec.ts b/src/core/user/domain/model/__tests__/user.spec.ts index 7089a88..093074e 100644 --- a/src/core/user/domain/model/__tests__/user.spec.ts +++ b/src/core/user/domain/model/__tests__/user.spec.ts @@ -5,7 +5,6 @@ import { Role } from '@/core/common/domain/value-objects/role' import { UserFactory } from '@/core/user/domain/model/user.factory' import { UserResponse } from '@/core/user/dto/responses/user.response' import { gravatar } from '@/lib/utils/gravatar' -import { unexpected } from '@/lib/utils/unexpected' describe('User', () => { describe('create', () => { @@ -23,90 +22,65 @@ describe('User', () => { ) // Assert - user.match( - (_user) => { - expect(_user.name.value).toEqual('Jane Doe') - expect(_user.roles.has(new Role('ROLE_USER'))).toBeTruthy() - expect(_user.email.value).toEqual('admin@example.com') - expect(_user.image.value).toEqual(gravatar('admin@example.com')) - }, - (error) => { - unexpected.error(error) - }, - ) + expect(user.name.value).toEqual('Jane Doe') + expect(user.roles.has(new Role('ROLE_USER'))).toBeTruthy() + expect(user.email.value).toEqual('admin@example.com') + expect(user.image.value).toEqual(gravatar('admin@example.com')) }) it('should return an error for an invalid email', () => { // Arrange const invalidEmail = 'invalid-email' // Act - const user = UserFactory.create( - UserResponse.with({ - email: invalidEmail, - id: ulid(), - image: gravatar(invalidEmail), - name: 'Jane Doe', - roles: ['ROLE_USER'], - }), - ) + const user = () => + UserFactory.create( + UserResponse.with({ + email: invalidEmail, + id: ulid(), + image: gravatar(invalidEmail), + name: 'Jane Doe', + roles: ['ROLE_USER'], + }), + ) // Assert - user.match( - (_user) => { - unexpected.success(_user) - }, - (error) => { - expect(error).toBeDefined() - }, - ) + expect(user).toThrowError() }) it('should return an error for an empty name', () => { // Arrange const emptyName = '' // Act - const user = UserFactory.create( - UserResponse.with({ - email: 'admin@example.com', - id: ulid(), - image: gravatar('admin@example.com'), - name: emptyName, - roles: ['ROLE_USER'], - }), - ) + const user = () => + UserFactory.create( + UserResponse.with({ + email: 'admin@example.com', + id: ulid(), + image: gravatar('admin@example.com'), + name: emptyName, + roles: ['ROLE_USER'], + }), + ) // Assert - user.match( - (_user) => { - unexpected.success(_user) - }, - (error) => { - expect(error).toBeDefined() - }, - ) + expect(user).toThrowError() }) it('should return an error for an invalid role', () => { // Arrange const invalidRole = 'INVALID_ROLE' // Act - const user = UserFactory.create( - UserResponse.with({ - email: 'admin@example.com', - id: ulid(), - image: gravatar('admin@example.com'), - name: 'Jane Doe', - roles: [invalidRole], - }), - ) + const user = () => + UserFactory.create( + UserResponse.with({ + email: 'admin@example.com', + id: ulid(), + image: gravatar('admin@example.com'), + name: 'Jane Doe', + roles: [invalidRole], + }), + ) // Assert - user.match( - (_user) => { - unexpected.success(_user) - }, - (error) => { - expect(error).toBeDefined() - }, - ) + expect(user).toThrowError() }) }) }) diff --git a/src/core/user/domain/model/user.factory.ts b/src/core/user/domain/model/user.factory.ts index 106db2a..963785d 100644 --- a/src/core/user/domain/model/user.factory.ts +++ b/src/core/user/domain/model/user.factory.ts @@ -1,6 +1,3 @@ -import { ok, Result, safeTry } from 'neverthrow' - -import { DomainError } from '@/core/common/domain/errors/domain-error' import { Email } from '@/core/common/domain/value-objects/email' import { FullName } from '@/core/common/domain/value-objects/fullname' import { Image } from '@/core/common/domain/value-objects/image' @@ -10,24 +7,12 @@ import { User } from '@/core/user/domain/model/user.entity' import { UserResponse } from '@/core/user/dto/responses/user.response' export const UserFactory = { - create: (userResponse: UserResponse): Result => - safeTry(function* () { - const id = yield* UserId.create(userResponse.id) - .mapErr((error) => error) - .safeUnwrap() - const email = yield* Email.create(userResponse.email) - .mapErr((error) => error) - .safeUnwrap() - const roles = yield* Roles.create(userResponse.roles) - .mapErr((error) => error) - .safeUnwrap() - const name = yield* FullName.create(userResponse.name) - .mapErr((error) => error) - .safeUnwrap() - const image = yield* Image.create(userResponse.image) - .mapErr((error) => error) - .safeUnwrap() - - return ok(new User(id, email, roles, name, image)) - }), + create: (userResponse: UserResponse): User => { + const id = UserId.create(userResponse.id) + const email = Email.create(userResponse.email) + const roles = Roles.create(userResponse.roles) + const name = FullName.create(userResponse.name) + const image = Image.create(userResponse.image) + return new User(id, email, roles, name, image) + }, } diff --git a/src/core/user/domain/services/users.repository.ts b/src/core/user/domain/services/users.repository.ts index 3385201..dd2f1f6 100644 --- a/src/core/user/domain/services/users.repository.ts +++ b/src/core/user/domain/services/users.repository.ts @@ -1,11 +1,7 @@ -import { ResultAsync } from 'neverthrow' - -import { NotFoundError } from '@/core/common/domain/errors/application/not-found-error' -import { ApplicationError } from '@/core/common/domain/errors/application-error' import { Email } from '@/core/common/domain/value-objects/email' import { User } from '@/core/user/domain/model/user.entity' export interface Users { - findByEmail(email: Email): ResultAsync - save(user: User): ResultAsync + findByEmail(email: Email): Promise + save(user: User): Promise } diff --git a/src/core/user/infrastructure/actions/find-user.ts b/src/core/user/infrastructure/actions/find-user.ts index e38d0b5..19b73f3 100644 --- a/src/core/user/infrastructure/actions/find-user.ts +++ b/src/core/user/infrastructure/actions/find-user.ts @@ -7,10 +7,9 @@ import { container } from '@/lib/container' export async function findUser( email: string, ): Promise { - const result = await container.findUser.with(FindUserRequest.with({ email })) - - return result.match( - (user) => user, - () => undefined, - ) + try { + return await container.findUser.with(FindUserRequest.with({ email })) + } catch { + return undefined + } } diff --git a/src/core/user/infrastructure/actions/find-users.ts b/src/core/user/infrastructure/actions/find-users.ts index eb5a233..8a7e77c 100644 --- a/src/core/user/infrastructure/actions/find-users.ts +++ b/src/core/user/infrastructure/actions/find-users.ts @@ -6,7 +6,13 @@ import { container } from '@/lib/container' export async function findUsers(): Promise { const getCachedUsers = cache( - async () => container.findUsers.with().unwrapOr([]), + async () => { + try { + return await container.findUsers.with() + } catch { + return [] + } + }, ['find-users'], { tags: ['users'], diff --git a/src/core/user/infrastructure/actions/me.ts b/src/core/user/infrastructure/actions/me.ts index 53ad006..543054c 100644 --- a/src/core/user/infrastructure/actions/me.ts +++ b/src/core/user/infrastructure/actions/me.ts @@ -13,10 +13,9 @@ export async function me(): Promise { return undefined } - const result = await container.findUser.with(FindUserRequest.with({ email })) - - return result.match( - (user) => user, - () => undefined, - ) + try { + return await container.findUser.with(FindUserRequest.with({ email })) + } catch { + return undefined + } } diff --git a/src/core/user/infrastructure/persistence/user.publisher.ts b/src/core/user/infrastructure/persistence/user.publisher.ts index f933b2e..efa5517 100644 --- a/src/core/user/infrastructure/persistence/user.publisher.ts +++ b/src/core/user/infrastructure/persistence/user.publisher.ts @@ -1,9 +1,7 @@ import { PrismaClient } from '@prisma/client' -import { ResultAsync } from 'neverthrow' import { ApplicationError } from '@/core/common/domain/errors/application-error' import { Publisher } from '@/core/common/domain/publisher/publisher' -import { ignore } from '@/core/common/utils/ignore' import { User } from '@/core/user/domain/model/user.entity' import { UserDataMapper } from '@/core/user/infrastructure/persistence/user.data-mapper' @@ -12,29 +10,31 @@ export class UserPublisher extends Publisher { super() } - create(user: User): ResultAsync { + async create(user: User): Promise { const data = UserDataMapper.toPrisma(user) - return ResultAsync.fromPromise( - this.prisma.user.create({ + try { + await this.prisma.user.create({ data, - }), - (error: unknown) => new ApplicationError((error as Error).toString()), - ).andThen(ignore) + }) + } catch (error) { + throw new ApplicationError((error as Error).toString()) + } } - update(user: User, version: number): ResultAsync { + async update(user: User, version: number): Promise { const { id, ...data } = UserDataMapper.toPrisma(user) - return ResultAsync.fromPromise( - this.prisma.user.update({ + try { + await this.prisma.user.update({ data, where: { id, version, }, - }), - (error: unknown) => new ApplicationError((error as Error).toString()), - ).andThen(ignore) + }) + } catch (error) { + throw new ApplicationError((error as Error).toString()) + } } } diff --git a/src/core/user/infrastructure/queries/find-all-users.query.ts b/src/core/user/infrastructure/queries/find-all-users.query.ts index 2147cb9..9de1c89 100644 --- a/src/core/user/infrastructure/queries/find-all-users.query.ts +++ b/src/core/user/infrastructure/queries/find-all-users.query.ts @@ -1,20 +1,18 @@ import { PrismaClient } from '@prisma/client' -import { okAsync, ResultAsync } from 'neverthrow' -import { ApplicationError } from '@/core/common/domain/errors/application-error' import { UserResponse } from '@/core/user/dto/responses/user.response' import { UserType } from '@/core/user/infrastructure/persistence/user.type' export class FindAllUsersQuery { constructor(private readonly prisma: PrismaClient) {} - with(): ResultAsync { - return ResultAsync.fromSafePromise(this.prisma.user.findMany({})).andThen( - (users) => this.mapToUserResponse(users), - ) + async with(): Promise { + const users = await this.prisma.user.findMany({}) + + return this.mapToUserResponse(users) } private mapToUserResponse(users: UserType[]) { - return okAsync(users.map((user) => UserResponse.fromType(user))) + return users.map((user) => UserResponse.fromType(user)) } } diff --git a/src/core/user/infrastructure/services/users-prisma.repository.ts b/src/core/user/infrastructure/services/users-prisma.repository.ts index 73996f3..2b3cd37 100644 --- a/src/core/user/infrastructure/services/users-prisma.repository.ts +++ b/src/core/user/infrastructure/services/users-prisma.repository.ts @@ -1,8 +1,6 @@ import { PrismaClient } from '@prisma/client' -import { okAsync, ResultAsync } from 'neverthrow' import { NotFoundError } from '@/core/common/domain/errors/application/not-found-error' -import { ApplicationError } from '@/core/common/domain/errors/application-error' import { Email } from '@/core/common/domain/value-objects/email' import { User } from '@/core/user/domain/model/user.entity' import { Users } from '@/core/user/domain/services/users.repository' @@ -16,18 +14,21 @@ export class UsersPrisma implements Users { this.publisher = new UserPublisher(prisma) } - findByEmail(email: Email): ResultAsync { - return ResultAsync.fromPromise( - this.prisma.user.findUniqueOrThrow({ + async findByEmail(email: Email): Promise { + try { + const user = await this.prisma.user.findUniqueOrThrow({ where: { email: email.value, }, - }), - () => new NotFoundError('user_email_not_found'), - ).andThen((user) => okAsync(UserDataMapper.toModel(user))) + }) + + return UserDataMapper.toModel(user) + } catch { + throw new NotFoundError('user_email_not_found') + } } - save(user: User): ResultAsync { + async save(user: User): Promise { return this.publisher.mergeObjectContext(user).commit() } }