diff --git a/.yarn/install-state.gz b/.yarn/install-state.gz index 5ba07dc..2a61f36 100644 Binary files a/.yarn/install-state.gz and b/.yarn/install-state.gz differ diff --git a/README-ko.md b/README-ko.md index 1db8bb3..4d27244 100644 --- a/README-ko.md +++ b/README-ko.md @@ -59,11 +59,13 @@ │ ├─ dtos │ └─ infrastructures │ └─ interface -├─ client-a +├─ client-a(built with React) │ └─ src +│ ├─ di │ └─ ... -└─ client-b +└─ client-b(built with Next.js) └─ src + ├─ di └─ ... ``` @@ -150,7 +152,7 @@ Presenters 레이어에서는 UI에서 필요로하는 메서드를 가지고 Vite, React, Jotai, Tailwind CSS, Jest, RTL, Cypress ``` -client-a는 `Domains`와 `Adapters` 레이어의 요소들을 그대로 사용해서 최종적으로 `DI`된 상위 레이어의 객체를 React의 Hooks와 전역 상태 라이브러리인 [Jotai](https://jotai.org/)를 활용하여 각 도메인의 메서드를 구현하고 이는 Presenters 레이어의 역할을 수행합니다. +Client-A는 `Domains`와 `Adapters` 레이어의 요소들을 그대로 사용해서 최종적으로 `DI`된 상위 레이어의 객체를 React의 Hooks와 전역 상태 라이브러리인 [Jotai](https://jotai.org/)를 활용하여 각 도메인의 메서드를 구현하고 이는 Presenters 레이어의 역할을 수행합니다. > 기존에 Adapters 패키지에서 Presenters 디렉토리로 명시적으로 Presenters 레이어를 나누었지만 이는 프레임워크에 의존하지 않은 범용적인 Presenters이며, 위 샘플 프로젝트처럼 React를 사용하는 서비스에서는 그에 부합하는 구성을 위해서 최종적으로 의존성을 주입한 Presenters 객체와 React Hooks을 활용하여 Presenters 영역을 확장 구성하였습니다. @@ -174,24 +176,28 @@ export default function di() { ### Presenters ```tsx -import { useCallback, useMemo, useTransition } from "react" +import { useCallback, useMemo, useOptimistic, useState, useTransition } from "react" import { atom, useAtom } from "jotai" -import IPost from "domains/aggregates/interfaces/IPost" -import Post from "domains/aggregates/Post" import presenters from "../di" +import PostVM from "../vms/PostVM" +import IPostVM from "../vms/interfaces/IPostVM" -const PostsAtoms = atom([]) +const PostsAtoms = atom([]) export default function usePosts() { const di = useMemo(() => presenters(), []) - const [posts, setPosts] = useAtom(PostsAtoms) + const [post, setPost] = useState(null) + const [posts, setPosts] = useAtom(PostsAtoms) + const [optimisticPost, setOptimisticPost] = useOptimistic(post) + const [optimisticPosts, setOptimisticPosts] = useOptimistic(posts) const [isPending, startTransition] = useTransition() const getPosts = useCallback(async () => { startTransition(async () => { const resPosts = await di.post.getPosts() - setPosts(resPosts) + const postVMs = resPosts.map((post) => new PostVM(post)) + setPosts(postVMs) }) }, [di.post, setPosts]) @@ -199,6 +205,89 @@ export default function usePosts() { } ``` +### View Models + +Client-A에서는 프로젝트 레이어에서 React의 UI 상태 관리에 적합하도록 View Model을 구성하여 사용하였습니다. + +```ts +import CryptoJS from "crypto-js" +import IUserInfoVO from "domains/vos/interfaces/IUserInfoVO" +import ICommentVM, { ICommentVMParams } from "./interfaces/ICommentVM" + +export default class CommentVM implements ICommentVM { + readonly id: string + key: string + readonly postId: string + readonly author: IUserInfoVO + content: string + readonly createdAt: Date + updatedAt: Date + + constructor(parmas: ICommentVMParams) { + this.id = parmas.id + this.postId = parmas.postId + this.author = parmas.author + this.content = parmas.content + this.createdAt = parmas.createdAt + this.updatedAt = parmas.updatedAt + this.key = this.generateKey(this.id, this.updatedAt) + } + + updateContent(content: string): void { + this.content = content + this.updatedAt = new Date() + this.key = this.generateKey(this.id, this.updatedAt) + } + + applyUpdatedAt(date: Date): void { + this.updatedAt = date + this.key = this.generateKey(this.id, this.updatedAt) + } + + private generateKey(id: string, updatedAt: Date): string { + const base = `${id}-${updatedAt.getTime()}` + return CryptoJS.MD5(base).toString() + } +} +``` + +View Model에서는 위와 같이 값 변경에 따른 메서드를 제공하며(ex. updateContent) 모든 변경에는 updatedAt 값이 함께 변경하고, updatedAt 값과 ID 값을 활용하여 고유한 `Key` 값을 만들어 사용함으로써 React가 View의 변경을 감지하고 리렌더링 할 수 있도록 하였습니다. + +```tsx +... + +export default function usePosts() { + ... + + const deleteComment = useCallback( + async (commentId: string) => { + startTransition(async () => { + setOptimisticPost((prevPost) => { + prevPost.deleteComment(commentId) + return prevPost + }) + + try { + const isSucess = await di.post.deleteComment(commentId) + if (isSucess) { + const resPost = await di.post.getPost(optimisticPost.id) + const postVM = new PostVM(resPost) + setPost(postVM) + } + } catch (e) { + console.error(e) + } + }) + }, + [di.post, optimisticPost, setOptimisticPost, setPost] + ) + + ... +} +``` + +Presenter 레이어의 Hooks에서도 위와 같이 Comment의 삭제 요청에 대한 간단한 예시로, VM에서 제공하는 메서드를 활용하여 낙관적 업데이트를 구현하고 요청이 성공하면 위 변경이 적용된 새로운 데이터를 요청하여 동기화 하도록 하였습니다. + ## Client-B ### Use Stack @@ -207,9 +296,11 @@ export default function usePosts() { Next.js, Jotai, Tailwind CSS, Jest, RTL, Cypress ``` -client-b 서비스는 client-a 서비스와 동일한 도메인을 활용한, 서비스 확장을 표현하는 서비스로 client-a 서비스와 유사하지만 client-a 서비스와 다르게 Next.js를 기반으로 기존의 client-a 서비스는 API 서버와의 HTTP 통신을 통해 데이터를 조작하지만 client-b는 HTTP 통신 없이 로컬 저장소(Local Storage)를 기반으로 설계하였습니다. +Client-B는 Client-A와 동일한 도메인을 활용한, 서비스 확장을 표현하는 서비스로 Client-A 서비스와 유사하지만 Client-A 서비스와 다르게 Next.js를 기반으로 하며 기존의 Client-A 서비스는 API 서버와의 HTTP 통신을 통해 데이터를 조작하지만 Client-b는 HTTP 통신 없이 로컬 저장소(Local Storage)를 기반으로 설계하였습니다. + +그렇기 때문에 Client-A와 다르게 Client-B에서는 `Domains`에서 정의한 Repository의 인터페이스를 구체화한 새로운 Repository를 구성하고 이를 의존성 주입하여 사용함으로써 간단하게 기존의 서비스를 확장한 새로운 서비스를 구현할 수 있습니다. -그렇기 때문에 client-a와 다르게 client-b에서는 `Domains`에서 정의한 Repository의 인터페이스를 구체화한 새로운 Repository를 구성하고 이를 의존성 주입하여 사용함으로써 간단하게 기존의 서비스를 확장한 새로운 서비스를 구현할 수 있습니다. +> Client-B는 구제적인 기능 구현보다는 동일한 도메인을 활용한 다른 클라이언트 서비스 구성에 대한 간단한 예시입니다. ## Design System diff --git a/README.md b/README.md index ce4c30b..e41409a 100644 --- a/README.md +++ b/README.md @@ -62,17 +62,19 @@ In the monorepo structure, the Domains layer, Adapters layer, and Service layer │ ├─ dtos │ └─ infrastructures │ └─ interface -├─ client-a +├─ client-a(built with React) │ └─ src +│ ├─ di │ └─ ... -└─ client-b +└─ client-b(built with Next.js) └─ src + ├─ di └─ ... ``` ## Tree Shaking -In this sample project, service packages use shared packages (`Domains`, `Adapters`, `and other potential packages`) through a `Source-to-Source` approach, rather than referencing pre-built outputs. This approach ensures that the service’s module bundler can effectively eliminate unused code during the final build. Therefore, all shared packages must be written using `ES Modules`. +In this sample project, service packages use shared packages (`Domains`, `Adapters`, `and other potential packages`) through a `Source-to-Source` approach, rather than referencing pre-built outputs. This approach ensures that the service's module bundler can effectively eliminate unused code during the final build. Therefore, all shared packages must be written using `ES Modules`. > Most module bundlers natively support tree shaking for code written in ES Modules. @@ -92,7 +94,7 @@ In the sample project, there are three entities: Post, Comment, and User. Clean Architecture shares a common goal with DDD in pursuing domain-centric design. While Clean Architecture focuses on structural flexibility, maintainability, technological independence, and testability of software, DDD emphasizes solving complex business problems. -However, Clean Architecture adopts some of DDD’s philosophy and principles, making it compatible with DDD and providing a framework to effectively implement DDD concepts. For example, Clean Architecture can leverage DDD concepts such as `Ubiquitous Language` and `Aggregate Root`. +However, Clean Architecture adopts some of DDD's philosophy and principles, making it compatible with DDD and providing a framework to effectively implement DDD concepts. For example, Clean Architecture can leverage DDD concepts such as `Ubiquitous Language` and `Aggregate Root`. ### Ubiquitous Language @@ -108,7 +110,7 @@ Ubiquitous Language refers to a shared language used by all team members to main An Aggregate is a consistency boundary that can include multiple entities and value objects. It encapsulates internal state and controls external access. All modifications must go through the Aggregate Root, which helps manage the complexity of relationships within the model and maintain consistency when services expand or transactions become more complex. -In the sample project, Post serves as an Aggregate, with the Comment entity having a dependent relationship on it. Therefore, adding or modifying a comment must be done through the Post entity. Additionally, while the Post entity requires information about the author (the User who wrote the post), the User is an independent entity. To maintain a loose relationship, only the User’s id and name are included as a Value Object within Post. +In the sample project, Post serves as an Aggregate, with the Comment entity having a dependent relationship on it. Therefore, adding or modifying a comment must be done through the Post entity. Additionally, while the Post entity requires information about the author (the User who wrote the post), the User is an independent entity. To maintain a loose relationship, only the User's id and name are included as a Value Object within Post. ## Use Cases @@ -153,7 +155,7 @@ The sample project's client services consist of two simple services: client-a an Vite, React, Jotai, Tailwind CSS, Jest, RTL, Cypress ``` -client-a directly utilizes elements from the `Domains` and `Adapters` layers and implements methods for each domain using React hooks and the global state management library [Jotai](https://jotai.org/). These methods act as the Presenters layer in the final service. +Client-A directly utilizes elements from the `Domains` and `Adapters` layers and implements methods for each domain using React hooks and the global state management library [Jotai](https://jotai.org/). These methods act as the Presenters layer in the final service. > Previously, the Adapters package explicitly included a Presenters directory to represent a framework-agnostic Presenters layer. However, in services like this sample project that use React, we extend the Presenters layer by injecting dependencies into the final Presenters objects and utilizing React hooks to achieve a composition that aligns with the framework. @@ -177,24 +179,28 @@ export default function di() { ### Presenters ```tsx -import { useCallback, useMemo, useTransition } from "react" +import { useCallback, useMemo, useOptimistic, useState, useTransition } from "react" import { atom, useAtom } from "jotai" -import IPost from "domains/aggregates/interfaces/IPost" -import Post from "domains/aggregates/Post" import presenters from "../di" +import PostVM from "../vms/PostVM" +import IPostVM from "../vms/interfaces/IPostVM" -const PostsAtoms = atom([]) +const PostsAtoms = atom([]) export default function usePosts() { const di = useMemo(() => presenters(), []) - const [posts, setPosts] = useAtom(PostsAtoms) + const [post, setPost] = useState(null) + const [posts, setPosts] = useAtom(PostsAtoms) + const [optimisticPost, setOptimisticPost] = useOptimistic(post) + const [optimisticPosts, setOptimisticPosts] = useOptimistic(posts) const [isPending, startTransition] = useTransition() const getPosts = useCallback(async () => { startTransition(async () => { const resPosts = await di.post.getPosts() - setPosts(resPosts) + const postVMs = resPosts.map((post) => new PostVM(post)) + setPosts(postVMs) }) }, [di.post, setPosts]) @@ -202,6 +208,89 @@ export default function usePosts() { } ``` +### View Models + +In Client-A, we structured the View Model in the project layer to effectively manage UI state in React. + +```ts +import CryptoJS from "crypto-js" +import IUserInfoVO from "domains/vos/interfaces/IUserInfoVO" +import ICommentVM, { ICommentVMParams } from "./interfaces/ICommentVM" + +export default class CommentVM implements ICommentVM { + readonly id: string + key: string + readonly postId: string + readonly author: IUserInfoVO + content: string + readonly createdAt: Date + updatedAt: Date + + constructor(parmas: ICommentVMParams) { + this.id = parmas.id + this.postId = parmas.postId + this.author = parmas.author + this.content = parmas.content + this.createdAt = parmas.createdAt + this.updatedAt = parmas.updatedAt + this.key = this.generateKey(this.id, this.updatedAt) + } + + updateContent(content: string): void { + this.content = content + this.updatedAt = new Date() + this.key = this.generateKey(this.id, this.updatedAt) + } + + applyUpdatedAt(date: Date): void { + this.updatedAt = date + this.key = this.generateKey(this.id, this.updatedAt) + } + + private generateKey(id: string, updatedAt: Date): string { + const base = `${id}-${updatedAt.getTime()}` + return CryptoJS.MD5(base).toString() + } +} +``` + +The View Model provides methods to handle value changes (e.g., updateContent). Whenever a value is updated, the updatedAt field is also modified. By using a combination of the updatedAt value and the ID, we generate a unique `key` that allows React to detect changes in the view and trigger re-renders as needed. + +```tsx +... + +export default function usePosts() { + ... + + const deleteComment = useCallback( + async (commentId: string) => { + startTransition(async () => { + setOptimisticPost((prevPost) => { + prevPost.deleteComment(commentId) + return prevPost + }) + + try { + const isSucess = await di.post.deleteComment(commentId) + if (isSucess) { + const resPost = await di.post.getPost(optimisticPost.id) + const postVM = new PostVM(resPost) + setPost(postVM) + } + } catch (e) { + console.error(e) + } + }) + }, + [di.post, optimisticPost, setOptimisticPost, setPost] + ) + + ... +} +``` + +In the Presenter layer's hooks, we implemented optimistic updates using the methods provided by the View Model. For instance, when sending a delete request for a comment, we immediately apply the changes locally. After the request succeeds, we fetch the updated data to synchronize the state. + ## Client-B ### Use Stack @@ -210,9 +299,11 @@ export default function usePosts() { Next.js, Jotai, Tailwind CSS, Jest, RTL, Cypress ``` -The client-b service is an extension of client-a, utilizing the same domain model to demonstrate service scalability. While it shares similarities with client-a, the key difference is that client-b is built on Next.js. Unlike client-a, which manipulates data through HTTP communication with an API server, client-b is designed to operate without HTTP communication, relying instead on local storage. +The Client-B service is an extension of Client-A, utilizing the same domain model to demonstrate service scalability. While it shares similarities with Client-A, the key difference is that Client-B is built on Next.js. Unlike Client-A, which manipulates data through HTTP communication with an API server, Client-B is designed to operate without HTTP communication, relying instead on local storage. + +Therefore, unlike Client-A, Client-B implements new repositories by concretely defining the repository interfaces from `Domains` and injecting these dependencies to create a new service that extends the existing functionality in a straightforward manner. -Therefore, unlike client-a, client-b implements new repositories by concretely defining the repository interfaces from `Domains` and injecting these dependencies to create a new service that extends the existing functionality in a straightforward manner. +> Client-B is a simple demonstration of another client service utilizing the same domain, focusing on the service structure rather than specific feature implementations. ## Design System diff --git a/package.json b/package.json index 55ad7ed..e704a4f 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,8 @@ "typescript": "^5.6.3" }, "resolutions": { - "path-to-regexp": "0.1.12" + "path-to-regexp": "0.1.12", + "esbuild": "0.25.0" }, "packageManager": "yarn@4.2.2" } diff --git a/packages/adapters/package.json b/packages/adapters/package.json index 2dd6cee..56c3fea 100644 --- a/packages/adapters/package.json +++ b/packages/adapters/package.json @@ -7,7 +7,11 @@ }, "dependencies": { "axios": "^1.7.7", + "crypto-js": "^4.2.0", "domains": "workspace:*" }, - "packageManager": "yarn@4.2.2" + "packageManager": "yarn@4.2.2", + "devDependencies": { + "@types/crypto-js": "^4" + } } diff --git a/packages/adapters/src/__test__/dtos/UserDTO.spec.ts b/packages/adapters/src/__test__/dtos/UserDTO.spec.ts index da9d759..7a4cbaa 100644 --- a/packages/adapters/src/__test__/dtos/UserDTO.spec.ts +++ b/packages/adapters/src/__test__/dtos/UserDTO.spec.ts @@ -7,8 +7,8 @@ describe("UserDTO", () => { id: "12345", name: "Falsy", email: "falsy@mail.com", - createdAt: new Date("2023-01-01T00:00:00Z"), - updatedAt: new Date("2023-01-02T00:00:00Z") + createdAt: "2023-01-01T00:00:00Z", + updatedAt: "2023-01-02T00:00:00Z" } const user = new UserDTO(params) @@ -16,7 +16,7 @@ describe("UserDTO", () => { expect(user.id).toBe("12345") expect(user.name).toBe("Falsy") expect(user.email).toBe("falsy@mail.com") - expect(user.createdAt).toEqual(new Date("2023-01-01T00:00:00Z")) - expect(user.updatedAt).toEqual(new Date("2023-01-02T00:00:00Z")) + expect(user.createdAt).toEqual("2023-01-01T00:00:00Z") + expect(user.updatedAt).toEqual("2023-01-02T00:00:00Z") }) }) diff --git a/packages/adapters/src/dtos/CommentDTO.ts b/packages/adapters/src/dtos/CommentDTO.ts index 765694b..705d7f6 100644 --- a/packages/adapters/src/dtos/CommentDTO.ts +++ b/packages/adapters/src/dtos/CommentDTO.ts @@ -6,8 +6,8 @@ export default class CommentDTO implements ICommentDTO { readonly postId: string content: string readonly author: IUserInfoVO - readonly createdAt: Date - updatedAt: Date + readonly createdAt: string + updatedAt: string constructor(params: ICommentDTO) { this.id = params.id diff --git a/packages/adapters/src/dtos/PostDTO.ts b/packages/adapters/src/dtos/PostDTO.ts index 337d0dd..bd22577 100644 --- a/packages/adapters/src/dtos/PostDTO.ts +++ b/packages/adapters/src/dtos/PostDTO.ts @@ -6,8 +6,8 @@ export default class PostDTO implements IPostDTO { title: string content: string readonly author: IUserInfoVO - readonly createdAt: Date - updatedAt: Date + readonly createdAt: string + updatedAt: string constructor(post: IPostDTO) { this.id = post.id diff --git a/packages/adapters/src/dtos/UserDTO.ts b/packages/adapters/src/dtos/UserDTO.ts index 4332b49..e9113ca 100644 --- a/packages/adapters/src/dtos/UserDTO.ts +++ b/packages/adapters/src/dtos/UserDTO.ts @@ -4,8 +4,8 @@ export default class UserDTO implements IUserDTO { readonly id: string readonly name: string readonly email: string - readonly createdAt: Date - readonly updatedAt: Date + readonly createdAt: string + readonly updatedAt: string constructor(params: IUserDTO) { this.id = params.id diff --git a/packages/adapters/src/presenters/PostPresenter.ts b/packages/adapters/src/presenters/PostPresenter.ts index a48bcc4..271c4cf 100644 --- a/packages/adapters/src/presenters/PostPresenter.ts +++ b/packages/adapters/src/presenters/PostPresenter.ts @@ -21,8 +21,8 @@ export default class PostPresenter implements IPostPresenter { return this.postUseCase.createPost(params) } - editPost(postId: string, params: IRequestPostParams): Promise { - return this.postUseCase.editPost(postId, params) + updatePost(postId: string, params: IRequestPostParams): Promise { + return this.postUseCase.updatePost(postId, params) } deletePost(postId: string): Promise { @@ -33,8 +33,8 @@ export default class PostPresenter implements IPostPresenter { return this.postUseCase.createComment(postId, content) } - editComment(commentId: string, content: string): Promise { - return this.postUseCase.editComment(commentId, content) + updateComment(commentId: string, content: string): Promise { + return this.postUseCase.updateComment(commentId, content) } deleteComment(commentId: string): Promise { diff --git a/packages/adapters/src/presenters/interfaces/IPostPresenter.ts b/packages/adapters/src/presenters/interfaces/IPostPresenter.ts index 352a93d..e54d97e 100644 --- a/packages/adapters/src/presenters/interfaces/IPostPresenter.ts +++ b/packages/adapters/src/presenters/interfaces/IPostPresenter.ts @@ -4,9 +4,9 @@ export default interface IPostPresenter { getPosts(): Promise getPost(postId: string): Promise createPost(params: IRequestPostParams): Promise - editPost(postId: string, params: IRequestPostParams): Promise + updatePost(postId: string, params: IRequestPostParams): Promise deletePost(postId: string): Promise createComment(postId: string, content: string): Promise - editComment(commentId: string, content: string): Promise + updateComment(commentId: string, content: string): Promise deleteComment(commentId: string): Promise } diff --git a/packages/adapters/src/repositories/CommentRepository.ts b/packages/adapters/src/repositories/CommentRepository.ts index b65ee91..7ffbdab 100644 --- a/packages/adapters/src/repositories/CommentRepository.ts +++ b/packages/adapters/src/repositories/CommentRepository.ts @@ -39,9 +39,9 @@ export default class CommentRepository implements ICommentRepository { } } - async editComment(commentId: string, content: string): Promise { + async updateComment(commentId: string, content: string): Promise { try { - const { data } = await this.client.put( + const { data } = await this.client.put( `/api/comments/${commentId}`, { content diff --git a/packages/adapters/src/repositories/PostRepository.ts b/packages/adapters/src/repositories/PostRepository.ts index bf9ca78..9c5abc7 100644 --- a/packages/adapters/src/repositories/PostRepository.ts +++ b/packages/adapters/src/repositories/PostRepository.ts @@ -57,9 +57,12 @@ export default class PostRepository implements IPostRepository { } } - async editPost(postId: string, params: IRequestPostParams): Promise { + async updatePost( + postId: string, + params: IRequestPostParams + ): Promise { try { - const { data } = await this.client.put( + const { data } = await this.client.put( `/api/posts/${postId}`, params ) diff --git a/packages/client-a/src/components/atoms/CommentList.tsx b/packages/client-a/src/components/atoms/CommentList.tsx index e1be969..69bf7c8 100644 --- a/packages/client-a/src/components/atoms/CommentList.tsx +++ b/packages/client-a/src/components/atoms/CommentList.tsx @@ -1,11 +1,11 @@ -import IComment from "domains/entities/interfaces/IComment" +import ICommentVM from "../../vms/interfaces/ICommentVM" import Button from "./Button" export default function CommentList({ comments, deleteComment }: { - comments?: IComment[] + comments: ICommentVM[] deleteComment: (commentId: string) => void }) { return ( @@ -13,9 +13,9 @@ export default function CommentList({

Comments

    - {comments?.map((comment) => ( + {comments.map((comment) => (
  • diff --git a/packages/client-a/src/components/molecules/PostBox.tsx b/packages/client-a/src/components/molecules/PostBox.tsx index f4e837b..889db66 100644 --- a/packages/client-a/src/components/molecules/PostBox.tsx +++ b/packages/client-a/src/components/molecules/PostBox.tsx @@ -1,29 +1,82 @@ import { Link } from "react-router" import IPost from "domains/aggregates/interfaces/IPost" import Button from "../atoms/Button" +import { ChangeEvent, useState } from "react" +import Input from "../atoms/Input" export default function PostBox({ post, + updatePost, deletePost }: { post: IPost - deletePost: (id: string) => void + updatePost(id: string, title: string, content: string): void + deletePost(id: string): void }) { - const { id, title } = post + const { id, title: bTitle, content: bContent } = post + + const [isEdit, setIsEdit] = useState(false) + const [title, setTitle] = useState(bTitle) + const [content, setContent] = useState(bContent) + + const handleChangeTitle = (e: ChangeEvent) => { + setTitle(e.target.value) + } + + const handleChangeContent = (e: ChangeEvent) => { + setContent(e.target.value) + } + + const handleClickUpdatePost = () => { + if (!title || !content) { + window.alert("Please enter a title and content.") + return + } + updatePost(id, title, content) + setIsEdit(false) + } return (
    -

    - Title: - - {title} - -

    -

    + +

    + + Title: + {bTitle} + + + Content: + {bContent} + +
    + +
    +
    + {isEdit && ( +
    +
    + + +
    +
    +
    +
    + )}
    ) } diff --git a/packages/client-a/src/components/molecules/PostList.tsx b/packages/client-a/src/components/molecules/PostList.tsx index 036e521..296d56f 100644 --- a/packages/client-a/src/components/molecules/PostList.tsx +++ b/packages/client-a/src/components/molecules/PostList.tsx @@ -3,9 +3,11 @@ import PostBox from "./PostBox" const PostList = ({ posts, + updatePost, deletePost }: { posts: IPost[] + updatePost(id: string, title: string, content: string): void deletePost(id: string): void }) => { return ( @@ -17,7 +19,11 @@ const PostList = ({ key={post.id} className="p-4 border border-gray/40 rounded-md text-sm" > - +
  • ))}
diff --git a/packages/client-a/src/components/molecules/SideMenu.tsx b/packages/client-a/src/components/molecules/SideMenu.tsx index 8c0b3ab..74ca9b2 100644 --- a/packages/client-a/src/components/molecules/SideMenu.tsx +++ b/packages/client-a/src/components/molecules/SideMenu.tsx @@ -4,11 +4,11 @@ export default function SideMenu() { return ( ) diff --git a/packages/client-a/src/components/organisms/contents/DashboardContent.tsx b/packages/client-a/src/components/organisms/contents/DashboardContent.tsx index 6738e62..37bf5f3 100644 --- a/packages/client-a/src/components/organisms/contents/DashboardContent.tsx +++ b/packages/client-a/src/components/organisms/contents/DashboardContent.tsx @@ -6,7 +6,8 @@ import PostList from "../../molecules/PostList" import DimmedLoading from "../../atoms/DimmedLoading" export default function DashboardContent() { - const { isPending, posts, getPosts, createPost, deletePost } = usePosts() + const { isPending, posts, getPosts, createPost, updatePost, deletePost } = + usePosts() useEffect(() => { getPosts() @@ -24,7 +25,11 @@ export default function DashboardContent() {

Dashboard

- + {isPending && }
diff --git a/packages/client-a/src/components/organisms/contents/PostContent.tsx b/packages/client-a/src/components/organisms/contents/PostContent.tsx index 68ed85a..cd2fd28 100644 --- a/packages/client-a/src/components/organisms/contents/PostContent.tsx +++ b/packages/client-a/src/components/organisms/contents/PostContent.tsx @@ -26,15 +26,17 @@ export default function PostContent() { return (

Post

-
- - - - {isPending && } -
+ {post && ( +
+ + + + {isPending && } +
+ )}
) } diff --git a/packages/client-a/src/hooks/usePosts.ts b/packages/client-a/src/hooks/usePosts.ts index 5c9fcda..c065103 100644 --- a/packages/client-a/src/hooks/usePosts.ts +++ b/packages/client-a/src/hooks/usePosts.ts @@ -7,17 +7,17 @@ import { useTransition } from "react" import { atom, useAtom } from "jotai" -import IPost from "domains/aggregates/interfaces/IPost" -import Post from "domains/aggregates/Post" import presenters from "../di" +import PostVM from "../vms/PostVM" +import IPostVM from "../vms/interfaces/IPostVM" -const PostsAtoms = atom([]) +const PostsAtoms = atom([]) export default function usePosts() { const di = useMemo(() => presenters(), []) - const [post, setPost] = useState(null) - const [posts, setPosts] = useAtom(PostsAtoms) + const [post, setPost] = useState(null) + const [posts, setPosts] = useAtom(PostsAtoms) const [optimisticPost, setOptimisticPost] = useOptimistic(post) const [optimisticPosts, setOptimisticPosts] = useOptimistic(posts) const [isPending, startTransition] = useTransition() @@ -25,7 +25,8 @@ export default function usePosts() { const getPosts = useCallback(async () => { startTransition(async () => { const resPosts = await di.post.getPosts() - setPosts(resPosts) + const postVMs = resPosts.map((post) => new PostVM(post)) + setPosts(postVMs) }) }, [di.post, setPosts]) @@ -33,7 +34,8 @@ export default function usePosts() { async (postId: string) => { startTransition(async () => { const resPost = await di.post.getPost(postId) - setPost(resPost) + const postVM = new PostVM(resPost) + setPost(postVM) }) }, [di.post, setPost] @@ -45,13 +47,43 @@ export default function usePosts() { const isSucess = await di.post.createPost({ title, content }) if (isSucess) { const resPosts = await di.post.getPosts() - setPosts(resPosts) + const postVMs = resPosts.map((post) => new PostVM(post)) + setPosts(postVMs) } }) }, [di.post, setPosts] ) + const updatePost = useCallback( + async (postId: string, title: string, content: string) => { + startTransition(async () => { + setOptimisticPosts((prevPosts) => { + return prevPosts.map((post) => { + if (post.id === postId) { + post.updateTitle(title) + post.updateContent(content) + } + return post + }) + }) + + const updateAt = await di.post.updatePost(postId, { title, content }) + if (updateAt !== "") { + setPosts((prevPosts) => { + return prevPosts.map((post) => { + if (post.id === postId) { + post.applyUpdatedAt(new Date(updateAt)) + } + return post + }) + }) + } + }) + }, + [di.post, setOptimisticPosts, setPosts] + ) + const deletePost = useCallback( async (postId: string) => { startTransition(async () => { @@ -63,7 +95,8 @@ export default function usePosts() { const isSucess = await di.post.deletePost(postId) if (isSucess) { const resPosts = await di.post.getPosts() - setPosts(resPosts) + const postVMs = resPosts.map((post) => new PostVM(post)) + setPosts(postVMs) } } catch (e) { console.error(e) @@ -79,7 +112,8 @@ export default function usePosts() { const isSucess = await di.post.createComment(postId, content) if (isSucess) { const resPost = await di.post.getPost(postId) - setPost(resPost) + const postVM = new PostVM(resPost) + setPost(postVM) } }) }, @@ -90,20 +124,16 @@ export default function usePosts() { async (commentId: string) => { startTransition(async () => { setOptimisticPost((prevPost) => { - const newPost = new Post({ - ...prevPost, - comments: prevPost.comments.filter( - (comment) => comment.id !== commentId - ) - }) - return newPost + prevPost.deleteComment(commentId) + return prevPost }) try { const isSucess = await di.post.deleteComment(commentId) if (isSucess) { const resPost = await di.post.getPost(optimisticPost.id) - setPost(resPost) + const postVM = new PostVM(resPost) + setPost(postVM) } } catch (e) { console.error(e) @@ -120,6 +150,7 @@ export default function usePosts() { getPosts, getPost, createPost, + updatePost, deletePost, createComment, deleteComment diff --git a/packages/client-a/src/vms/CommentVM.ts b/packages/client-a/src/vms/CommentVM.ts new file mode 100644 index 0000000..4820512 --- /dev/null +++ b/packages/client-a/src/vms/CommentVM.ts @@ -0,0 +1,39 @@ +import CryptoJS from "crypto-js" +import IUserInfoVO from "domains/vos/interfaces/IUserInfoVO" +import ICommentVM, { ICommentVMParams } from "./interfaces/ICommentVM" + +export default class CommentVM implements ICommentVM { + readonly id: string + key: string + readonly postId: string + readonly author: IUserInfoVO + content: string + readonly createdAt: Date + updatedAt: Date + + constructor(parmas: ICommentVMParams) { + this.id = parmas.id + this.postId = parmas.postId + this.author = parmas.author + this.content = parmas.content + this.createdAt = parmas.createdAt + this.updatedAt = parmas.updatedAt + this.key = this.generateKey(this.id, this.updatedAt) + } + + updateContent(content: string): void { + this.content = content + this.updatedAt = new Date() + this.key = this.generateKey(this.id, this.updatedAt) + } + + applyUpdatedAt(date: Date): void { + this.updatedAt = date + this.key = this.generateKey(this.id, this.updatedAt) + } + + private generateKey(id: string, updatedAt: Date): string { + const base = `${id}-${updatedAt.getTime()}` + return CryptoJS.MD5(base).toString() + } +} diff --git a/packages/client-a/src/vms/PostVM.ts b/packages/client-a/src/vms/PostVM.ts new file mode 100644 index 0000000..c728dce --- /dev/null +++ b/packages/client-a/src/vms/PostVM.ts @@ -0,0 +1,55 @@ +import CryptoJS from "crypto-js" +import IUserInfoVO from "domains/vos/interfaces/IUserInfoVO" +import IPostVM, { IPostVMParams } from "./interfaces/IPostVM" +import ICommentVM from "./interfaces/ICommentVM" +import CommentVM from "./CommentVM" + +export default class PostVM implements IPostVM { + readonly id: string + key: string + title: string + content: string + readonly author: IUserInfoVO + comments: ICommentVM[] + readonly createdAt: Date + updatedAt: Date + + constructor(params: IPostVMParams) { + this.id = params.id + this.title = params.title + this.content = params.content + this.author = params.author + this.comments = params.comments.map((comment) => new CommentVM(comment)) + this.createdAt = params.createdAt + this.updatedAt = params.updatedAt + this.key = this.generateKey(this.id, this.updatedAt) + } + + updateTitle(title: string): void { + this.title = title + this.updatedAt = new Date() + this.key = this.generateKey(this.id, this.updatedAt) + } + + updateContent(content: string): void { + this.content = content + this.updatedAt = new Date() + this.key = this.generateKey(this.id, this.updatedAt) + } + + applyUpdatedAt(date: Date): void { + this.updatedAt = date + this.key = this.generateKey(this.id, this.updatedAt) + } + + deleteComment(commentId: string): void { + this.comments = this.comments.filter((comment) => comment.id !== commentId) + this.updatedAt = new Date() + this.key = this.generateKey(this.id, this.updatedAt) + } + + private generateKey(id: string, updatedAt: Date): string { + const base = `${id}-${updatedAt.getTime()}` + return CryptoJS.MD5(base).toString() + } +} diff --git a/packages/client-a/src/vms/interfaces/ICommentVM.ts b/packages/client-a/src/vms/interfaces/ICommentVM.ts new file mode 100644 index 0000000..a343702 --- /dev/null +++ b/packages/client-a/src/vms/interfaces/ICommentVM.ts @@ -0,0 +1,22 @@ +import IUserInfoVO from "domains/vos/interfaces/IUserInfoVO" + +export default interface ICommentVM { + readonly id: string + key: string + readonly postId: string + readonly author: IUserInfoVO + content: string + readonly createdAt: Date + updatedAt: Date + updateContent(content: string): void + applyUpdatedAt(date: Date): void +} + +export interface ICommentVMParams { + readonly id: string + readonly postId: string + readonly author: IUserInfoVO + readonly content: string + readonly createdAt: Date + readonly updatedAt: Date +} diff --git a/packages/client-a/src/vms/interfaces/IPostVM.ts b/packages/client-a/src/vms/interfaces/IPostVM.ts new file mode 100644 index 0000000..c04488c --- /dev/null +++ b/packages/client-a/src/vms/interfaces/IPostVM.ts @@ -0,0 +1,28 @@ +import IComment from "domains/entities/interfaces/IComment" +import IUserInfoVO from "domains/vos/interfaces/IUserInfoVO" +import ICommentVM from "./ICommentVM" + +export default interface IPostVM { + readonly id: string + key: string + title: string + content: string + readonly author: IUserInfoVO + comments: ICommentVM[] + readonly createdAt: Date + updatedAt: Date + updateTitle(title: string): void + updateContent(content: string): void + deleteComment(commentId: string): void + applyUpdatedAt(date: Date): void +} + +export interface IPostVMParams { + readonly id: string + readonly title: string + readonly content: string + readonly author: IUserInfoVO + readonly comments: IComment[] + readonly createdAt: Date + readonly updatedAt: Date +} diff --git a/packages/client-a/vite.config.mjs b/packages/client-a/vite.config.mjs index e8b21cc..c09e68e 100644 --- a/packages/client-a/vite.config.mjs +++ b/packages/client-a/vite.config.mjs @@ -48,13 +48,29 @@ export default defineConfig({ userId: "1", userName: "sample" }, - createdAt: new Date(), - updatedAt: new Date() + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString() }) res.json(true) }, 200) }) + app.put("/api/posts/:postId", (req, res) => { + setTimeout(() => { + const index = posts.findIndex( + (post) => post.id === req.params.postId + ) + const updatedAt = new Date().toISOString() + posts[index] = { + ...posts[index], + title: req.body.title, + content: req.body.content, + updatedAt + } + res.json(updatedAt) + }, 200) + }) + app.get("/api/posts/:postId", (req, res) => { setTimeout(() => { res.json(posts.find((post) => post.id === req.params.postId)) @@ -89,8 +105,8 @@ export default defineConfig({ userId: "1", userName: "sample" }, - createdAt: new Date(), - updatedAt: new Date() + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString() }) res.json(true) }, 200) diff --git a/packages/client-b/src/components/molecules/SideMenu.tsx b/packages/client-b/src/components/molecules/SideMenu.tsx index 91db0cb..a18dc8c 100644 --- a/packages/client-b/src/components/molecules/SideMenu.tsx +++ b/packages/client-b/src/components/molecules/SideMenu.tsx @@ -1,10 +1,14 @@ +import Link from "next/link" + export default function SideMenu() { return ( ) diff --git a/packages/domains/src/aggregates/Post.ts b/packages/domains/src/aggregates/Post.ts index 74e9fec..9c3817a 100644 --- a/packages/domains/src/aggregates/Post.ts +++ b/packages/domains/src/aggregates/Post.ts @@ -17,7 +17,7 @@ export default class Post implements IPost { this.content = params.content this.author = params.author this.comments = params.comments - this.createdAt = params.createdAt - this.updatedAt = params.updatedAt + this.createdAt = new Date(params.createdAt) + this.updatedAt = new Date(params.updatedAt) } } diff --git a/packages/domains/src/aggregates/interfaces/IPost.ts b/packages/domains/src/aggregates/interfaces/IPost.ts index f2b9856..af2d48f 100644 --- a/packages/domains/src/aggregates/interfaces/IPost.ts +++ b/packages/domains/src/aggregates/interfaces/IPost.ts @@ -17,8 +17,8 @@ export interface IPostParams { readonly content: string readonly author: IUserInfoVO readonly comments: IComment[] - readonly createdAt: Date - readonly updatedAt: Date + readonly createdAt: string + readonly updatedAt: string } export interface IRequestPostParams { diff --git a/packages/domains/src/dtos/interfaces/ICommentDTO.ts b/packages/domains/src/dtos/interfaces/ICommentDTO.ts index 586333b..459f6e8 100644 --- a/packages/domains/src/dtos/interfaces/ICommentDTO.ts +++ b/packages/domains/src/dtos/interfaces/ICommentDTO.ts @@ -5,6 +5,6 @@ export default interface ICommentDTO { readonly postId: string content: string readonly author: IUserInfoVO - readonly createdAt: Date - updatedAt: Date + readonly createdAt: string + updatedAt: string } diff --git a/packages/domains/src/dtos/interfaces/IPostDTO.ts b/packages/domains/src/dtos/interfaces/IPostDTO.ts index 19e545e..b008d13 100644 --- a/packages/domains/src/dtos/interfaces/IPostDTO.ts +++ b/packages/domains/src/dtos/interfaces/IPostDTO.ts @@ -5,6 +5,6 @@ export default interface IPostDTO { title: string content: string readonly author: IUserInfoVO - readonly createdAt: Date - updatedAt: Date + readonly createdAt: string + updatedAt: string } diff --git a/packages/domains/src/dtos/interfaces/IUserDTO.ts b/packages/domains/src/dtos/interfaces/IUserDTO.ts index c599bf3..ba340d7 100644 --- a/packages/domains/src/dtos/interfaces/IUserDTO.ts +++ b/packages/domains/src/dtos/interfaces/IUserDTO.ts @@ -2,6 +2,6 @@ export default interface IUserDTO { readonly id: string readonly name: string readonly email: string - readonly createdAt: Date - readonly updatedAt: Date + readonly createdAt: string + readonly updatedAt: string } diff --git a/packages/domains/src/entities/Comment.ts b/packages/domains/src/entities/Comment.ts index 26cce49..0238026 100644 --- a/packages/domains/src/entities/Comment.ts +++ b/packages/domains/src/entities/Comment.ts @@ -14,7 +14,7 @@ export default class Comment implements IComment { this.postId = parmas.postId this.author = parmas.author this.content = parmas.content - this.createdAt = parmas.createdAt - this.updatedAt = parmas.updatedAt + this.createdAt = new Date(parmas.createdAt) + this.updatedAt = new Date(parmas.updatedAt) } } diff --git a/packages/domains/src/entities/User.ts b/packages/domains/src/entities/User.ts index 0fe4eb0..b523d5e 100644 --- a/packages/domains/src/entities/User.ts +++ b/packages/domains/src/entities/User.ts @@ -1,4 +1,4 @@ -import IUser from "./interfaces/IUser" +import IUser, { IUserParams } from "./interfaces/IUser" export default class User implements IUser { readonly id: string @@ -7,11 +7,11 @@ export default class User implements IUser { readonly createdAt: Date readonly updatedAt: Date - constructor(params: IUser) { + constructor(params: IUserParams) { this.id = params.id this.name = params.name this.email = params.email - this.createdAt = params.createdAt - this.updatedAt = params.updatedAt + this.createdAt = new Date(params.createdAt) + this.updatedAt = new Date(params.updatedAt) } } diff --git a/packages/domains/src/entities/interfaces/IComment.ts b/packages/domains/src/entities/interfaces/IComment.ts index b186e13..36f7f83 100644 --- a/packages/domains/src/entities/interfaces/IComment.ts +++ b/packages/domains/src/entities/interfaces/IComment.ts @@ -14,6 +14,6 @@ export interface ICommentParams { readonly postId: string readonly author: IUserInfoVO readonly content: string - readonly createdAt: Date - readonly updatedAt: Date + readonly createdAt: string + readonly updatedAt: string } diff --git a/packages/domains/src/entities/interfaces/IUser.ts b/packages/domains/src/entities/interfaces/IUser.ts index fc7ebad..49254c1 100644 --- a/packages/domains/src/entities/interfaces/IUser.ts +++ b/packages/domains/src/entities/interfaces/IUser.ts @@ -10,6 +10,6 @@ export interface IUserParams { readonly id: string readonly name: string readonly email: string - readonly createdAt: Date - readonly updatedAt: Date + readonly createdAt: string + readonly updatedAt: string } diff --git a/packages/domains/src/repositories/interfaces/ICommentRepository.ts b/packages/domains/src/repositories/interfaces/ICommentRepository.ts index efd05d9..83b50d6 100644 --- a/packages/domains/src/repositories/interfaces/ICommentRepository.ts +++ b/packages/domains/src/repositories/interfaces/ICommentRepository.ts @@ -3,6 +3,6 @@ import ICommentDTO from "domains/dtos/interfaces/ICommentDTO" export default interface ICommentRepository { getComments(postId: string): Promise createComment(postId: string, content: string): Promise - editComment(commentId: string, content: string): Promise + updateComment(commentId: string, content: string): Promise deleteComment(commentId: string): Promise } diff --git a/packages/domains/src/repositories/interfaces/IPostRepository.ts b/packages/domains/src/repositories/interfaces/IPostRepository.ts index b92f70a..ba5e447 100644 --- a/packages/domains/src/repositories/interfaces/IPostRepository.ts +++ b/packages/domains/src/repositories/interfaces/IPostRepository.ts @@ -5,6 +5,6 @@ export default interface IPostRepository { getPosts(): Promise getPost(postId: string): Promise createPost(params: IRequestPostParams): Promise - editPost(postId: string, params: IRequestPostParams): Promise + updatePost(postId: string, params: IRequestPostParams): Promise deletePost(postId: string): Promise } diff --git a/packages/domains/src/useCases/PostUseCase.ts b/packages/domains/src/useCases/PostUseCase.ts index 8f44ed4..9f033fd 100644 --- a/packages/domains/src/useCases/PostUseCase.ts +++ b/packages/domains/src/useCases/PostUseCase.ts @@ -65,8 +65,8 @@ export default class PostUseCase implements IPostUseCase { return this.postRepository.createPost(params) } - editPost(postId: string, params: IRequestPostParams): Promise { - return this.postRepository.editPost(postId, params) + updatePost(postId: string, params: IRequestPostParams): Promise { + return this.postRepository.updatePost(postId, params) } deletePost(postId: string): Promise { @@ -77,8 +77,8 @@ export default class PostUseCase implements IPostUseCase { return this.commentRepository.createComment(postId, content) } - editComment(commentId: string, content: string): Promise { - return this.commentRepository.editComment(commentId, content) + updateComment(commentId: string, content: string): Promise { + return this.commentRepository.updateComment(commentId, content) } deleteComment(commentId: string): Promise { diff --git a/packages/domains/src/useCases/interfaces/IPostUseCase.ts b/packages/domains/src/useCases/interfaces/IPostUseCase.ts index 5564a93..108dd06 100644 --- a/packages/domains/src/useCases/interfaces/IPostUseCase.ts +++ b/packages/domains/src/useCases/interfaces/IPostUseCase.ts @@ -4,9 +4,9 @@ export default interface IPostUseCase { getPosts(): Promise getPost(postId: string): Promise createPost(params: IRequestPostParams): Promise - editPost(postId: string, params: IRequestPostParams): Promise + updatePost(postId: string, params: IRequestPostParams): Promise deletePost(postId: string): Promise createComment(postId: string, content: string): Promise - editComment(commentId: string, content: string): Promise + updateComment(commentId: string, content: string): Promise deleteComment(commentId: string): Promise } diff --git a/yarn.lock b/yarn.lock index 4b2b659..b0e56c3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -678,177 +678,177 @@ __metadata: languageName: node linkType: hard -"@esbuild/aix-ppc64@npm:0.24.2": - version: 0.24.2 - resolution: "@esbuild/aix-ppc64@npm:0.24.2" +"@esbuild/aix-ppc64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/aix-ppc64@npm:0.25.0" conditions: os=aix & cpu=ppc64 languageName: node linkType: hard -"@esbuild/android-arm64@npm:0.24.2": - version: 0.24.2 - resolution: "@esbuild/android-arm64@npm:0.24.2" +"@esbuild/android-arm64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/android-arm64@npm:0.25.0" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@esbuild/android-arm@npm:0.24.2": - version: 0.24.2 - resolution: "@esbuild/android-arm@npm:0.24.2" +"@esbuild/android-arm@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/android-arm@npm:0.25.0" conditions: os=android & cpu=arm languageName: node linkType: hard -"@esbuild/android-x64@npm:0.24.2": - version: 0.24.2 - resolution: "@esbuild/android-x64@npm:0.24.2" +"@esbuild/android-x64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/android-x64@npm:0.25.0" conditions: os=android & cpu=x64 languageName: node linkType: hard -"@esbuild/darwin-arm64@npm:0.24.2": - version: 0.24.2 - resolution: "@esbuild/darwin-arm64@npm:0.24.2" +"@esbuild/darwin-arm64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/darwin-arm64@npm:0.25.0" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@esbuild/darwin-x64@npm:0.24.2": - version: 0.24.2 - resolution: "@esbuild/darwin-x64@npm:0.24.2" +"@esbuild/darwin-x64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/darwin-x64@npm:0.25.0" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@esbuild/freebsd-arm64@npm:0.24.2": - version: 0.24.2 - resolution: "@esbuild/freebsd-arm64@npm:0.24.2" +"@esbuild/freebsd-arm64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/freebsd-arm64@npm:0.25.0" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard -"@esbuild/freebsd-x64@npm:0.24.2": - version: 0.24.2 - resolution: "@esbuild/freebsd-x64@npm:0.24.2" +"@esbuild/freebsd-x64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/freebsd-x64@npm:0.25.0" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@esbuild/linux-arm64@npm:0.24.2": - version: 0.24.2 - resolution: "@esbuild/linux-arm64@npm:0.24.2" +"@esbuild/linux-arm64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/linux-arm64@npm:0.25.0" conditions: os=linux & cpu=arm64 languageName: node linkType: hard -"@esbuild/linux-arm@npm:0.24.2": - version: 0.24.2 - resolution: "@esbuild/linux-arm@npm:0.24.2" +"@esbuild/linux-arm@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/linux-arm@npm:0.25.0" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@esbuild/linux-ia32@npm:0.24.2": - version: 0.24.2 - resolution: "@esbuild/linux-ia32@npm:0.24.2" +"@esbuild/linux-ia32@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/linux-ia32@npm:0.25.0" conditions: os=linux & cpu=ia32 languageName: node linkType: hard -"@esbuild/linux-loong64@npm:0.24.2": - version: 0.24.2 - resolution: "@esbuild/linux-loong64@npm:0.24.2" +"@esbuild/linux-loong64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/linux-loong64@npm:0.25.0" conditions: os=linux & cpu=loong64 languageName: node linkType: hard -"@esbuild/linux-mips64el@npm:0.24.2": - version: 0.24.2 - resolution: "@esbuild/linux-mips64el@npm:0.24.2" +"@esbuild/linux-mips64el@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/linux-mips64el@npm:0.25.0" conditions: os=linux & cpu=mips64el languageName: node linkType: hard -"@esbuild/linux-ppc64@npm:0.24.2": - version: 0.24.2 - resolution: "@esbuild/linux-ppc64@npm:0.24.2" +"@esbuild/linux-ppc64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/linux-ppc64@npm:0.25.0" conditions: os=linux & cpu=ppc64 languageName: node linkType: hard -"@esbuild/linux-riscv64@npm:0.24.2": - version: 0.24.2 - resolution: "@esbuild/linux-riscv64@npm:0.24.2" +"@esbuild/linux-riscv64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/linux-riscv64@npm:0.25.0" conditions: os=linux & cpu=riscv64 languageName: node linkType: hard -"@esbuild/linux-s390x@npm:0.24.2": - version: 0.24.2 - resolution: "@esbuild/linux-s390x@npm:0.24.2" +"@esbuild/linux-s390x@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/linux-s390x@npm:0.25.0" conditions: os=linux & cpu=s390x languageName: node linkType: hard -"@esbuild/linux-x64@npm:0.24.2": - version: 0.24.2 - resolution: "@esbuild/linux-x64@npm:0.24.2" +"@esbuild/linux-x64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/linux-x64@npm:0.25.0" conditions: os=linux & cpu=x64 languageName: node linkType: hard -"@esbuild/netbsd-arm64@npm:0.24.2": - version: 0.24.2 - resolution: "@esbuild/netbsd-arm64@npm:0.24.2" +"@esbuild/netbsd-arm64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/netbsd-arm64@npm:0.25.0" conditions: os=netbsd & cpu=arm64 languageName: node linkType: hard -"@esbuild/netbsd-x64@npm:0.24.2": - version: 0.24.2 - resolution: "@esbuild/netbsd-x64@npm:0.24.2" +"@esbuild/netbsd-x64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/netbsd-x64@npm:0.25.0" conditions: os=netbsd & cpu=x64 languageName: node linkType: hard -"@esbuild/openbsd-arm64@npm:0.24.2": - version: 0.24.2 - resolution: "@esbuild/openbsd-arm64@npm:0.24.2" +"@esbuild/openbsd-arm64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/openbsd-arm64@npm:0.25.0" conditions: os=openbsd & cpu=arm64 languageName: node linkType: hard -"@esbuild/openbsd-x64@npm:0.24.2": - version: 0.24.2 - resolution: "@esbuild/openbsd-x64@npm:0.24.2" +"@esbuild/openbsd-x64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/openbsd-x64@npm:0.25.0" conditions: os=openbsd & cpu=x64 languageName: node linkType: hard -"@esbuild/sunos-x64@npm:0.24.2": - version: 0.24.2 - resolution: "@esbuild/sunos-x64@npm:0.24.2" +"@esbuild/sunos-x64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/sunos-x64@npm:0.25.0" conditions: os=sunos & cpu=x64 languageName: node linkType: hard -"@esbuild/win32-arm64@npm:0.24.2": - version: 0.24.2 - resolution: "@esbuild/win32-arm64@npm:0.24.2" +"@esbuild/win32-arm64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/win32-arm64@npm:0.25.0" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@esbuild/win32-ia32@npm:0.24.2": - version: 0.24.2 - resolution: "@esbuild/win32-ia32@npm:0.24.2" +"@esbuild/win32-ia32@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/win32-ia32@npm:0.25.0" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@esbuild/win32-x64@npm:0.24.2": - version: 0.24.2 - resolution: "@esbuild/win32-x64@npm:0.24.2" +"@esbuild/win32-x64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/win32-x64@npm:0.25.0" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -1844,6 +1844,13 @@ __metadata: languageName: node linkType: hard +"@types/crypto-js@npm:^4": + version: 4.2.2 + resolution: "@types/crypto-js@npm:4.2.2" + checksum: 10c0/760a2078f36f2a3a1089ef367b0d13229876adcf4bcd6e8824d00d9e9bfad8118dc7e6a3cc66322b083535e12be3a29044ccdc9603bfb12519ff61551a3322c6 + languageName: node + linkType: hard + "@types/estree@npm:1.0.6": version: 1.0.6 resolution: "@types/estree@npm:1.0.6" @@ -2271,7 +2278,9 @@ __metadata: version: 0.0.0-use.local resolution: "adapters@workspace:packages/adapters" dependencies: + "@types/crypto-js": "npm:^4" axios: "npm:^1.7.7" + crypto-js: "npm:^4.2.0" domains: "workspace:*" languageName: unknown linkType: soft @@ -3427,6 +3436,13 @@ __metadata: languageName: node linkType: hard +"crypto-js@npm:^4.2.0": + version: 4.2.0 + resolution: "crypto-js@npm:4.2.0" + checksum: 10c0/8fbdf9d56f47aea0794ab87b0eb9833baf80b01a7c5c1b0edc7faf25f662fb69ab18dc2199e2afcac54670ff0cd9607a9045a3f7a80336cccd18d77a55b9fdf0 + languageName: node + linkType: hard + "css.escape@npm:^1.5.1": version: 1.5.1 resolution: "css.escape@npm:1.5.1" @@ -4081,35 +4097,35 @@ __metadata: languageName: node linkType: hard -"esbuild@npm:^0.24.2": - version: 0.24.2 - resolution: "esbuild@npm:0.24.2" - dependencies: - "@esbuild/aix-ppc64": "npm:0.24.2" - "@esbuild/android-arm": "npm:0.24.2" - "@esbuild/android-arm64": "npm:0.24.2" - "@esbuild/android-x64": "npm:0.24.2" - "@esbuild/darwin-arm64": "npm:0.24.2" - "@esbuild/darwin-x64": "npm:0.24.2" - "@esbuild/freebsd-arm64": "npm:0.24.2" - "@esbuild/freebsd-x64": "npm:0.24.2" - "@esbuild/linux-arm": "npm:0.24.2" - "@esbuild/linux-arm64": "npm:0.24.2" - "@esbuild/linux-ia32": "npm:0.24.2" - "@esbuild/linux-loong64": "npm:0.24.2" - "@esbuild/linux-mips64el": "npm:0.24.2" - "@esbuild/linux-ppc64": "npm:0.24.2" - "@esbuild/linux-riscv64": "npm:0.24.2" - "@esbuild/linux-s390x": "npm:0.24.2" - "@esbuild/linux-x64": "npm:0.24.2" - "@esbuild/netbsd-arm64": "npm:0.24.2" - "@esbuild/netbsd-x64": "npm:0.24.2" - "@esbuild/openbsd-arm64": "npm:0.24.2" - "@esbuild/openbsd-x64": "npm:0.24.2" - "@esbuild/sunos-x64": "npm:0.24.2" - "@esbuild/win32-arm64": "npm:0.24.2" - "@esbuild/win32-ia32": "npm:0.24.2" - "@esbuild/win32-x64": "npm:0.24.2" +"esbuild@npm:0.25.0": + version: 0.25.0 + resolution: "esbuild@npm:0.25.0" + dependencies: + "@esbuild/aix-ppc64": "npm:0.25.0" + "@esbuild/android-arm": "npm:0.25.0" + "@esbuild/android-arm64": "npm:0.25.0" + "@esbuild/android-x64": "npm:0.25.0" + "@esbuild/darwin-arm64": "npm:0.25.0" + "@esbuild/darwin-x64": "npm:0.25.0" + "@esbuild/freebsd-arm64": "npm:0.25.0" + "@esbuild/freebsd-x64": "npm:0.25.0" + "@esbuild/linux-arm": "npm:0.25.0" + "@esbuild/linux-arm64": "npm:0.25.0" + "@esbuild/linux-ia32": "npm:0.25.0" + "@esbuild/linux-loong64": "npm:0.25.0" + "@esbuild/linux-mips64el": "npm:0.25.0" + "@esbuild/linux-ppc64": "npm:0.25.0" + "@esbuild/linux-riscv64": "npm:0.25.0" + "@esbuild/linux-s390x": "npm:0.25.0" + "@esbuild/linux-x64": "npm:0.25.0" + "@esbuild/netbsd-arm64": "npm:0.25.0" + "@esbuild/netbsd-x64": "npm:0.25.0" + "@esbuild/openbsd-arm64": "npm:0.25.0" + "@esbuild/openbsd-x64": "npm:0.25.0" + "@esbuild/sunos-x64": "npm:0.25.0" + "@esbuild/win32-arm64": "npm:0.25.0" + "@esbuild/win32-ia32": "npm:0.25.0" + "@esbuild/win32-x64": "npm:0.25.0" dependenciesMeta: "@esbuild/aix-ppc64": optional: true @@ -4163,7 +4179,7 @@ __metadata: optional: true bin: esbuild: bin/esbuild - checksum: 10c0/5a25bb08b6ba23db6e66851828d848bd3ff87c005a48c02d83e38879058929878a6baa5a414e1141faee0d1dece3f32b5fbc2a87b82ed6a7aa857cf40359aeb5 + checksum: 10c0/5767b72da46da3cfec51661647ec850ddbf8a8d0662771139f10ef0692a8831396a0004b2be7966cecdb08264fb16bdc16290dcecd92396fac5f12d722fa013d languageName: node linkType: hard