Repository navigation
feat(new-webui): MongoCDC Framework #809
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
14c1b46
a16c7db
56b1bf9
06f4c6c
c3b1bdc
c91af4b
ea93bb8
2aa2512
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -7,7 +7,7 @@ | |||||||||||||||||||||||||||||
| "build": "tsc -b && vite build", | ||||||||||||||||||||||||||||||
| "lint:check": "eslint", | ||||||||||||||||||||||||||||||
| "lint:fix": "eslint --fix", | ||||||||||||||||||||||||||||||
| "start": "vite", | ||||||||||||||||||||||||||||||
| "start": "vite --host 10.1.0.21", | ||||||||||||||||||||||||||||||
| "antd": "VITE_USE_ANTD_APP=true npm run start" | ||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||
| "author": "YScope Inc. <dev@yscope.com>", | ||||||||||||||||||||||||||||||
|
|
@@ -21,13 +21,20 @@ | |||||||||||||||||||||||||||||
| "antd": "^5.24.5", | ||||||||||||||||||||||||||||||
| "axios": "^1.7.9", | ||||||||||||||||||||||||||||||
| "react": "^19.0.0", | ||||||||||||||||||||||||||||||
| "react-dom": "^19.0.0" | ||||||||||||||||||||||||||||||
| "react-dom": "^19.0.0", | ||||||||||||||||||||||||||||||
| "socket.io-client": "^4.8.1" | ||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||
| "devDependencies": { | ||||||||||||||||||||||||||||||
| "@eslint/js": "^9.19.0", | ||||||||||||||||||||||||||||||
| "@types/react": "^19.0.10", | ||||||||||||||||||||||||||||||
| "@types/react-dom": "^19.0.4", | ||||||||||||||||||||||||||||||
| "@vitejs/plugin-react": "^4.3.4", | ||||||||||||||||||||||||||||||
| "eslint": "^9.19.0", | ||||||||||||||||||||||||||||||
| "eslint-config-yscope": "latest", | ||||||||||||||||||||||||||||||
| "eslint-plugin-react": "^7.37.4", | ||||||||||||||||||||||||||||||
| "eslint-plugin-react-hooks": "^5.0.0", | ||||||||||||||||||||||||||||||
| "eslint-plugin-react-refresh": "^0.4.18", | ||||||||||||||||||||||||||||||
| "globals": "^15.14.0", | ||||||||||||||||||||||||||||||
|
Comment on lines
+28
to
+38
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't have to explicitly state those eslint dependencies because
Suggested change
|
||||||||||||||||||||||||||||||
| "typescript": "~5.6.2", | ||||||||||||||||||||||||||||||
| "vite": "^6.2.5" | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. NOTE: changes in this file is to be removed. |
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,41 @@ | ||||||||||||
| import { | ||||||||||||
| io, | ||||||||||||
| Socket, | ||||||||||||
| } from "socket.io-client"; | ||||||||||||
|
|
||||||||||||
| import MongoReplicaCollectionReactiveCursor from "./MongoReplicaCollectionReactiveCursor.js"; | ||||||||||||
|
|
||||||||||||
|
|
||||||||||||
| class MongoReplicaCollection { | ||||||||||||
| private socket: Socket; | ||||||||||||
|
|
||||||||||||
| constructor (collectionName: string) { | ||||||||||||
| // TODO: use the server URL from the environment / constructor args | ||||||||||||
| this.socket = io(); | ||||||||||||
|
|
||||||||||||
| this.socket.emit("collection::init", { | ||||||||||||
| collectionName: collectionName, | ||||||||||||
| }); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| // TODO: add support for non-reactive cursors | ||||||||||||
| find (query: object, options: object) { | ||||||||||||
| return new MongoReplicaCollectionReactiveCursor({ | ||||||||||||
| options: options, | ||||||||||||
| query: query, | ||||||||||||
| socket: this.socket, | ||||||||||||
| }); | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| // const main = async () => { | ||||||||||||
| // const collection = new MongoReplicaCollection("3"); | ||||||||||||
| // const cursor = collection.find({}, {}); | ||||||||||||
|
|
||||||||||||
| // console.log(await cursor.toArray()); | ||||||||||||
| // }; | ||||||||||||
|
|
||||||||||||
| // | ||||||||||||
| // await main(); | ||||||||||||
|
|
||||||||||||
| export default MongoReplicaCollection; | ||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we tend to reserve two empty lines before exports. Sorry about that the linter has not been smart enough to autofix this.
Suggested change
|
||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| import { Socket } from "socket.io-client"; | ||
|
|
||
| interface CursorConstructorArgs { | ||
| socket: Socket; | ||
| query: object; | ||
| options: object; | ||
| } | ||
|
|
||
| class MongoReplicaCollectionCursor { | ||
| socket: Socket; | ||
|
|
||
| findQuery: object; | ||
|
|
||
| findOptions: object; | ||
|
|
||
| constructor ({socket, query, options}: CursorConstructorArgs) { | ||
| this.socket = socket; | ||
|
|
||
| this.findQuery = query; | ||
| this.findOptions = options; | ||
| } | ||
|
|
||
| sort (sort: object): this { | ||
| this.findOptions = { | ||
| ...this.findOptions, | ||
| sort: sort, | ||
| }; | ||
|
|
||
| return this; | ||
| } | ||
|
|
||
| limit (number: number): this { | ||
| this.findOptions = { | ||
| ...this.findOptions, | ||
| limit: number, | ||
| }; | ||
|
|
||
| return this; | ||
| } | ||
|
|
||
| skip (offset:number): this { | ||
| this.findOptions = { | ||
| ...this.findOptions, | ||
| skip: offset, | ||
| }; | ||
|
|
||
| return this; | ||
| } | ||
|
|
||
| toArray () { | ||
| return new Promise((resolve, reject) => { | ||
| this.socket.emit("collection::find::toArray", { | ||
| query: this.findQuery, | ||
| options: this.findOptions, | ||
| }, (response: { error?: any; data?: any }) => { | ||
| if (response.error) { | ||
| return reject(response.error); | ||
| } | ||
|
|
||
| return resolve(response.data); | ||
| }); | ||
| }); | ||
| } | ||
| } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. NOTE: not in use at the moment. |
||
|
|
||
|
|
||
| export default MongoReplicaCollectionCursor; | ||
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,66 @@ | ||||||||||||
| import MongoReplicaCollectionCursor from "./MongoReplicaCollectionCursor.js"; | ||||||||||||
| import { Socket } from "socket.io-client"; | ||||||||||||
|
|
||||||||||||
| interface Listener { | ||||||||||||
| onData: (data: any) => void; | ||||||||||||
| onError: (error: any) => void; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| interface ReactiveArrayCallback { | ||||||||||||
| onData: (data: any) => void; | ||||||||||||
| onError: (error: any) => void; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| class MongoReplicaCollectionReactiveCursor extends MongoReplicaCollectionCursor { | ||||||||||||
| /** | ||||||||||||
| * @type {Listener} | ||||||||||||
| */ | ||||||||||||
| private listener: Listener | null = null; | ||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For private properties / functions, can we use the |
||||||||||||
|
|
||||||||||||
| constructor (props: {socket: Socket; query: object; options: object}) { | ||||||||||||
| super(props); | ||||||||||||
|
|
||||||||||||
| this.socket.on("collection::find::update", (response: {error?: any; data?: any}) => { | ||||||||||||
| if (response.error) { | ||||||||||||
| return this.listener?.onError(response.error); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| return this.listener?.onData(response.data); | ||||||||||||
| }); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| /** | ||||||||||||
| * Subscribe to the collection | ||||||||||||
| * | ||||||||||||
| * @param {{onData: Function, onError: Function}} callback | ||||||||||||
| * @return {function(): void} The cleanup function. | ||||||||||||
| */ | ||||||||||||
| toReactiveArray (callback: ReactiveArrayCallback): () => void { | ||||||||||||
| console.log("toReactiveArrya"); | ||||||||||||
| let queryHash: string | undefined; | ||||||||||||
| this.socket.emit("collection::find::toReactiveArray", { | ||||||||||||
| query: this.findQuery, | ||||||||||||
| options: this.findOptions, | ||||||||||||
| }, (response: {error?: any; queryHash?: string}) => { | ||||||||||||
| if (response.error) { | ||||||||||||
| return callback.onError(response.error); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| ({queryHash} = response); | ||||||||||||
|
|
||||||||||||
| this.listener = callback; | ||||||||||||
| }); | ||||||||||||
|
|
||||||||||||
| return () => { | ||||||||||||
| // TODO: the server should also rely on some heartbeat from the client | ||||||||||||
| // for deciding when to free the resources. | ||||||||||||
| this.socket.emit("collection::find::unsubscribe", { | ||||||||||||
| queryHash: queryHash, | ||||||||||||
| }); | ||||||||||||
|
|
||||||||||||
| this.socket.off("collection::find::update"); | ||||||||||||
| }; | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| export default MongoReplicaCollectionReactiveCursor; | ||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,40 @@ | ||||||||||
| import { | ||||||||||
| useEffect, | ||||||||||
| useState, | ||||||||||
| } from "react"; | ||||||||||
|
|
||||||||||
| /** | ||||||||||
| * Custom hook to track a MongoDB cursor and return its data as an array. | ||||||||||
| * | ||||||||||
| * @param {Function} getCursor Function that returns a MongoReplicaCollectionReactiveCursor. | ||||||||||
| * @param {object[]} dependencies Array of dependencies for the effect. | ||||||||||
| * @return {object[]} The data from the cursor as an array. | ||||||||||
| */ | ||||||||||
| const useTracker = ( | ||||||||||
| getCursor: Function, | ||||||||||
| dependencies: object[] = [] | ||||||||||
| ): any[] => { | ||||||||||
| const [data, setData] = useState<any[]>([]); | ||||||||||
|
|
||||||||||
| useEffect(() => { | ||||||||||
| const cursor = getCursor(); | ||||||||||
|
|
||||||||||
| const handleData = (newData: any[]) => setData(newData); | ||||||||||
|
|
||||||||||
| const unsubscribe = cursor.toReactiveArray({ | ||||||||||
| onData: handleData, | ||||||||||
| onError: (error: any) => { | ||||||||||
| console.error("Error fetching data:", error); | ||||||||||
| }, | ||||||||||
| }); | ||||||||||
|
|
||||||||||
| return () => { | ||||||||||
| unsubscribe(); | ||||||||||
| }; | ||||||||||
| // eslint-disable-next-line react-hooks/exhaustive-deps | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
| }, dependencies); | ||||||||||
|
|
||||||||||
| return data; | ||||||||||
| }; | ||||||||||
|
|
||||||||||
| export {useTracker}; | ||||||||||
|
Comment on lines
+44
to
+45
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's do default export here:
Suggested change
We also need to update the import in the sample App.tsx: |
||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,6 +14,11 @@ export default defineConfig({ | |
| target: "http://localhost:3000/", | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since the comment in L12-13 applies to both routes:
@coderabbitai Let's create an issue to track the effort of serving Fastify routes under "/api". And we should replace this "/query/" proxy entry with "/api/" to ensure the other RESTful APIs are proxied to the backend.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain@junhaoliao I'll create an issue to track this effort. Let me take care of that for you. 🏁 Script executed: Length of output: 718 @junhaoliao I've created an issue to track the effort of serving Fastify routes under "/api/" and replacing the "/query/" proxy entry. You can find it here: #810 The issue includes the objectives we discussed about updating the Vite configuration to ensure all RESTful APIs are properly proxied to the backend. |
||
| changeOrigin: true, | ||
| }, | ||
| "/socket.io/": { | ||
| target: "ws://localhost:3000/", | ||
| changeOrigin: true, | ||
| ws: true, | ||
| }, | ||
| }, | ||
| }, | ||
| publicDir: "public", | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.