├── .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 |
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 |
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 |
82 |
83 |
84 |
85 | Electron - React - {databaseMode}
86 |
87 | {dashboard}
88 |
89 |
90 |
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------