From db99cf376855d93e7df13cb894817c5238528c9b Mon Sep 17 00:00:00 2001 From: feznyng Date: Thu, 21 Nov 2024 20:28:12 -0500 Subject: [PATCH 1/6] created remote adapter --- examples/typescript/yarn.lock | 8 +-- src/adapters/remote/index.js | 102 ++++++++++++++++++++++++++++++++++ src/adapters/remote/type.d.ts | 11 ++++ src/adapters/remote/type.js | 13 +++++ 4 files changed, 130 insertions(+), 4 deletions(-) create mode 100644 src/adapters/remote/index.js create mode 100644 src/adapters/remote/type.d.ts create mode 100644 src/adapters/remote/type.js diff --git a/examples/typescript/yarn.lock b/examples/typescript/yarn.lock index 5803d3ab8..fb4b4d795 100644 --- a/examples/typescript/yarn.lock +++ b/examples/typescript/yarn.lock @@ -2,10 +2,10 @@ # yarn lockfile v1 -"@babel/runtime@7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.7.tgz#f4f0d5530e8dbdf59b3451b9b3e594b6ba082e12" - integrity sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw== +"@babel/runtime@7.26.0": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.0.tgz#8600c2f595f277c60815256418b85356a65173c1" + integrity sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw== dependencies: regenerator-runtime "^0.14.0" diff --git a/src/adapters/remote/index.js b/src/adapters/remote/index.js new file mode 100644 index 000000000..bc2af071b --- /dev/null +++ b/src/adapters/remote/index.js @@ -0,0 +1,102 @@ +// @flow + +import { type ResultCallback } from '../../utils/fp/Result' + +import type { RecordId } from '../../Model' +import type { SerializedQuery } from '../../Query' +import type { TableName, AppSchema } from '../../Schema' +import type { SchemaMigrations } from '../../Schema/migrations' +import type { + DatabaseAdapter, + CachedQueryResult, + CachedFindResult, + BatchOperation, + UnsafeExecuteOperations, +} from '../type' + +type RemoteHandler = (op: string, args: any[], callback: ResultCallback) => void; + +type RemoteAdapterOptions = { + schema: AppSchema, + migrations?: SchemaMigrations, + handler: RemoteHandler, +} + +export default class RemoteAdapter implements DatabaseAdapter { + schema: AppSchema + dbName: string + migrations: ?SchemaMigrations + handler: RemoteHandler + + constructor(options: RemoteAdapterOptions) { + const { schema, migrations, handler } = options; + + this.schema = schema + this.migrations = migrations + this.handler = handler + } + + find(table: TableName, id: RecordId, callback: ResultCallback) { + this.handler('find', [table, id], callback) + } + + query(query: SerializedQuery, callback: ResultCallback) { + this.handler('query', [query], callback) + } + + queryIds(query: SerializedQuery, callback: ResultCallback) { + this.handler('queryIds', [query], callback) + } + + unsafeQueryRaw(query: SerializedQuery, callback: ResultCallback) { + this.handler('unsafeQueryRaw', [query], callback) + } + + count(query: SerializedQuery, callback: ResultCallback) { + this.handler('count', [query], callback) + } + + batch(operations: BatchOperation[], callback: ResultCallback) { + this.handler('batch', [operations], callback) + } + + getDeletedRecords(tableName: TableName, callback: ResultCallback) { + this.handler('getDeletedRecords', [tableName], callback) + } + + destroyDeletedRecords( + tableName: TableName, + recordIds: RecordId[], + callback: ResultCallback, + ) { + this.handler('batch', [tableName, recordIds], callback) + } + + unsafeLoadFromSync(jsonId: number, callback: ResultCallback) { + this.handler('unsafeLoadFromSync', [jsonId], callback) + } + + provideSyncJson(id: number, syncPullResultJson: string, callback: ResultCallback) { + this.handler('provideSyncJson', [id, syncPullResultJson], callback) + } + + unsafeResetDatabase(callback: ResultCallback) { + this.handler('unsafeResetDatabase', [], callback) + } + + unsafeExecute(work: UnsafeExecuteOperations, callback: ResultCallback) { + this.handler('unsafeExecute', [work], callback) + } + + getLocal(key: string, callback: ResultCallback) { + this.handler('getLocal', [key], callback) + } + + setLocal(key: string, value: string, callback: ResultCallback) { + this.handler('getLocal', [key, value], callback) + } + + removeLocal(key: string, callback: ResultCallback) { + this.handler('getLocal', [key], callback) + } +} \ No newline at end of file diff --git a/src/adapters/remote/type.d.ts b/src/adapters/remote/type.d.ts new file mode 100644 index 000000000..55abefbcb --- /dev/null +++ b/src/adapters/remote/type.d.ts @@ -0,0 +1,11 @@ +import { AppSchema } from "../../Schema"; +import { SchemaMigrations } from "../../Schema/migrations"; +import { ResultCallback } from "../../utils/fp/Result"; + +type RemoteHandler = (op: string, args: any[], callback: ResultCallback) => void; + +type RemoteAdapterOptions = { + schema: AppSchema, + migrations?: SchemaMigrations, + handler: RemoteHandler, +} \ No newline at end of file diff --git a/src/adapters/remote/type.js b/src/adapters/remote/type.js new file mode 100644 index 000000000..85c77fe58 --- /dev/null +++ b/src/adapters/remote/type.js @@ -0,0 +1,13 @@ +// @flow + +import { type ResultCallback } from '../../utils/fp/Result' +import type { AppSchema } from '../../Schema' +import type { SchemaMigrations } from '../../Schema/migrations' + +type RemoteHandler = (op: string, args: any[], callback: ResultCallback) => void; + +export type RemoteAdapterOptions = { + schema: AppSchema, + migrations?: SchemaMigrations, + handler: RemoteHandler, +} \ No newline at end of file From 2d67379839f626f4d81c7028b49d23eca3677ff6 Mon Sep 17 00:00:00 2001 From: feznyng Date: Thu, 21 Nov 2024 21:05:34 -0500 Subject: [PATCH 2/6] added installation instructions for electron --- docs-website/docs/docs/Installation.mdx | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/docs-website/docs/docs/Installation.mdx b/docs-website/docs/docs/Installation.mdx index 7fb85d359..bbd2ce468 100644 --- a/docs-website/docs/docs/Installation.mdx +++ b/docs-website/docs/docs/Installation.mdx @@ -382,6 +382,26 @@ You only need this if you want to use WatermelonDB in NodeJS with SQLite (e.g. f --- +## Electron (SQLite) setup + +You only need this if you want to use WatermelonDB in Electron with SQLite. + +1. Install [better-sqlite3](https://github.com/JoshuaWise/better-sqlite3) peer dependency + + ```sh + yarn add --dev better-sqlite3 + + # (or with npm:) + npm install -D better-sqlite3 + ``` +2. Run electron rebuild on sqlite3. This step is necessary to ensure the sqlite native build (.node) is compatible with Electron's version of Node.js. If you're using Electron Forge, this step will be performed for you during build **but not development**. + + ```sh + npx electron-rebuild -f -w -t dev better-sqlite3 + ``` + +--- + ## Next steps ➡️ After Watermelon is installed, [**set it up**](./Setup.md) From 4216188009d535f078d3366c21f2838d3775db19 Mon Sep 17 00:00:00 2001 From: feznyng Date: Thu, 21 Nov 2024 21:55:51 -0500 Subject: [PATCH 3/6] add electron set up --- docs-website/docs/docs/Setup.md | 79 ++++++++++++++++++++++++++++++++- 1 file changed, 77 insertions(+), 2 deletions(-) diff --git a/docs-website/docs/docs/Setup.md b/docs-website/docs/docs/Setup.md index 88b940c2e..428346e23 100644 --- a/docs-website/docs/docs/Setup.md +++ b/docs-website/docs/docs/Setup.md @@ -7,6 +7,8 @@ hide_title: true Make sure you [installed Watermelon](./Installation.mdx) before proceeding. +## Common + Create `model/schema.js` in your project. You'll need it for [the next step](./Schema.md). ```js @@ -32,7 +34,9 @@ export default schemaMigrations({ }) ``` -Now, in your `index.native.js`: +## React Native and Node.js (SQLite) + +Now, in your `index.native.js` (React Native) or `index.js` (Node.js): ```js import { Platform } from 'react-native' @@ -68,7 +72,78 @@ const database = new Database({ }) ``` -The above will work on React Native (iOS/Android) and NodeJS. For the web, instead of `SQLiteAdapter` use `LokiJSAdapter`: +## Electron (SQLite) +Electron requires a little extra set up since we have to use IPC between our renderer and main processes to execute queries and return the response. However, if you'd like to use LokiJS instead of SQLite you can skip this section and go to the Web section below. + +Let's set things up on the renderer side first. + +```js +import RemoteAdapter from '@nozbe/watermelondb/adapters/remote' +import { Database } from '@nozbe/watermelondb' +import schema from './model/schema' +import migrations from './model/migrations' + +const electronAPI = window.electronAPI + +const adapter = new RemoteAdapter({ + schema, + migrations, + handler: (op, args, callback) => { + electronAPI.handleAdapter({op, args}).then((res) => callback(res[0])) + } +}) + +const database = new Database({ + adapter, + modelClasses: [ + // Post, // ⬅️ You'll add Models to Watermelon here + ], +}) + +export default database +``` +Whenever Watermelon needs to interact with the database, it will do so through the remote adapter which in turn sends queries to sqlite over the Electron IPC bridge via the handler callback. + +Now that our renderer is all set, let's set up the other side in `main.js`: +```js +import SQLiteAdapter from "@nozbe/watermelondb/adapters/sqlite"; +import schema from './model/schema'; +import migrations from './model/migrations'; + +mainWindow.webContents.on('did-finish-load', () => { + const adapter = new SQLiteAdapter({ + schema, + migrations + }) + + async function handleAdapter(_, dispatch) { + return new Promise((res) => { + const { op, args } = dispatch; + adapter[op](...args, (...resp) => res(resp)) + }) + } + + ipcMain.removeHandler('db:handle') + ipcMain.handle('db:handle', handleAdapter) +}) +``` + +Above, we've set up the adapter that will actually interact with our SQLite database. When the renderer sends the `db:handle` event, the handleAdapter callback function will be invoked with the required arguments. It will then return a promise with the data the renderer wants. + +Note that we're re-instantiating the adapter inside a `'did-finish-load'` event handler. This ensures the cache maintained in the renderer and main is kept consistent during reloads (manually or due to HMR). + +We still need to expose this event to our renderer so in `preload.js` we'll add the following: +```js +import { contextBridge, ipcRenderer } from 'electron' + +contextBridge.exposeInMainWorld('electronAPI', { + handleAdapter: (...args) => ipcRenderer.invoke('db:handle', ...args) +}) +``` + +## Web (LokiJS) + +This set up is suitable for web apps. ```js import LokiJSAdapter from '@nozbe/watermelondb/adapters/lokijs' From bbd5d5fdcac3dd2e4c4ff2a27b669337b13ff99a Mon Sep 17 00:00:00 2001 From: feznyng Date: Thu, 21 Nov 2024 22:06:14 -0500 Subject: [PATCH 4/6] dedup types --- src/adapters/remote/index.js | 11 ++++------- src/adapters/remote/type.d.ts | 4 ++-- src/adapters/remote/type.js | 2 +- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/adapters/remote/index.js b/src/adapters/remote/index.js index bc2af071b..d61d36de8 100644 --- a/src/adapters/remote/index.js +++ b/src/adapters/remote/index.js @@ -14,13 +14,10 @@ import type { UnsafeExecuteOperations, } from '../type' -type RemoteHandler = (op: string, args: any[], callback: ResultCallback) => void; - -type RemoteAdapterOptions = { - schema: AppSchema, - migrations?: SchemaMigrations, - handler: RemoteHandler, -} +import type { + RemoteHandler, + RemoteAdapterOptions +} from './type' export default class RemoteAdapter implements DatabaseAdapter { schema: AppSchema diff --git a/src/adapters/remote/type.d.ts b/src/adapters/remote/type.d.ts index 55abefbcb..b23630e39 100644 --- a/src/adapters/remote/type.d.ts +++ b/src/adapters/remote/type.d.ts @@ -2,9 +2,9 @@ import { AppSchema } from "../../Schema"; import { SchemaMigrations } from "../../Schema/migrations"; import { ResultCallback } from "../../utils/fp/Result"; -type RemoteHandler = (op: string, args: any[], callback: ResultCallback) => void; +export type RemoteHandler = (op: string, args: any[], callback: ResultCallback) => void; -type RemoteAdapterOptions = { +export type RemoteAdapterOptions = { schema: AppSchema, migrations?: SchemaMigrations, handler: RemoteHandler, diff --git a/src/adapters/remote/type.js b/src/adapters/remote/type.js index 85c77fe58..a35c39bb0 100644 --- a/src/adapters/remote/type.js +++ b/src/adapters/remote/type.js @@ -4,7 +4,7 @@ import { type ResultCallback } from '../../utils/fp/Result' import type { AppSchema } from '../../Schema' import type { SchemaMigrations } from '../../Schema/migrations' -type RemoteHandler = (op: string, args: any[], callback: ResultCallback) => void; +export type RemoteHandler = (op: string, args: any[], callback: ResultCallback) => void; export type RemoteAdapterOptions = { schema: AppSchema, From e92e579f9f90175e706fbb806f6331931d366118 Mon Sep 17 00:00:00 2001 From: feznyng Date: Thu, 21 Nov 2024 22:20:11 -0500 Subject: [PATCH 5/6] revert yarn.lock --- yarn.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index ad9234b3a..ff1b38b53 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10102,4 +10102,4 @@ yocto-queue@^0.1.0: yoctocolors-cjs@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz#f4b905a840a37506813a7acaa28febe97767a242" - integrity sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA== + integrity sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA== \ No newline at end of file From 304647caf9416e1e3c65e6d0ebd6e43a80b13b75 Mon Sep 17 00:00:00 2001 From: feznyng Date: Fri, 22 Nov 2024 06:28:05 -0500 Subject: [PATCH 6/6] fix ops --- src/adapters/remote/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/adapters/remote/index.js b/src/adapters/remote/index.js index d61d36de8..d576f08be 100644 --- a/src/adapters/remote/index.js +++ b/src/adapters/remote/index.js @@ -66,7 +66,7 @@ export default class RemoteAdapter implements DatabaseAdapter { recordIds: RecordId[], callback: ResultCallback, ) { - this.handler('batch', [tableName, recordIds], callback) + this.handler('destroyDeletedRecords', [tableName, recordIds], callback) } unsafeLoadFromSync(jsonId: number, callback: ResultCallback) { @@ -90,10 +90,10 @@ export default class RemoteAdapter implements DatabaseAdapter { } setLocal(key: string, value: string, callback: ResultCallback) { - this.handler('getLocal', [key, value], callback) + this.handler('setLocal', [key, value], callback) } removeLocal(key: string, callback: ResultCallback) { - this.handler('getLocal', [key], callback) + this.handler('removeLocal', [key], callback) } } \ No newline at end of file