Skip to content

Commit 8586a5a

Browse files
committed
Start node example running PowerSync in main process
1 parent 8c14e99 commit 8586a5a

File tree

10 files changed

+2082
-880
lines changed

10 files changed

+2082
-880
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.webpack/
2+
out/
3+
packages/
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import { createRequire } from 'node:module';
2+
3+
import type { ForgeConfig } from '@electron-forge/shared-types';
4+
import { MakerSquirrel } from '@electron-forge/maker-squirrel';
5+
import { MakerZIP } from '@electron-forge/maker-zip';
6+
import { MakerDeb } from '@electron-forge/maker-deb';
7+
import { MakerRpm } from '@electron-forge/maker-rpm';
8+
import { AutoUnpackNativesPlugin } from '@electron-forge/plugin-auto-unpack-natives';
9+
import { WebpackPlugin } from '@electron-forge/plugin-webpack';
10+
import type { Configuration, ModuleOptions } from 'webpack';
11+
import type IForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin';
12+
13+
const require = createRequire(import.meta.url);
14+
15+
const ForkTsCheckerWebpackPlugin: typeof IForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
16+
17+
const webpackPlugins = [
18+
new ForkTsCheckerWebpackPlugin({
19+
//logger: 'webpack-infrastructure'
20+
})
21+
];
22+
23+
const defaultWebpackRules: () => Required<ModuleOptions>['rules'] = () => {
24+
return [
25+
// Add support for native node modules
26+
{
27+
// We're specifying native_modules in the test because the asset relocator loader generates a
28+
// "fake" .node file which is really a cjs file.
29+
test: /native_modules[/\\].+\.node$/,
30+
use: 'node-loader'
31+
},
32+
{
33+
test: /[/\\]node_modules[/\\].+\.(m?js|node)$/,
34+
parser: { amd: false },
35+
use: {
36+
loader: '@vercel/webpack-asset-relocator-loader',
37+
options: {
38+
outputAssetBase: 'native_modules'
39+
}
40+
}
41+
},
42+
{
43+
test: /\.tsx?$/,
44+
exclude: /(node_modules|\.webpack)/,
45+
use: {
46+
loader: 'ts-loader',
47+
options: {
48+
transpileOnly: true
49+
}
50+
}
51+
}
52+
];
53+
};
54+
55+
const mainConfig: Configuration = {
56+
/**
57+
* This is the main entry point for your application, it's the first file
58+
* that runs in the main process.
59+
*/
60+
entry: './src/main/index.ts',
61+
// Put your normal webpack config below here
62+
module: {
63+
rules: defaultWebpackRules(),
64+
},
65+
plugins: webpackPlugins,
66+
resolve: {
67+
extensions: ['.js', '.ts', '.jsx', '.tsx', '.css', '.json']
68+
}
69+
};
70+
71+
const rendererConfig: Configuration = {
72+
module: {
73+
rules: [
74+
...defaultWebpackRules(),
75+
{
76+
test: /\.css$/,
77+
use: [{ loader: 'style-loader' }, { loader: 'css-loader' }]
78+
}
79+
],
80+
},
81+
plugins: webpackPlugins,
82+
resolve: {
83+
extensions: ['.js', '.ts', '.jsx', '.tsx', '.css']
84+
}
85+
};
86+
87+
const config: ForgeConfig = {
88+
packagerConfig: {
89+
asar: true
90+
},
91+
rebuildConfig: {},
92+
makers: [
93+
new MakerSquirrel(),
94+
new MakerZIP({}, ['darwin']),
95+
new MakerRpm({ options: { icon: './public/icons/icon' } }),
96+
new MakerDeb({ options: { icon: './public/icons/icon' } })
97+
],
98+
plugins: [
99+
new AutoUnpackNativesPlugin({}),
100+
new WebpackPlugin({
101+
mainConfig,
102+
renderer: {
103+
config: rendererConfig,
104+
entryPoints: [
105+
{
106+
name: 'main_window',
107+
html: './src/render/index.html',
108+
js: './src/render/main.ts'
109+
}
110+
]
111+
}
112+
})
113+
]
114+
};
115+
116+
export default config;
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"name": "example-electron-node",
3+
"version": "1.0.0",
4+
"description": "",
5+
"keywords": [],
6+
"author": "",
7+
"license": "ISC",
8+
"packageManager": "[email protected]",
9+
"main": ".webpack/main",
10+
"scripts": {
11+
"start": "electron-forge start",
12+
"package": "electron-forge package",
13+
"make": "electron-forge make",
14+
"publish": "electron-forge publish"
15+
},
16+
"devDependencies": {
17+
"@electron-forge/cli": "^7.7.0",
18+
"@electron-forge/maker-deb": "^7.7.0",
19+
"@electron-forge/maker-squirrel": "^7.7.0",
20+
"@electron-forge/maker-zip": "^7.7.0",
21+
"@electron-forge/plugin-auto-unpack-natives": "^7.7.0",
22+
"@electron-forge/plugin-webpack": "^7.7.0",
23+
"@vercel/webpack-asset-relocator-loader": "1.7.3",
24+
"css-loader": "^6.11.0",
25+
"electron": "30.0.2",
26+
"fork-ts-checker-webpack-plugin": "^9.0.2",
27+
"node-loader": "^2.1.0",
28+
"style-loader": "^3.3.4",
29+
"ts-loader": "^9.5.2",
30+
"ts-node": "^10.9.2",
31+
"typescript": "^5.8.2",
32+
"webpack": "^5.90.1"
33+
},
34+
"dependencies": {
35+
"@powersync/node": "workspace:*",
36+
"electron-squirrel-startup": "^1.0.1"
37+
}
38+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { Worker } from 'node:worker_threads';
2+
3+
import { PowerSyncDatabase } from '@powersync/node';
4+
import { app, BrowserWindow } from 'electron';
5+
import { AppSchema } from './powersync';
6+
import { default as Logger } from 'js-logger';
7+
8+
const logger = Logger.get('PowerSyncDemo');
9+
Logger.useDefaults({ defaultLevel: logger.WARN });
10+
11+
const database = new PowerSyncDatabase({
12+
schema: AppSchema,
13+
database: {
14+
dbFilename: 'test.db',
15+
openWorker(filename, options) {
16+
return new Worker(new URL('./worker.ts', import.meta.url), options);
17+
},
18+
},
19+
logger
20+
});
21+
22+
// This allows TypeScript to pick up the magic constants that's auto-generated by Forge's Webpack
23+
// plugin that tells the Electron app where to look for the Webpack-bundled app code (depending on
24+
// whether you're running in development or production).
25+
declare const MAIN_WINDOW_WEBPACK_ENTRY: string;
26+
declare const MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY: string;
27+
28+
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
29+
if (require('electron-squirrel-startup')) {
30+
app.quit();
31+
}
32+
33+
const createWindow = (): void => {
34+
// Create the browser window.
35+
const mainWindow = new BrowserWindow({
36+
height: 600,
37+
width: 800,
38+
webPreferences: {
39+
preload: MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY
40+
}
41+
});
42+
43+
// and load the index.html of the app.
44+
mainWindow.loadURL(MAIN_WINDOW_WEBPACK_ENTRY);
45+
46+
// Open the DevTools.
47+
mainWindow.webContents.openDevTools();
48+
};
49+
50+
// This method will be called when Electron has finished
51+
// initialization and is ready to create browser windows.
52+
// Some APIs can only be used after this event occurs.
53+
app.on('ready', createWindow);
54+
55+
// Quit when all windows are closed, except on macOS. There, it's common
56+
// for applications and their menu bar to stay active until the user quits
57+
// explicitly with Cmd + Q.
58+
app.on('window-all-closed', () => {
59+
if (process.platform !== 'darwin') {
60+
app.quit();
61+
}
62+
});
63+
64+
app.on('activate', () => {
65+
// On OS X it's common to re-create a window in the app when the
66+
// dock icon is clicked and there are no other windows open.
67+
if (BrowserWindow.getAllWindows().length === 0) {
68+
createWindow();
69+
}
70+
});
71+
72+
// In this file you can include the rest of your app's specific main process
73+
// code. You can also put them in separate files and import them here.
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { AbstractPowerSyncDatabase, column, PowerSyncBackendConnector, Schema, Table } from '@powersync/node';
2+
3+
export class DemoConnector implements PowerSyncBackendConnector {
4+
async fetchCredentials() {
5+
const response = await fetch(`${process.env.BACKEND}/api/auth/token`);
6+
if (response.status != 200) {
7+
throw 'Could not fetch token';
8+
}
9+
10+
const { token } = await response.json();
11+
12+
return {
13+
endpoint: process.env.SYNC_SERVICE!,
14+
token: token
15+
};
16+
}
17+
18+
async uploadData(database: AbstractPowerSyncDatabase) {
19+
const batch = await database.getCrudBatch();
20+
if (batch == null) {
21+
return;
22+
}
23+
24+
const entries: any[] = [];
25+
for (const op of batch.crud) {
26+
entries.push({
27+
table: op.table,
28+
op: op.op,
29+
id: op.id,
30+
data: op.opData
31+
});
32+
}
33+
34+
const response = await fetch(`${process.env.BACKEND}/api/data/`, {
35+
method: 'POST',
36+
headers: { 'Content-Type': 'application/json' },
37+
body: JSON.stringify({ batch: entries })
38+
});
39+
if (response.status !== 200) {
40+
throw new Error(`Server returned HTTP ${response.status}: ${await response.text()}`);
41+
}
42+
43+
await batch?.complete();
44+
}
45+
}
46+
47+
export const LIST_TABLE = 'lists';
48+
export const TODO_TABLE = 'todos';
49+
50+
const todos = new Table(
51+
{
52+
list_id: column.text,
53+
created_at: column.text,
54+
completed_at: column.text,
55+
description: column.text,
56+
created_by: column.text,
57+
completed_by: column.text,
58+
completed: column.integer,
59+
photo_id: column.text
60+
},
61+
{ indexes: { list: ['list_id'] } }
62+
);
63+
64+
const lists = new Table({
65+
created_at: column.text,
66+
name: column.text,
67+
owner_id: column.text
68+
});
69+
70+
export const AppSchema = new Schema({
71+
lists,
72+
todos
73+
});
74+
75+
export type Database = (typeof AppSchema)['types'];
76+
export type TodoRecord = Database['todos'];
77+
export type ListRecord = Database['lists'];

demos/example-electron-node/src/main/worker.ts

Whitespace-only changes.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<!doctype html>
2+
<html>
3+
<head>
4+
<meta charset="UTF-8" />
5+
<title>Hello World!</title>
6+
</head>
7+
<body>
8+
<h1>Hello World!</h1>
9+
<p>Welcome to your Electron application.</p>
10+
</body>
11+
</html>
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/**
2+
* This file will automatically be loaded by webpack and run in the "renderer" context.
3+
* To learn more about the differences between the "main" and the "renderer" context in
4+
* Electron, visit:
5+
*
6+
* https://electronjs.org/docs/latest/tutorial/process-model
7+
*
8+
* By default, Node.js integration in this file is disabled. When enabling Node.js integration
9+
* in a renderer process, please be aware of potential security implications. You can read
10+
* more about security risks here:
11+
*
12+
* https://electronjs.org/docs/tutorial/security
13+
*
14+
* To enable Node.js integration in this file, open up `main.js` and enable the `nodeIntegration`
15+
* flag:
16+
*
17+
* ```
18+
* // Create the browser window.
19+
* mainWindow = new BrowserWindow({
20+
* width: 800,
21+
* height: 600,
22+
* webPreferences: {
23+
* nodeIntegration: true
24+
* }
25+
* });
26+
* ```
27+
*/
28+
29+
console.log('👋 This message is being logged by "renderer.js", included via webpack');
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"compilerOptions": {
3+
"target": "ES6",
4+
"allowJs": true,
5+
"module": "ESNext",
6+
"skipLibCheck": true,
7+
"noImplicitAny": true,
8+
"sourceMap": true,
9+
"baseUrl": ".",
10+
"outDir": "dist",
11+
"moduleResolution": "bundler",
12+
"paths": {
13+
"*": ["node_modules/*"]
14+
}
15+
},
16+
"include": ["src/**/*"],
17+
"references": [
18+
{
19+
"path": "../../packages/node"
20+
}
21+
]
22+
}
23+

0 commit comments

Comments
 (0)