├── .github ├── dependabot.yml └── workflows │ └── ship.yml ├── .gitignore ├── .yarnclean ├── README.md ├── changelog.md ├── config └── webpack.renderer.config.js ├── electron-builder.js ├── electron-webpack.json5 ├── package.json ├── ship.sh ├── src ├── main │ └── index.ts └── renderer │ ├── App.tsx │ ├── components │ ├── DatabaseDetail.tsx │ └── RemoteTable.tsx │ ├── custom.d.ts │ ├── databases │ ├── realm │ │ ├── Person.ts │ │ ├── db.ts │ │ ├── helpers.ts │ │ ├── sample.ts │ │ └── schema.ts │ ├── rxdb │ │ ├── db.ts │ │ └── index.ts │ └── sqlite │ │ ├── db.ts │ │ ├── models │ │ └── Person.ts │ │ └── service.ts │ ├── index.css │ ├── index.tsx │ ├── logo.svg │ ├── pages │ ├── Home.tsx │ ├── RealMDashboard.tsx │ ├── RxDbDashboard.tsx │ └── SQLiteDashboard.tsx │ ├── types.ts │ └── utils │ ├── helpers.tsx │ └── index.ts ├── tsconfig.json └── yarn.lock /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "npm" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | ignore: 8 | # Ignore auto updates for electron, which may affect prebuild availability for realm 9 | - dependency-name: "@types/node" 10 | # For @types/node, ignore all updates for version non LTS version 11 | versions: ["13.x"] 12 | 13 | reviewers: 14 | - 'vazra' 15 | labels: 16 | - 'dependencies' 17 | open-pull-requests-limit: 5 18 | -------------------------------------------------------------------------------- /.github/workflows/ship.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: ship 4 | 5 | on: 6 | push: 7 | branches: [release] 8 | 9 | jobs: 10 | release: 11 | runs-on: windows-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: actions/setup-node@v2-beta 15 | with: 16 | node-version: "12" 17 | 18 | # - name: Build & Ship 19 | # env: 20 | # AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 21 | # AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 22 | # run: | 23 | # yarn install 24 | # yarn ship 25 | # echo "Done!" 26 | - name: Build/release Electron app 27 | uses: samuelmeuli/action-electron-builder@v1 28 | env: 29 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 30 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 31 | with: 32 | github_token: ${{ secrets.github_token }} 33 | release: ${{ startsWith(github.ref, 'refs/tags/v') }} 34 | args: "--ia32 --x64 --win --publish=always" 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | dist/ 3 | node_modules/ 4 | thumbs.db 5 | .idea/ 6 | .env 7 | 8 | # DB files 9 | *-rxdb-*/**/* 10 | rxdb-adapter-check 11 | .db/**/* -------------------------------------------------------------------------------- /.yarnclean: -------------------------------------------------------------------------------- 1 | # test directories 2 | __tests__ 3 | node_modules/*/test 4 | node_modules/*/tests 5 | powered-test 6 | 7 | # asset directories 8 | docs 9 | doc 10 | website 11 | images 12 | 13 | # examples 14 | example 15 | examples 16 | 17 | # code coverage directories 18 | coverage 19 | .nyc_output 20 | 21 | # build scripts 22 | Makefile 23 | Gulpfile.js 24 | Gruntfile.js 25 | 26 | # configs 27 | .tern-project 28 | .gitattributes 29 | .editorconfig 30 | .*ignore 31 | .eslintrc 32 | .jshintrc 33 | .flowconfig 34 | .documentup.json 35 | .yarn-metadata.json 36 | 37 | # misc 38 | *.gz 39 | *.md 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Electron React (Typescript) with RxDB, Realm & SQLite 2 | 3 | > Demo of Native Databases with Electron and ReactJS. Realm, SQLite and RxDB ( with LevelDB/IndexedDB/InMemory adapters) 4 | 5 | - The electron & react part is bootstraped with [`electron-webpack-typescript-react boilerplate`](https://github.com/vazra/electron-webpack-typescript-react) which is based in `electron-webpack`. 6 | 7 | - Use of [`webpack-dev-server`](https://github.com/webpack/webpack-dev-server) for development 8 | - HMR for both `renderer` and `main` processes 9 | - Use of [`babel-preset-env`](https://github.com/babel/babel-preset-env) that is automatically configured based on your `electron` version 10 | - Use of [`electron-builder`](https://github.com/electron-userland/electron-builder) to package and build a distributable electron application 11 | 12 | Make sure to check out [`electron-webpack`'s documentation](https://webpack.electron.build/) for more details. 13 | 14 | - Implemented RxDB with native adapters of LevelDB & NodeSQL 15 | - Implemented native [Realm database](https://github.com/realm/realm-js) 16 | - Implemented native [SQLite3 database](https://github.com/mapbox/node-sqlite3) 17 | 18 | ## Getting Started 19 | 20 | Simply fork/clone this repository, install dependencies, and try `yarn dev`. 21 | 22 | ```bash 23 | # clone thee repo 24 | mkdir electron-react-dbs && cd electron-react-dbs 25 | git clone https://github.com/vazra/electron-react-ts-rxdb-realm-sqlite.git 26 | cd electron-react-dbs 27 | 28 | # install dependencies 29 | yarn 30 | 31 | # run in dev mode 32 | yarn dev 33 | 34 | ``` 35 | 36 | You will be able to tryout all the databases available. 37 | 38 | The use of the [yarn](https://yarnpkg.com/) package manager is **strongly** recommended, as opposed to using `npm`. 39 | 40 | ## FAQ 41 | 42 | 1. Can I use this as a boilerplate for my electron-react app with native databases 43 | 44 | Ans. Yes, you can. this project itself is bootstrapped with [`electron-react boilerplate`](https://github.com/vazra/electron-webpack-typescript-react) You can either take it as the base project or fork this repo and remove unwanted db codes. The code is structured in such a way that any db code can be removed without much effort. 45 | 46 | For any bugs or requests create issues [here](https://github.com/vazra/electron-react-ts-rxdb-realm-sqlite/issues) 47 | 48 | Pull requests are also invited. :rocket: 49 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | - added react 2 | 3 | - added react files from CRA 4 | - added hot reloading 5 | 6 | - migrated typescript 7 | 8 | - fixed @types/node versionm to ^12 to fix type warnings Ref. [issue](https://github.com/electron/electron/issues/21612) 9 | - skip type checking in renderer index.ts as a demo page with vue is added without types. 10 | 11 | - cloned from [electron-webpack-quick-start](https://github.com/electron-userland/electron-webpack-quick-start) 12 | -------------------------------------------------------------------------------- /config/webpack.renderer.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | module: { 3 | rules: [], 4 | }, 5 | resolve: { 6 | alias: { 7 | // this is to fix react warning while using hot-loader 8 | "react-dom": "@hot-loader/react-dom", 9 | }, 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /electron-builder.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | productName: "ReactDesktopApp", 3 | appId: "com.test.reactdeesk", 4 | copyright: "Copyright © 2020 Vazra.", 5 | 6 | // asar: true, 7 | // npmRebuild: true, 8 | // buildDependenciesFromSource: false, 9 | 10 | // Windows configuration 11 | win: { 12 | target: "nsis-web", 13 | }, 14 | 15 | // Release repo 16 | publish: [ 17 | { 18 | provider: "github", 19 | repo: "electron-react-ts-rxdb", 20 | owner: "vazra", 21 | releaseType: "release", 22 | }, 23 | { 24 | provider: "s3", 25 | bucket: "shdesk", 26 | }, 27 | ], 28 | }; 29 | -------------------------------------------------------------------------------- /electron-webpack.json5: -------------------------------------------------------------------------------- 1 | { 2 | whiteListedModules: ["react-bootstrap", "rxdb"], 3 | renderer: { 4 | webpackConfig: "config/webpack.renderer.config.js", 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reactdeskapp", 3 | "version": "0.1.5", 4 | "license": "MIT", 5 | "scripts": { 6 | "dev": "electron-webpack dev", 7 | "compile": "electron-webpack", 8 | "build": "electron-webpack", 9 | "dist": "yarn compile && electron-builder", 10 | "ship": "yarn dist --win --ia32 --publish=always", 11 | "rebuildrealm": "electron-rebuild -f -w realm --arch ia32", 12 | "postinstall": "electron-builder install-app-deps", 13 | "dist:dir": "yarn dist --dir -c.compression=store -c.mac.identity=null", 14 | "pre:r": "prebuild-install -t 8.3.2 -r electron --verbose" 15 | }, 16 | "dependencies": { 17 | "@hot-loader/react-dom": "17.0.2", 18 | "bootstrap": "4.6.0", 19 | "electron-devtools-installer": "^3.2.0", 20 | "faker": "6.6.6", 21 | "pouchdb-adapter-idb": "7.3.0", 22 | "pouchdb-adapter-leveldb": "^7.3.0", 23 | "pouchdb-adapter-memory": "7.3.0", 24 | "pouchdb-adapter-node-websql": "^7.0.0", 25 | "pouchdb-adapter-websql": "7.0.0", 26 | "react": "17.0.2", 27 | "react-bootstrap": "2.3.0", 28 | "react-bootstrap-table-next": "4.0.3", 29 | "react-bootstrap-table2-paginator": "2.1.2", 30 | "react-dom": "16.14.0", 31 | "react-hot-loader": "^4.13.0", 32 | "realm": "^10.16.0", 33 | "rxdb": "11.6.0", 34 | "rxjs": "7.5.5", 35 | "source-map-support": "^0.5.21", 36 | "sql.js": "^1.6.2", 37 | "sqlite3": "^5.0.4", 38 | "trilogy": "^2.0.5" 39 | }, 40 | "devDependencies": { 41 | "@babel/preset-react": "^7.16.7", 42 | "@types/electron-devtools-installer": "^2.2.2", 43 | "@types/faker": "6.6.9", 44 | "@types/generic-pool": "^3.1.10", 45 | "@types/node": "^17.0.25", 46 | "@types/react": "^18.0.6", 47 | "@types/react-bootstrap-table-next": "4.0.18", 48 | "@types/react-bootstrap-table2-paginator": "2.1.2", 49 | "@types/react-dom": "^18.0.2", 50 | "electron": "18.1.0", 51 | "electron-builder": "^23.0.3", 52 | "electron-webpack": "^2.8.2", 53 | "electron-webpack-ts": "^4.0.1", 54 | "typescript": "^4.6.3", 55 | "webpack": "~5.72.0" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /ship.sh: -------------------------------------------------------------------------------- 1 | # get the current branch 2 | BRANCH=$(git rev-parse --abbrev-ref HEAD) 3 | 4 | # bump version 5 | yarn version --patch 6 | 7 | # checkout release branch 8 | git checkout release 9 | 10 | # rebase to current branch 11 | git rebase $BRANCH 12 | 13 | # push 14 | git push && git push --tags 15 | 16 | git checkout $BRANCH 17 | 18 | # push master 19 | git push && git push --tags 20 | -------------------------------------------------------------------------------- /src/main/index.ts: -------------------------------------------------------------------------------- 1 | import { app, BrowserWindow, screen } from "electron"; 2 | import * as path from "path"; 3 | import { format as formatUrl } from "url"; 4 | import installExtension, { 5 | REDUX_DEVTOOLS, 6 | REACT_DEVELOPER_TOOLS, 7 | REACT_PERF, 8 | } from "electron-devtools-installer"; 9 | 10 | const isDevelopment = process.env.NODE_ENV !== "production"; 11 | 12 | // global reference to mainWindow (necessary to prevent window from being garbage collected) 13 | let mainWindow: BrowserWindow | null; 14 | 15 | function createMainWindow() { 16 | const { width, height } = screen.getPrimaryDisplay().workAreaSize; 17 | const window = new BrowserWindow({ 18 | width, 19 | height, 20 | webPreferences: { nodeIntegration: true }, 21 | }); 22 | 23 | if (isDevelopment) { 24 | installExtension([REDUX_DEVTOOLS, REACT_DEVELOPER_TOOLS, REACT_PERF]) 25 | .then((name) => console.log(`Added Extension: ${name}`)) 26 | .catch((err) => console.log("An error occurred: ", err)); 27 | 28 | window.webContents.openDevTools(); 29 | } 30 | 31 | if (isDevelopment) { 32 | window.loadURL(`http://localhost:${process.env.ELECTRON_WEBPACK_WDS_PORT}`); 33 | } else { 34 | window.loadURL( 35 | formatUrl({ 36 | pathname: path.join(__dirname, "index.html"), 37 | protocol: "file", 38 | slashes: true, 39 | }) 40 | ); 41 | } 42 | 43 | window.on("closed", () => { 44 | mainWindow = null; 45 | }); 46 | 47 | window.webContents.on("devtools-opened", () => { 48 | window.focus(); 49 | setImmediate(() => { 50 | window.focus(); 51 | }); 52 | }); 53 | 54 | return window; 55 | } 56 | 57 | // quit application when all windows are closed 58 | app.on("window-all-closed", () => { 59 | // on macOS it is common for applications to stay open until the user explicitly quits 60 | if (process.platform !== "darwin") { 61 | app.quit(); 62 | } 63 | }); 64 | 65 | app.on("activate", () => { 66 | // on macOS it is common to re-create a window even after all windows have been closed 67 | if (mainWindow === null) { 68 | mainWindow = createMainWindow(); 69 | } 70 | }); 71 | 72 | // create main BrowserWindow when electron is ready 73 | app.on("ready", () => { 74 | mainWindow = createMainWindow(); 75 | }); 76 | 77 | if (module.hot) { 78 | module.hot.accept(); 79 | } 80 | -------------------------------------------------------------------------------- /src/renderer/App.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { hot } from "react-hot-loader/root"; 3 | import Home from "./pages/Home"; 4 | 5 | function App() { 6 | return ; 7 | } 8 | 9 | export default hot(App); 10 | -------------------------------------------------------------------------------- /src/renderer/components/DatabaseDetail.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export type IDatabaseMode = "RxDB" | "SQLite" | "Realm"; 4 | 5 | interface IDatabaseDetail { 6 | dbType: IDatabaseMode; 7 | } 8 | 9 | export function DatabaseDetail({ dbType }: IDatabaseDetail) { 10 | return
; 11 | } 12 | 13 | export default DatabaseDetail; 14 | -------------------------------------------------------------------------------- /src/renderer/components/RemoteTable.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import paginationFactory, { 3 | PaginationProvider, 4 | PaginationListStandalone, 5 | PaginationTotalStandalone, 6 | SizePerPageDropdownStandalone, 7 | } from "react-bootstrap-table2-paginator"; 8 | import BootstrapTable, { 9 | TableChangeType, 10 | TableChangeState, 11 | } from "react-bootstrap-table-next"; 12 | import { Row, Col } from "react-bootstrap"; 13 | 14 | interface IRemoteTable { 15 | data: any[]; 16 | page: number; 17 | sizePerPage: number; 18 | onTableChange: ( 19 | type: TableChangeType, 20 | newState: TableChangeState 21 | ) => void; 22 | totalSize: number; 23 | } 24 | 25 | export function RemoteTable({ 26 | data, 27 | page, 28 | sizePerPage, 29 | onTableChange, 30 | totalSize, 31 | }: IRemoteTable) { 32 | const columns = [ 33 | { 34 | dataField: "name", 35 | text: "Name", 36 | }, 37 | { 38 | dataField: "phone", 39 | text: "Phone No", 40 | }, 41 | { 42 | dataField: "address", 43 | text: "Address", 44 | }, 45 | { 46 | dataField: "area", 47 | text: "Area", 48 | }, 49 | ]; 50 | 51 | return ( 52 |
53 | 61 | {({ paginationProps, paginationTableProps }) => ( 62 |
63 |
64 | 65 | 66 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 |
86 | 87 |
88 | 89 |
90 |
91 | )} 92 |
93 |
94 | ); 95 | } 96 | 97 | export default RemoteTable; 98 | -------------------------------------------------------------------------------- /src/renderer/custom.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.svg" { 2 | const content: string; 3 | export default content; 4 | } 5 | -------------------------------------------------------------------------------- /src/renderer/databases/realm/Person.ts: -------------------------------------------------------------------------------- 1 | import getDB from "./db"; 2 | import { UserDocType } from "../../types"; 3 | import { createAUser } from "../../utils"; 4 | export const COLL_PERSON = "Person"; 5 | 6 | export class Person { 7 | static schema = { 8 | name: COLL_PERSON, 9 | primaryKey: "id", 10 | 11 | properties: { 12 | id: { type: "string", indexed: true }, 13 | name: "string", 14 | phone: "string", 15 | address: "string", 16 | area: "string", 17 | }, 18 | }; 19 | 20 | static getPersons(perPageCount?: number, pageNo?: number) { 21 | if (perPageCount && pageNo) { 22 | return getDB() 23 | .objects(COLL_PERSON) 24 | .slice((pageNo - 1) * perPageCount, perPageCount); 25 | } else { 26 | return getDB().objects(COLL_PERSON); 27 | } 28 | } 29 | 30 | static addPerson(doc: UserDocType) { 31 | const db = getDB(); 32 | const newObj = { ...doc, id: Math.random() }; 33 | db.write(() => db.create(COLL_PERSON, newObj)); 34 | } 35 | 36 | static bulkAddPersons(docs: UserDocType[], firstid?: number) { 37 | const db = getDB(); 38 | 39 | let docId = 40 | firstid === undefined ? Math.floor(Math.random() * 10000000) : firstid; 41 | db.write(() => { 42 | for (let aDoc of docs) { 43 | const newObj = { ...aDoc, id: docId.toString() }; 44 | db.create(COLL_PERSON, newObj); 45 | docId = docId + 1; 46 | } 47 | }); 48 | } 49 | 50 | static AddDummyPersons(count: number, firstid?: number) { 51 | const db = getDB(); 52 | 53 | let docId = 54 | firstid === undefined ? Math.floor(Math.random() * 10000000) : firstid; 55 | db.write(() => { 56 | for (let i = 0; i < count; i++) { 57 | const aDoc = createAUser(); 58 | const newObj = { ...aDoc, id: docId.toString() }; 59 | db.create(COLL_PERSON, newObj); 60 | docId = docId + 1; 61 | } 62 | }); 63 | } 64 | 65 | static deleteAllPersons() { 66 | const db = getDB(); 67 | let allObjects = db.objects(Person); 68 | db.write(() => db.delete(allObjects)); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/renderer/databases/realm/db.ts: -------------------------------------------------------------------------------- 1 | import Realm from "realm"; 2 | 3 | import { Person } from "./Person"; 4 | import { getDBDir } from "../../utils"; 5 | 6 | const dbConfig: Realm.Configuration = { 7 | schema: [Person], 8 | deleteRealmIfMigrationNeeded: true, 9 | path: getDBDir("realm", "data.realm"), 10 | }; 11 | 12 | let realmInstance: Realm | null; 13 | let dbPromise: ProgressPromise; 14 | 15 | const getDB = (): Realm => { 16 | if (realmInstance == null) realmInstance = new Realm(dbConfig); 17 | return realmInstance!; 18 | }; 19 | 20 | export const getDbPromise = () => { 21 | if (!dbPromise) dbPromise = Realm.open(dbConfig); 22 | return dbPromise; 23 | }; 24 | 25 | export default getDB; 26 | -------------------------------------------------------------------------------- /src/renderer/databases/realm/helpers.ts: -------------------------------------------------------------------------------- 1 | // import { Person as KPerson } from "./schema"; 2 | import getDB from "./db"; 3 | // import { UserDocType } from "../types"; 4 | import { timeStart, timeEnd } from "../../utils"; 5 | import { Person } from "./Person"; 6 | 7 | // const addAUser = (user: UserDocType): KPerson => { 8 | // const addedUser = getDB().create(KPerson, user); 9 | // return addedUser; 10 | // }; 11 | 12 | export const getCount = () => { 13 | const t0 = timeStart(); 14 | 15 | const count = getDB().objects(Person).length; 16 | console.log("Total users Count: ", count); 17 | timeEnd(t0, `getCount - ${count}`); 18 | return count; 19 | }; 20 | 21 | export const getDocs = ( 22 | count: number, 23 | page: number = 1, 24 | saveTimeTaken?: React.Dispatch> 25 | ) => { 26 | const t0 = timeStart(); 27 | const allDocs = getDB().objects(Person).filtered("phone != name"); 28 | const skip = (page - 1) * count; 29 | let lazyDocs = allDocs.slice(skip, skip + count); 30 | const docs = Array.from(lazyDocs); 31 | console.log( 32 | `retrived ${docs.length} docs from users (skipped : ${page * count})` 33 | ); 34 | const timeTaken = timeEnd(t0, `getDocs - ${docs.length} items`); 35 | saveTimeTaken && saveTimeTaken([timeTaken, docs.length]); 36 | return (docs as unknown) as Person[]; 37 | }; 38 | 39 | export const addUserstoRealm = async ( 40 | total: number, 41 | setProgress: React.Dispatch>, 42 | saveTimeTaken?: React.Dispatch> 43 | ) => { 44 | setProgress(100); 45 | const t0 = performance.now(); 46 | const timeTaken = []; 47 | // if the chunk is the default one set it to an appropriate value (max of 100 or .5% increment is considered) 48 | let docID = getCount(); 49 | const ta0 = performance.now(); 50 | Person.AddDummyPersons(total, docID); 51 | const ta1 = performance.now(); 52 | timeTaken.push(ta1 - ta0); 53 | const t1 = performance.now(); 54 | console.log( 55 | `realm: Time Taken to add ${total} users : ${(ta1 - ta0).toFixed(1)}ms/${( 56 | t1 - t0 57 | ).toFixed(1)}` 58 | ); 59 | saveTimeTaken && saveTimeTaken([+(ta1 - ta0).toFixed(2), total]); 60 | setProgress(100); 61 | console.log("done adding users"); 62 | }; 63 | 64 | export const deleteAllUsers = () => { 65 | Person.deleteAllPersons(); 66 | }; 67 | -------------------------------------------------------------------------------- /src/renderer/databases/realm/sample.ts: -------------------------------------------------------------------------------- 1 | import Realm from "realm"; 2 | 3 | export const testRealm = () => { 4 | Realm.open({ 5 | schema: [ 6 | { 7 | name: "Page", 8 | properties: { 9 | id: "int", 10 | }, 11 | }, 12 | ], 13 | }).then((realm) => { 14 | console.time("nothing"); 15 | console.timeEnd("nothing"); 16 | 17 | console.time("warmup"); 18 | realm.write(() => { 19 | for (let i = 0; i < 10; i++) { 20 | realm.create("Page", { id: 5 }); 21 | } 22 | }); 23 | console.timeEnd("warmup"); 24 | 25 | for (let objects = 0; objects <= 1000; objects += 100) { 26 | let test = "Objects " + objects; 27 | console.time(test); 28 | realm.write(() => { 29 | for (let i = 0; i < 100; i++) { 30 | realm.create("Page", { id: 5 }); 31 | } 32 | }); 33 | console.timeEnd(test); 34 | } 35 | }); 36 | }; 37 | -------------------------------------------------------------------------------- /src/renderer/databases/realm/schema.ts: -------------------------------------------------------------------------------- 1 | import Realm from "realm"; 2 | import { UserDocType } from "../../types"; 3 | 4 | export class Person implements UserDocType { 5 | name: string = ""; 6 | phone: string = ""; 7 | address: string = ""; 8 | area?: string | undefined; 9 | static schema: Realm.ObjectSchema; 10 | } 11 | 12 | Person.schema = { 13 | name: "Person", 14 | properties: { 15 | name: "string", 16 | phone: "string", 17 | address: "string", 18 | area: "string", 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /src/renderer/databases/rxdb/db.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import { 3 | createRxDatabase, 4 | addRxPlugin, 5 | RxDatabase, 6 | RxJsonSchema, 7 | checkAdapter, 8 | } from "rxdb"; 9 | import { timeStart, timeEnd, getDBDir } from "../../utils/helpers"; 10 | import { 11 | UserCollection, 12 | UserDocType, 13 | UserCollectionMethods, 14 | MyDatabaseCollections, 15 | MyDatabase, 16 | IAdapter, 17 | } from "../../types"; 18 | 19 | import { RxDBAdapterCheckPlugin } from "rxdb/plugins/adapter-check"; 20 | import { RxDBEncryptionPlugin } from "rxdb/plugins/encryption"; 21 | import { RxDBQueryBuilderPlugin } from "rxdb/plugins/query-builder"; 22 | import { RxDBValidatePlugin } from "rxdb/plugins/validate"; 23 | 24 | let dbPromise: Promise>; 25 | const supportedAdapters: IAdapter[] = []; 26 | 27 | addRxPlugin(RxDBAdapterCheckPlugin); 28 | addRxPlugin(RxDBEncryptionPlugin); 29 | addRxPlugin(RxDBQueryBuilderPlugin); 30 | addRxPlugin(RxDBValidatePlugin); 31 | 32 | addRxPlugin(require("pouchdb-adapter-memory")); 33 | addRxPlugin(require("pouchdb-adapter-idb")); 34 | addRxPlugin(require("pouchdb-adapter-node-websql")); 35 | // addRxPlugin(require("pouchdb-adapter-websql")); 36 | addRxPlugin(require("pouchdb-adapter-leveldb")); 37 | 38 | const _checkAdapter = () => { 39 | checkAdapter("localstorage").then((val) => { 40 | console.log("RXJS -> Adapter -> localstorage status :", val); 41 | if (val && supportedAdapters.indexOf("localstorage") === -1) 42 | supportedAdapters.push("localstorage"); 43 | }); 44 | checkAdapter("idb").then((val) => { 45 | console.log("RXJS -> Adapter -> idb status :", val); 46 | if (val && supportedAdapters.indexOf("idb") === -1) 47 | supportedAdapters.push("idb"); 48 | }); 49 | checkAdapter("memory").then((val) => { 50 | console.log("RXJS -> Adapter -> memory status :", val); 51 | if (val && supportedAdapters.indexOf("memory") === -1) 52 | supportedAdapters.push("memory"); 53 | }); 54 | checkAdapter("leveldb").then((val) => { 55 | console.log("RXJS -> Adapter -> leveldb status :", val); 56 | if (val && supportedAdapters.indexOf("leveldb") === -1) 57 | supportedAdapters.push("leveldb"); 58 | }); 59 | }; 60 | _checkAdapter(); 61 | 62 | const userSchema: RxJsonSchema = { 63 | title: "vendor schema", 64 | description: "describes a vendor", 65 | version: 0, 66 | keyCompression: false, 67 | type: "object", 68 | properties: { 69 | name: { 70 | type: "string", 71 | }, 72 | phone: { 73 | type: "string", 74 | primary: true, 75 | }, 76 | address: { 77 | type: "string", 78 | }, 79 | area: { 80 | type: "string", 81 | }, 82 | }, 83 | required: ["name", "phone", "address"], 84 | }; 85 | 86 | const userCollectionMethods: UserCollectionMethods = { 87 | async getCount(this: UserCollection) { 88 | const t0 = timeStart(); 89 | const allDocs = await this.find().exec(); 90 | console.log("Total users Count: ", allDocs.length); 91 | timeEnd(t0, `getCount - ${allDocs.length}`); 92 | return allDocs.length; 93 | }, 94 | async getCountPouch(this: UserCollection) { 95 | const t0 = timeStart(); 96 | 97 | const entries = await this.pouch.allDocs().catch((err) => { 98 | console.log("failed to get Count with Pouch", err); 99 | }); 100 | console.log("Total users Count: ", entries.rows.length); 101 | timeEnd(t0, `getCountPouch - ${entries.rows.length}`); 102 | 103 | return entries.rows.length; 104 | }, 105 | 106 | async getCountWithInfo(this: UserCollection) { 107 | const t0 = timeStart(); 108 | const info = await this.pouch.info(); 109 | console.log("Total users Count: ", info.doc_count); 110 | timeEnd(t0, `getCountWithInfo - ${info.doc_count}`); 111 | return info.doc_count; 112 | }, 113 | 114 | async getDocs( 115 | this: UserCollection, 116 | count: number, 117 | page: number = 1, 118 | saveTimeTaken?: React.Dispatch> 119 | ) { 120 | const t0 = timeStart(); 121 | 122 | const allDocs = await this.find() 123 | .skip(count * (page - 1)) 124 | .limit(count) 125 | .exec(); 126 | console.log( 127 | `retrived ${allDocs.length} docs from users (skipped : ${page * count})` 128 | ); 129 | const timeTaken = timeEnd(t0, `getDocs - ${allDocs.length} items`); 130 | saveTimeTaken && saveTimeTaken([timeTaken, allDocs.length]); 131 | return allDocs; 132 | }, 133 | 134 | async getDocsPouch(this: UserCollection, count: number, page: number = 0) { 135 | const t0 = timeStart(); 136 | const allDocs = await this.pouch.allDocs({ include_docs: true }); 137 | timeEnd(t0, `getDocsPouch - ${allDocs.length} items`); 138 | return allDocs; 139 | }, 140 | 141 | async addDocs( 142 | this: UserCollection, 143 | docs: UserDocType[], 144 | saveTimeTaken?: React.Dispatch> 145 | ) { 146 | const t0 = timeStart(); 147 | const res = await this.bulkInsert(docs); 148 | const timeTaken = timeEnd(t0, `addDocs - ${docs.length} items`); 149 | saveTimeTaken && saveTimeTaken([timeTaken, docs.length]); 150 | 151 | return res; 152 | }, 153 | }; 154 | 155 | const collections = [ 156 | { 157 | name: "users", 158 | schema: userSchema, 159 | // methods: userDocMethods, 160 | statics: userCollectionMethods, 161 | }, 162 | ]; 163 | 164 | const createDB = async (adapter: IAdapter) => { 165 | console.log("DatabaseService: creating database.."); 166 | let dbname = "testdb"; 167 | if (adapter === "leveldb") dbname = getDBDir("rxdb", "data.ldb"); 168 | if (adapter === "websql") dbname = getDBDir("rxdb", "data.sqlite"); 169 | 170 | const db: MyDatabase = await createRxDatabase({ 171 | name: dbname, // <- name 172 | adapter: adapter, // <- storage-adapter 173 | password: "passpasspass", // <- password (optional) 174 | multiInstance: false, // This should be set to false when you have single-instances like a single-window electron-app 175 | eventReduce: true, // <- eventReduce (optional, default: true) 176 | }); 177 | 178 | console.dir(db); 179 | console.log("DatabaseService: created database"); 180 | await Promise.all(collections.map((colData) => db.collection(colData))); 181 | return db; 182 | }; 183 | 184 | const deleteDB = async () => { 185 | if (!dbPromise) return false; 186 | const db = await dbPromise; 187 | await db.destroy(); 188 | await db.remove(); 189 | return true; 190 | }; 191 | 192 | export const changeAdapter = async (adapter: IAdapter) => { 193 | console.warn(`re-creating database with adapter '${adapter}'`); 194 | await deleteDB(); 195 | dbPromise = createDB(adapter); 196 | return dbPromise; 197 | }; 198 | 199 | const getDB = async (adpater: IAdapter) => { 200 | if (!dbPromise) dbPromise = createDB(adpater); 201 | return dbPromise; 202 | }; 203 | 204 | // eslint-disable-next-line import/prefer-default-export 205 | export { getDB, supportedAdapters }; 206 | -------------------------------------------------------------------------------- /src/renderer/databases/rxdb/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./db"; 2 | -------------------------------------------------------------------------------- /src/renderer/databases/sqlite/db.ts: -------------------------------------------------------------------------------- 1 | import { connect } from "trilogy"; 2 | import { getDBDir } from "../../utils"; 3 | 4 | const dbPath = getDBDir("sqlite", "data.sqlite"); 5 | 6 | // export const db = connect(dbPath, { 7 | // client: "sql.js", 8 | // }); 9 | export const db = connect(dbPath); 10 | -------------------------------------------------------------------------------- /src/renderer/databases/sqlite/models/Person.ts: -------------------------------------------------------------------------------- 1 | import { db } from "../db"; 2 | import { UserDocType } from "../../../types"; 3 | import { Model } from "trilogy"; 4 | 5 | const personSchema = { 6 | name: String, 7 | phone: String, 8 | address: String, 9 | area: String, 10 | id: "increments", 11 | }; 12 | 13 | let userModel: Model | undefined = undefined; 14 | 15 | export const getUserModel = async () => { 16 | if (!userModel) 17 | userModel = await db.model("users", personSchema); 18 | return userModel; 19 | }; 20 | -------------------------------------------------------------------------------- /src/renderer/databases/sqlite/service.ts: -------------------------------------------------------------------------------- 1 | import { timeStart, timeEnd, createAUser, promiseProgress } from "../../utils"; 2 | import { getUserModel } from "./models/Person"; 3 | import { UserDocType } from "../../types"; 4 | import { db } from "./db"; 5 | 6 | export const getCount = async () => { 7 | const t0 = timeStart(); 8 | const users = await getUserModel(); 9 | 10 | const count = await users.count(); 11 | 12 | console.log("Total users Count: ", count); 13 | timeEnd(t0, `getCount - ${count}`); 14 | return count; 15 | }; 16 | 17 | export const getDocs = async ( 18 | limit: number, 19 | page: number = 1, 20 | saveTimeTaken?: React.Dispatch> 21 | ) => { 22 | console.log("getDocs"); 23 | const users = await getUserModel(); 24 | 25 | const t0 = timeStart(); 26 | const skip = (page - 1) * limit; 27 | 28 | const docs = await users.find({}, { limit, skip }); 29 | console.log(`retrived ${docs.length} docs from users (skipped : ${skip})`); 30 | const timeTaken = timeEnd(t0, `getDocs - ${docs.length} items`); 31 | saveTimeTaken && saveTimeTaken([timeTaken, docs.length]); 32 | return docs; 33 | }; 34 | 35 | export const addUserstoDB = async ( 36 | total: number, 37 | setProgress: React.Dispatch>, 38 | saveTimeTaken?: React.Dispatch> 39 | ) => { 40 | const t0 = performance.now(); 41 | const timeTaken = []; 42 | 43 | const sqliteChunkLimit = 200; // this should be changed, hardcoded to fix SQLITE ERROR, Toomany Variables 44 | const chunk = Math.min(Math.floor(total / 100), sqliteChunkLimit); 45 | console.log("inserting data in chunks of ", chunk); 46 | 47 | const chunkArray = Array(Math.floor(total / chunk)).fill(chunk); 48 | if (total % chunk > 0) chunkArray.push(total % chunk); 49 | let done = 0; 50 | 51 | for (const aChunk of chunkArray) { 52 | const userArry = []; 53 | for (let i = 0; i < aChunk; i++) { 54 | userArry.push(createAUser()); 55 | } 56 | const ta0 = performance.now(); 57 | await db.knex("users").insert(userArry); 58 | const ta1 = performance.now(); 59 | timeTaken.push(ta1 - ta0); 60 | done = done + aChunk; 61 | setProgress(+((done / total) * 100).toFixed(1)); 62 | } 63 | const t1 = performance.now(); 64 | console.log( 65 | `sqlite: Time Taken to add ${total} users : ${(t1 - t0).toFixed(1)}ms` 66 | ); 67 | console.log( 68 | `Pass: ${timeTaken.length}, time: ${timeTaken 69 | .reduce((a, b) => a + b, 0) 70 | .toFixed(1)},min: ${Math.min(...timeTaken).toFixed(1)},max: ${Math.max( 71 | ...timeTaken 72 | ).toFixed(1)},avg: ${( 73 | timeTaken.reduce((a, b) => a + b, 0) / timeTaken.length 74 | ).toFixed(1)}, ` 75 | ); 76 | saveTimeTaken && 77 | saveTimeTaken([+timeTaken.reduce((a, b) => a + b, 0).toFixed(2), done]); 78 | }; 79 | 80 | export const addUserstoDBV1 = async ( 81 | total: number, 82 | setProgress: React.Dispatch>, 83 | saveTimeTaken?: React.Dispatch> 84 | ) => { 85 | setProgress(0); 86 | let progrssVal = 0; 87 | const users = await getUserModel(); 88 | const t0 = performance.now(); 89 | const timeTaken = []; 90 | const usersListPromise: Promise[] = []; 91 | for (let i = 0; i < total; i++) { 92 | const aDoc = createAUser(); 93 | const userPromise = users.create(aDoc); 94 | usersListPromise.push(userPromise); 95 | } 96 | const ta0 = performance.now(); 97 | 98 | // await Promise.all(usersListPromise); 99 | await promiseProgress(usersListPromise, (percent) => { 100 | // set progress only for 1% increase 101 | if (progrssVal + 1 <= percent) { 102 | console.log("kke progress => ", percent, progrssVal); 103 | progrssVal = Math.ceil(percent); 104 | setProgress(percent); 105 | } 106 | }); 107 | 108 | const ta1 = performance.now(); 109 | timeTaken.push(ta1 - ta0); 110 | const t1 = performance.now(); 111 | console.log( 112 | `realm: Time Taken to add ${total} users : ${(ta1 - ta0).toFixed(1)}ms/${( 113 | t1 - t0 114 | ).toFixed(1)}` 115 | ); 116 | saveTimeTaken && saveTimeTaken([+(ta1 - ta0).toFixed(2), total]); 117 | setProgress(100); 118 | console.log("done adding users"); 119 | }; 120 | 121 | export const deleteAllUsers = async () => { 122 | const users = await getUserModel(); 123 | return await users.clear(); 124 | }; 125 | -------------------------------------------------------------------------------- /src/renderer/index.css: -------------------------------------------------------------------------------- 1 | main > .container { 2 | padding: 90px 0 50px 0; 3 | } 4 | -------------------------------------------------------------------------------- /src/renderer/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import "./index.css"; 4 | import App from "./App"; 5 | import "bootstrap/dist/css/bootstrap.min.css"; 6 | import "react-bootstrap-table-next/dist/react-bootstrap-table2.min.css"; 7 | 8 | ReactDOM.render( 9 | 10 | 11 | , 12 | document.getElementById("app") 13 | ); 14 | 15 | if (module.hot) { 16 | module.hot.accept(); 17 | } 18 | -------------------------------------------------------------------------------- /src/renderer/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/renderer/pages/Home.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Container, Navbar, Nav, Card } from "react-bootstrap"; 3 | import RealMDashboard from "./RealMDashboard"; 4 | import RxJsDashboard from "./RxDbDashboard"; 5 | import SQLiteDashboard from "./SQLiteDashboard"; 6 | import { version } from "./../../../package.json"; 7 | import { IDatabaseMode } from "../components/DatabaseDetail"; 8 | import logo from "../logo.svg"; 9 | 10 | export function Home() { 11 | const [databaseMode, setDatabaseMode] = useState("RxDB"); 12 | 13 | let dashboard = <>; 14 | 15 | switch (databaseMode) { 16 | case "RxDB": 17 | dashboard = ; 18 | break; 19 | case "Realm": 20 | dashboard = ; 21 | break; 22 | case "SQLite": 23 | dashboard = ; 24 | break; 25 | 26 | default: 27 | dashboard = <>; 28 | break; 29 | } 30 | 31 | return ( 32 | <> 33 |
34 | 40 |
41 | 42 | 49 | 50 | Electron-React 51 |
52 |
53 | 79 |
80 |
81 |
82 |
83 | 84 | 85 |

Electron - React - {databaseMode}

86 |
87 | {dashboard} 88 |
89 |
90 |
91 | 95 | 96 | Copyright © {new Date().getFullYear()} Vazra. MIT License. 97 | 98 | 99 | v{version} 100 | 101 | 102 |
103 | 104 | ); 105 | } 106 | 107 | export default Home; 108 | -------------------------------------------------------------------------------- /src/renderer/pages/RealMDashboard.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { 3 | Button, 4 | Row, 5 | Col, 6 | Form, 7 | ProgressBar, 8 | Spinner, 9 | ButtonGroup, 10 | Card, 11 | } from "react-bootstrap"; 12 | import RemoteTable from "../components/RemoteTable"; 13 | import { 14 | addUserstoRealm, 15 | getDocs, 16 | getCount, 17 | deleteAllUsers, 18 | } from "../databases/realm/helpers"; 19 | import { TableChangeType, TableChangeState } from "react-bootstrap-table-next"; 20 | import { Person } from "../databases/realm/Person"; 21 | 22 | export function RealMDashboard() { 23 | const [users, setUsers] = useState(); 24 | // const [db, setDB] = useState>(); 25 | const [totalCount, setTotalCount] = useState(0); 26 | const [addCount, setAddCount] = useState(100); 27 | const [progress, setProgress] = useState(0); 28 | const [sizePerPage, setSizePerPage] = useState(10); 29 | const [page, setPage] = useState(1); 30 | const [isLoading, setLoading] = useState<[boolean, string]>([false, ""]); 31 | const [latestReadTime, setLatestReadTime] = useState<[number, number]>([ 32 | 334.54, 33 | 20, 34 | ]); 35 | const [latestWriteTime, setLatestWriteTime] = useState<[number, number]>([ 36 | 334.54, 37 | 20, 38 | ]); 39 | 40 | useEffect(() => { 41 | async function anyNameFunction() { 42 | setLoading([true, "initializing database"]); 43 | await addUserstoRealm(100, setProgress, setLatestWriteTime); 44 | setLoading([false, ""]); 45 | } 46 | anyNameFunction(); 47 | }, []); 48 | 49 | const reloadUI = async () => { 50 | setUsers([]); 51 | setProgress(0); 52 | setTotalCount(0); 53 | setPage(1); 54 | setSizePerPage(10); 55 | getDocsAndCount(sizePerPage, page); 56 | }; 57 | 58 | function getDocsAndCount(perPageCount: number, pageNo: number) { 59 | const count = getCount(); 60 | setTotalCount(count); 61 | const newUsers = getDocs(perPageCount, pageNo, setLatestReadTime); 62 | setUsers(newUsers); 63 | } 64 | 65 | useEffect(() => { 66 | console.log("pagechanged -", "getDocsAndCount", page, sizePerPage); 67 | getDocsAndCount(sizePerPage, page); 68 | }, [page, sizePerPage]); 69 | 70 | const handleChangeInput = (e: React.ChangeEvent) => { 71 | e.preventDefault(); 72 | setAddCount(+e.target.value); 73 | setProgress(0); 74 | }; 75 | 76 | const handleAddSubmit = async (e: React.FormEvent) => { 77 | e.preventDefault(); 78 | setProgress(0); 79 | await addUserstoRealm(addCount, setProgress, setLatestWriteTime); 80 | setAddCount(100); 81 | getDocsAndCount(sizePerPage, page); 82 | }; 83 | 84 | const handleTableChange = ( 85 | type: TableChangeType, 86 | { page, sizePerPage }: TableChangeState 87 | ) => { 88 | setPage(page); 89 | setSizePerPage(sizePerPage); 90 | }; 91 | 92 | const progressInstance = ( 93 | 94 | ); 95 | return ( 96 | <> 97 | 98 | 99 |
100 | 101 | 102 | 109 | 110 | 111 | 112 | 115 |
116 | {progressInstance} 117 | 118 | 119 |
120 |
121 | 122 | 123 | {isLoading[0] ? ( 124 | 125 | 126 | Loading...{isLoading[1]} 127 | 128 | 129 | ) : ( 130 | 131 | )} 132 | 133 | 134 | 135 | {" "} 144 | {" "} 154 |

155 | ({users?.length}/{totalCount}) Fetched 156 |

157 | 158 | 159 |
160 | 161 | 162 | 163 | 164 | Latest Read Time : {latestReadTime[0]}ms for {latestReadTime[1]}{" "} 165 | docs 166 | 167 | 168 | 169 | 170 | 171 | 172 | Latest Write Time : {latestWriteTime[0]}ms for{" "} 173 | {latestWriteTime[1]} docs 174 | 175 | 176 | 177 | 178 | 179 | 180 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | {" "} 194 | First Doc : {JSON.stringify(users && users[0])} 195 | 196 | 197 | 198 | 199 | 200 | ); 201 | } 202 | 203 | export default RealMDashboard; 204 | -------------------------------------------------------------------------------- /src/renderer/pages/RxDbDashboard.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { getDB, changeAdapter } from "../databases/rxdb/db"; 3 | import { RxDatabase } from "rxdb"; 4 | import { addUserstoDB } from "../utils/helpers"; 5 | import { 6 | Button, 7 | Row, 8 | Col, 9 | Form, 10 | ProgressBar, 11 | Card, 12 | ButtonGroup, 13 | Spinner, 14 | } from "react-bootstrap"; 15 | import { TableChangeType, TableChangeState } from "react-bootstrap-table-next"; 16 | import RemoteTable from "../components/RemoteTable"; 17 | import { UserDocType, MyDatabaseCollections, IAdapter } from "../types"; 18 | 19 | export function RxDbDashboard() { 20 | const [users, setUsers] = useState(); 21 | const [db, setDB] = useState>(); 22 | const [totalCount, setTotalCount] = useState(0); 23 | const [addCount, setAddCount] = useState(100); 24 | const [progress, setProgress] = useState(0); 25 | const [sizePerPage, setSizePerPage] = useState(10); 26 | const [page, setPage] = useState(1); 27 | const [adapter, setAdapter] = useState("memory"); 28 | const [isLoading, setLoading] = useState<[boolean, string]>([false, ""]); 29 | 30 | const [latestReadTime, setLatestReadTime] = useState<[number, number]>([ 31 | 334.54, 32 | 20, 33 | ]); 34 | const [latestWriteTime, setLatestWriteTime] = useState<[number, number]>([ 35 | 334.54, 36 | 20, 37 | ]); 38 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 39 | const [availabeAdapters] = useState([ 40 | "idb", 41 | "memory", 42 | "leveldb", 43 | "websql", 44 | ]); 45 | 46 | useEffect(() => { 47 | // create the databse 48 | async function anyNameFunction() { 49 | setLoading([true, "initializing database"]); 50 | const theDB = await getDB(adapter); 51 | 52 | setDB(theDB); 53 | await addUserstoDB(theDB, 100, setProgress, setLatestWriteTime); 54 | 55 | const users = await theDB?.users?.getDocs(10, 1, setLatestReadTime); 56 | setUsers(users); 57 | setLoading([false, ""]); 58 | } 59 | anyNameFunction(); 60 | }, [adapter]); 61 | 62 | const getDocs = async () => { 63 | const users = 64 | (await db?.users?.getDocs(sizePerPage, page, setLatestReadTime)) || []; 65 | setUsers(users); 66 | }; 67 | 68 | const reloadUI = async () => { 69 | setUsers([]); 70 | setTotalCount(0); 71 | setProgress(0); 72 | const theDB = await getDB(adapter); 73 | setDB(theDB); 74 | setPage(1); 75 | setSizePerPage(10); 76 | await getDocs(); 77 | }; 78 | 79 | useEffect(() => { 80 | async function anyNameFunction() { 81 | setLoading([true, "initializing database"]); 82 | const users = await db?.users?.getDocs( 83 | sizePerPage, 84 | page, 85 | setLatestReadTime 86 | ); 87 | setUsers(users); 88 | setLoading([false, ""]); 89 | } 90 | anyNameFunction(); 91 | }, [db, page, sizePerPage]); 92 | 93 | useEffect(() => { 94 | // Create an scoped async function in the hook 95 | async function anyNameFunction() { 96 | const theDB = await getDB(adapter); 97 | await theDB?.users?.getCountWithInfo().then((count) => { 98 | setTotalCount(count); 99 | }); 100 | } 101 | // Execute the created function directly 102 | anyNameFunction(); 103 | }, [adapter, users]); 104 | 105 | const handleChangeInput = (e: React.ChangeEvent) => { 106 | e.preventDefault(); 107 | setAddCount(+e.target.value); 108 | setProgress(0); 109 | }; 110 | const adapterLabel = { 111 | idb: "IndexedDB", 112 | memory: "In Memmory", 113 | websql: "Node SQL", 114 | leveldb: "Level DB", 115 | localstorage: "Local Storage", 116 | }; 117 | 118 | const handleAddSubmit = async (e: React.FormEvent) => { 119 | e.preventDefault(); 120 | setProgress(0); 121 | db && (await addUserstoDB(db, addCount, setProgress, setLatestWriteTime)); 122 | setAddCount(100); 123 | getDocs(); 124 | }; 125 | 126 | const progressInstance = ( 127 | 128 | ); 129 | 130 | const handleTableChange = ( 131 | type: TableChangeType, 132 | { page, sizePerPage }: TableChangeState 133 | ) => { 134 | setPage(page); 135 | setSizePerPage(sizePerPage); 136 | }; 137 | 138 | return ( 139 | <> 140 | 141 | 142 |
143 | 144 | 145 | 152 | 153 | 154 | 155 | 158 |
159 | {progressInstance} 160 | 161 | 162 |
163 |
164 | 165 | 166 | {isLoading[0] ? ( 167 | 168 | 169 | Loading...{isLoading[1]} 170 | 171 | 172 | ) : ( 173 | 174 | {availabeAdapters.map((anAdapter) => ( 175 | 186 | ))} 187 | 188 | )} 189 | 190 | 191 | 192 | {" "} 201 | {" "} 211 |

212 | ({users?.length}/{totalCount}) Fetched 213 |

214 | 215 | 216 |
217 | 218 | 219 | 220 | 221 | Latest Read Time : {latestReadTime[0]}ms for {latestReadTime[1]}{" "} 222 | docs 223 | 224 | 225 | 226 | 227 | 228 | 229 | Latest Write Time : {latestWriteTime[0]}ms for{" "} 230 | {latestWriteTime[1]} docs 231 | 232 | 233 | 234 | 235 | 236 | 237 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | {" "} 251 | First Doc : {JSON.stringify(users && users[0])} 252 | 253 | 254 | 255 | 256 | 257 | ); 258 | } 259 | 260 | export default RxDbDashboard; 261 | -------------------------------------------------------------------------------- /src/renderer/pages/SQLiteDashboard.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { 3 | Button, 4 | Row, 5 | Col, 6 | Form, 7 | ProgressBar, 8 | Spinner, 9 | ButtonGroup, 10 | Card, 11 | } from "react-bootstrap"; 12 | import RemoteTable from "../components/RemoteTable"; 13 | import { TableChangeType, TableChangeState } from "react-bootstrap-table-next"; 14 | import { UserDocType } from "../types"; 15 | import { 16 | addUserstoDB, 17 | getCount, 18 | getDocs, 19 | deleteAllUsers, 20 | } from "../databases/sqlite/service"; 21 | 22 | export function SQLiteDashboard() { 23 | const [users, setUsers] = useState(); 24 | const [totalCount, setTotalCount] = useState(0); 25 | const [addCount, setAddCount] = useState(100); 26 | const [progress, setProgress] = useState(0); 27 | const [sizePerPage, setSizePerPage] = useState(10); 28 | const [page, setPage] = useState(1); 29 | const [isLoading, setLoading] = useState<[boolean, string]>([false, ""]); 30 | const [latestReadTime, setLatestReadTime] = useState<[number, number]>([ 31 | 334.54, 32 | 20, 33 | ]); 34 | const [latestWriteTime, setLatestWriteTime] = useState<[number, number]>([ 35 | 334.54, 36 | 20, 37 | ]); 38 | 39 | useEffect(() => { 40 | // create the databse 41 | async function anyNameFunction() { 42 | setLoading([true, "initializing database"]); 43 | await addUserstoDB(100, setProgress, setLatestWriteTime); 44 | setLoading([false, ""]); 45 | } 46 | anyNameFunction(); 47 | }, []); 48 | 49 | const reloadUI = async () => { 50 | setUsers([]); 51 | setProgress(0); 52 | setTotalCount(0); 53 | setPage(1); 54 | setSizePerPage(10); 55 | getDocsAndCount(sizePerPage, page); 56 | }; 57 | 58 | async function getDocsAndCount(perPageCount: number, pageNo: number) { 59 | const count = await getCount(); 60 | setTotalCount(count); 61 | const newUsers = await getDocs(perPageCount, pageNo, setLatestReadTime); 62 | setUsers(newUsers); 63 | } 64 | 65 | useEffect(() => { 66 | console.log("pagechanged - ", "getDocsAndCount", page, sizePerPage); 67 | getDocsAndCount(sizePerPage, page); 68 | }, [page, sizePerPage]); 69 | 70 | const handleChangeInput = (e: React.ChangeEvent) => { 71 | e.preventDefault(); 72 | setAddCount(+e.target.value); 73 | setProgress(0); 74 | }; 75 | 76 | const handleAddSubmit = async (e: React.FormEvent) => { 77 | e.preventDefault(); 78 | setProgress(0); 79 | await addUserstoDB(addCount, setProgress, setLatestWriteTime); 80 | setAddCount(100); 81 | getDocsAndCount(sizePerPage, page); 82 | }; 83 | 84 | const handleTableChange = ( 85 | type: TableChangeType, 86 | { page, sizePerPage }: TableChangeState 87 | ) => { 88 | setPage(page); 89 | setSizePerPage(sizePerPage); 90 | }; 91 | 92 | const progressInstance = ( 93 | 94 | ); 95 | return ( 96 | <> 97 | 98 | 99 |
100 | 101 | 102 | 109 | 110 | 111 | 112 | 115 |
116 | {progressInstance} 117 | 118 | 119 |
120 |
121 | 122 | 123 | {isLoading[0] ? ( 124 | 125 | 126 | Loading...{isLoading[1]} 127 | 128 | 129 | ) : ( 130 | 131 | )} 132 | 133 | 134 | 135 | {" "} 144 | {" "} 154 |

155 | ({users?.length}/{totalCount}) Fetched 156 |

157 | 158 | 159 |
160 | 161 | 162 | 163 | 164 | Latest Read Time : {latestReadTime[0]}ms for {latestReadTime[1]}{" "} 165 | docs 166 | 167 | 168 | 169 | 170 | 171 | 172 | Latest Write Time : {latestWriteTime[0]}ms for{" "} 173 | {latestWriteTime[1]} docs 174 | 175 | 176 | 177 | 178 | 179 | 180 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | First Doc : {JSON.stringify(users && users[0])} 194 | 195 | 196 | 197 | 198 | 199 | ); 200 | } 201 | 202 | export default SQLiteDashboard; 203 | -------------------------------------------------------------------------------- /src/renderer/types.ts: -------------------------------------------------------------------------------- 1 | import { RxCollection, RxDocument, RxDatabase } from "rxdb/dist/types/types"; 2 | 3 | export type UserDocType = { 4 | name: string; 5 | phone: string; 6 | address: string; 7 | area?: string; // optional 8 | }; 9 | 10 | export type UserDocMethods = { 11 | scream: (v: string) => string; 12 | }; 13 | 14 | export type UserDocument = RxDocument; 15 | export type IAdapter = "idb" | "memory" | "websql" | "leveldb" | "localstorage"; 16 | 17 | export type MyDatabaseCollections = { 18 | users: UserCollection; 19 | }; 20 | 21 | export type MyDatabase = RxDatabase; 22 | 23 | // we declare one static ORM-method for the collection 24 | export type UserCollectionMethods = { 25 | getCount: (this: UserCollection) => Promise; 26 | getCountPouch: (this: UserCollection) => Promise; 27 | getCountWithInfo: (this: UserCollection) => Promise; 28 | addDocs: ( 29 | this: UserCollection, 30 | docs: UserDocType[], 31 | saveTimeTaken?: React.Dispatch> 32 | ) => void; 33 | getDocs: ( 34 | this: UserCollection, 35 | count: number, 36 | page?: number, 37 | saveTimeTaken?: React.Dispatch> 38 | ) => Promise; 39 | getDocsPouch: ( 40 | this: UserCollection, 41 | count: number, 42 | page: number 43 | ) => Promise; 44 | }; 45 | 46 | // and then merge all our types 47 | export type UserCollection = RxCollection< 48 | UserDocType, 49 | UserDocMethods, 50 | UserCollectionMethods 51 | >; 52 | -------------------------------------------------------------------------------- /src/renderer/utils/helpers.tsx: -------------------------------------------------------------------------------- 1 | import { RxDatabase } from "rxdb"; 2 | import React from "react"; 3 | import faker from "faker"; 4 | import { UserDocType, MyDatabaseCollections } from "../types"; 5 | 6 | import path from "path"; 7 | import fs from "fs"; 8 | 9 | const remote = require("electron").remote; 10 | const app = remote.app; 11 | 12 | export const getDBDir = (dbname: string, dbfile: string) => { 13 | const dirPath = path.join(app.getPath("userData"), "db", dbname); 14 | if (!fs.existsSync(dirPath)) fs.mkdirSync(dirPath, { recursive: true }); 15 | const dbPath = path.join(dirPath, dbfile); 16 | console.log("DB_PATH: ", dbPath); 17 | return dbPath; 18 | }; 19 | 20 | export function promiseProgress( 21 | proms: Promise[], 22 | progress_cb: (x: number) => void 23 | ) { 24 | let d = 0; 25 | progress_cb(0); 26 | for (const p of proms) { 27 | p.then(() => { 28 | d++; 29 | progress_cb((d * 100) / proms.length); 30 | }); 31 | } 32 | return Promise.all(proms); 33 | } 34 | 35 | export const createAUser = (): UserDocType => { 36 | var name = faker.name.findName(); 37 | var phone = faker.phone.phoneNumber(); 38 | var address = faker.address.streetAddress(); 39 | var area = faker.address.countryCode(); 40 | return { name, phone, address, area }; 41 | }; 42 | 43 | // ass n - number of dummy users to the db 44 | export const addUserstoDB = async ( 45 | db: RxDatabase | undefined, 46 | total: number, 47 | setProgress: React.Dispatch>, 48 | saveTimeTaken?: React.Dispatch>, 49 | chunk?: number 50 | ) => { 51 | const t0 = performance.now(); 52 | const timeTaken = []; 53 | 54 | // if the chunk is the default one set it to an appropriate value (max of 100 or .5% increment is considered) 55 | if (!chunk) { 56 | chunk = Math.max(100, Math.ceil(total / 200)); 57 | } 58 | console.log("inserting data in chunks of ", chunk); 59 | 60 | const chunkArray = Array(Math.floor(total / chunk)).fill(chunk); 61 | if (total % chunk > 0) chunkArray.push(total % chunk); 62 | console.log("chunk arry", chunkArray); 63 | let done = 0; 64 | 65 | for (const aChunk of chunkArray) { 66 | const userArry = []; 67 | for (let i = 0; i < aChunk; i++) { 68 | userArry.push(createAUser()); 69 | } 70 | const ta0 = performance.now(); 71 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 72 | /* const result = */ await db?.users.bulkInsert(userArry); 73 | const ta1 = performance.now(); 74 | timeTaken.push(ta1 - ta0); 75 | done = done + aChunk; 76 | setProgress(+((done / total) * 100).toFixed(1)); 77 | } 78 | const t1 = performance.now(); 79 | console.log( 80 | `${db?.adapter}: Time Taken to add ${total} users : ${(t1 - t0).toFixed( 81 | 1 82 | )}ms` 83 | ); 84 | console.log( 85 | `Pass: ${timeTaken.length}, time: ${timeTaken 86 | .reduce((a, b) => a + b, 0) 87 | .toFixed(1)},min: ${Math.min(...timeTaken).toFixed(1)},max: ${Math.max( 88 | ...timeTaken 89 | ).toFixed(1)},avg: ${( 90 | timeTaken.reduce((a, b) => a + b, 0) / timeTaken.length 91 | ).toFixed(1)}, ` 92 | ); 93 | saveTimeTaken && 94 | saveTimeTaken([+timeTaken.reduce((a, b) => a + b, 0).toFixed(2), done]); 95 | }; 96 | 97 | // helper function thart starts performace - time measurement 98 | export const timeStart = () => { 99 | return performance.now(); 100 | }; 101 | 102 | // helper function to end and print - time measurement 103 | export const timeEnd = (timeStart: number, funName: string) => { 104 | var t1 = performance.now(); 105 | console.log(`fun: ${funName} took ${(t1 - timeStart).toFixed(2)}ms`); 106 | return +(t1 - timeStart).toFixed(2); 107 | }; 108 | export const kkk = ""; 109 | -------------------------------------------------------------------------------- /src/renderer/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./helpers"; 2 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/electron-webpack/tsconfig-base.json", 3 | "compilerOptions": { 4 | "jsx": "react", 5 | "resolveJsonModule": true 6 | } 7 | } 8 | --------------------------------------------------------------------------------