Switch to npm workspaces and electron forge

This commit is contained in:
Hakan Shehu
2024-11-16 16:55:45 +01:00
parent 22b7e39c8e
commit 184851403e
39 changed files with 18152 additions and 13037 deletions

View File

@@ -3,5 +3,6 @@ out
tsconfig.base.json
tsconfig.json
pnpm-lock.yaml
package-lock.json
.env
LICENSE.md

View File

@@ -1,4 +0,0 @@
node_modules
dist
out
.gitignore

View File

@@ -1,9 +0,0 @@
module.exports = {
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:react/jsx-runtime',
'@electron-toolkit/eslint-config-ts/recommended',
'@electron-toolkit/eslint-config-prettier',
],
};

View File

@@ -0,0 +1,16 @@
{
"env": {
"browser": true,
"es6": true,
"node": true
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"plugin:import/recommended",
"plugin:import/electron",
"plugin:import/typescript"
],
"parser": "@typescript-eslint/parser"
}

View File

@@ -1,3 +0,0 @@
electron_mirror=https://npmmirror.com/mirrors/electron/
electron_builder_binaries_mirror=https://npmmirror.com/mirrors/electron-builder-binaries/
shamefully-hoist=true

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

View File

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
</dict>
</plist>

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

View File

@@ -1,3 +0,0 @@
provider: generic
url: https://example.com/auto-updates
updaterCacheDirName: colanode-updater

View File

@@ -1,54 +0,0 @@
{
"appId": "com.colanode.desktop",
"productName": "Colanode",
"directories": {
"buildResources": "build"
},
"files": [
"!**/.vscode/*",
"!src/*",
"!electron.vite.config.{js,ts,mjs,cjs}",
"!{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}",
"!{.env,.env.*,.npmrc,pnpm-lock.yaml}",
"!{tsconfig.json,tsconfig.node.json,tsconfig.web.json}"
],
"asarUnpack": ["resources/**"],
"win": {
"executableName": "Colanode",
"nsis": {
"artifactName": "${name}-${version}-setup.${ext}",
"shortcutName": "${productName}",
"uninstallDisplayName": "${productName}",
"createDesktopShortcut": "always"
}
},
"mac": {
"entitlementsInherit": "build/entitlements.mac.plist",
"extendInfo": {
"NSCameraUsageDescription": "Application requests access to the device's camera.",
"NSMicrophoneUsageDescription": "Application requests access to the device's microphone.",
"NSDocumentsFolderUsageDescription": "Application requests access to the user's Documents folder.",
"NSDownloadsFolderUsageDescription": "Application requests access to the user's Downloads folder."
},
"notarize": false
},
"dmg": {
"artifactName": "${name}-${version}.${ext}"
},
"linux": {
"target": ["AppImage", "snap", "deb"],
"maintainer": "electronjs.org",
"category": "Utility"
},
"appImage": {
"artifactName": "${name}-${version}.${ext}"
},
"npmRebuild": false,
"publish": {
"provider": "generic",
"url": "https://example.com/auto-updates"
},
"electronDownload": {
"mirror": "https://npmmirror.com/mirrors/electron/"
}
}

View File

@@ -1,49 +0,0 @@
import { resolve } from 'path';
import { defineConfig, externalizeDepsPlugin } from 'electron-vite';
import react from '@vitejs/plugin-react';
import { viteStaticCopy } from 'vite-plugin-static-copy';
export default defineConfig({
main: {
plugins: [
externalizeDepsPlugin({
exclude: ['@colanode/core', '@colanode/crdt'],
}),
viteStaticCopy({
targets: [
{
src: 'assets/**/*',
dest: 'assets',
},
],
}),
],
resolve: {
alias: {
'@/main': resolve('src/main'),
'@/shared': resolve('src/shared'),
},
},
},
preload: {
plugins: [
externalizeDepsPlugin({
exclude: ['@colanode/core', '@colanode/crdt'],
}),
],
resolve: {
alias: {
'@/shared': resolve('src/shared'),
},
},
},
renderer: {
resolve: {
alias: {
'@/renderer': resolve('src/renderer'),
'@/shared': resolve('src/shared'),
},
},
plugins: [react()],
},
});

View File

@@ -0,0 +1,59 @@
import type { ForgeConfig } from '@electron-forge/shared-types';
import { MakerSquirrel } from '@electron-forge/maker-squirrel';
import { MakerZIP } from '@electron-forge/maker-zip';
import { MakerDeb } from '@electron-forge/maker-deb';
import { MakerRpm } from '@electron-forge/maker-rpm';
import { VitePlugin } from '@electron-forge/plugin-vite';
import { FusesPlugin } from '@electron-forge/plugin-fuses';
import { FuseV1Options, FuseVersion } from '@electron/fuses';
const config: ForgeConfig = {
packagerConfig: {
asar: true,
},
rebuildConfig: {},
makers: [
new MakerSquirrel({}),
new MakerZIP({}, ['darwin']),
new MakerRpm({}),
new MakerDeb({}),
],
plugins: [
new VitePlugin({
// `build` can specify multiple entry builds, which can be Main process, Preload scripts, Worker process, etc.
// If you are familiar with Vite configuration, it will look really familiar.
build: [
{
// `entry` is just an alias for `build.lib.entry` in the corresponding file of `config`.
entry: 'src/main.ts',
config: 'vite.main.config.ts',
target: 'main',
},
{
entry: 'src/preload.ts',
config: 'vite.preload.config.ts',
target: 'preload',
},
],
renderer: [
{
name: 'main_window',
config: 'vite.renderer.config.ts',
},
],
}),
// Fuses are used to enable/disable various Electron functionality
// at package time, before code signing the application
new FusesPlugin({
version: FuseVersion.V1,
[FuseV1Options.RunAsNode]: false,
[FuseV1Options.EnableCookieEncryption]: true,
[FuseV1Options.EnableNodeOptionsEnvironmentVariable]: false,
[FuseV1Options.EnableNodeCliInspectArguments]: false,
[FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: true,
[FuseV1Options.OnlyLoadAppFromAsar]: true,
}),
],
};
export default config;

4
apps/desktop/forge.env.d.ts vendored Normal file
View File

@@ -0,0 +1,4 @@
/// <reference types="@electron-forge/plugin-vite/forge-vite-env" />
declare const MAIN_WINDOW_VITE_DEV_SERVER_URL: string;
declare const MAIN_WINDOW_VITE_NAME: string;

17
apps/desktop/index.html Normal file
View File

@@ -0,0 +1,17 @@
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<title>Colanode</title>
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self' 'unsafe-inline' data: avatar: local-file: local-file-preview: asset:;"
/>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/renderer.ts"></script>
</body>
</html>

View File

@@ -3,29 +3,49 @@
"productName": "Colanode",
"version": "1.0.0",
"description": "Colanode desktop application",
"main": "./out/main/index.js",
"author": "Hakan Shehu",
"main": ".vite/build/main.js",
"scripts": {
"format": "prettier --write .",
"lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix",
"typecheck:node": "tsc --noEmit -p tsconfig.node.json --composite false",
"typecheck:web": "tsc --noEmit -p tsconfig.web.json --composite false",
"typecheck": "pnpm run typecheck:node && pnpm run typecheck:web",
"start": "electron-vite preview",
"dev": "electron-vite dev --watch",
"build": "pnpm run typecheck && electron-vite build",
"postinstall": "electron-builder install-app-deps",
"build:unpack": "pnpm run build && electron-builder --dir",
"build:win": "pnpm run build && electron-builder --win",
"build:mac": "electron-vite build && electron-builder --mac",
"build:linux": "electron-vite build && electron-builder --linux"
"dev": "electron-forge start",
"package": "electron-forge package",
"make": "electron-forge make",
"publish": "electron-forge publish",
"lint": "eslint --ext .ts,.tsx .",
"postinstall": "electron-rebuild"
},
"devDependencies": {
"@electron-forge/cli": "^7.5.0",
"@electron-forge/maker-deb": "^7.5.0",
"@electron-forge/maker-rpm": "^7.5.0",
"@electron-forge/maker-squirrel": "^7.5.0",
"@electron-forge/maker-zip": "^7.5.0",
"@electron-forge/plugin-auto-unpack-natives": "^7.5.0",
"@electron-forge/plugin-fuses": "^7.5.0",
"@electron-forge/plugin-vite": "^7.5.0",
"@electron/fuses": "^1.8.0",
"@types/better-sqlite3": "^7.6.11",
"@types/is-hotkey": "^0.1.10",
"@types/mime-types": "^2.1.4",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@types/react-window": "^1.8.8",
"@types/unzipper": "^0.10.10",
"autoprefixer": "^10.4.20",
"electron": "33.2.0",
"postcss": "^8.4.49",
"tailwindcss": "^3.4.15",
"vite": "^5.4.11",
"vite-plugin-static-copy": "^2.1.0"
},
"keywords": [],
"author": {
"name": "Hakan Shehu",
"email": "***REMOVED***"
},
"license": "MIT",
"dependencies": {
"@colanode/core": "workspace:*",
"@colanode/crdt": "workspace:*",
"@electron-toolkit/preload": "^3.0.1",
"@electron-toolkit/utils": "^3.0.0",
"@hookform/resolvers": "^3.9.0",
"@colanode/core": "*",
"@colanode/crdt": "*",
"@hookform/resolvers": "^3.9.1",
"@radix-ui/react-alert-dialog": "^1.1.2",
"@radix-ui/react-checkbox": "^1.1.2",
"@radix-ui/react-collapsible": "^1.1.1",
@@ -33,18 +53,16 @@
"@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-dropdown-menu": "^2.1.2",
"@radix-ui/react-hover-card": "^1.1.2",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-icons": "^1.3.2",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-popover": "^1.1.2",
"@radix-ui/react-scroll-area": "^1.2.0",
"@radix-ui/react-scroll-area": "^1.2.1",
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-tabs": "^1.1.1",
"@radix-ui/react-toast": "^1.2.2",
"@radix-ui/react-tooltip": "^1.1.3",
"@radix-ui/react-tooltip": "^1.1.4",
"@radix-ui/react-visually-hidden": "^1.1.0",
"@react-oauth/google": "^0.12.1",
"@tanstack/react-query": "^5.59.20",
"@tanstack/react-query": "^5.60.5",
"@tiptap/core": "^2.9.1",
"@tiptap/extension-blockquote": "^2.9.1",
"@tiptap/extension-bold": "^2.9.1",
@@ -53,9 +71,6 @@
"@tiptap/extension-code-block-lowlight": "^2.9.1",
"@tiptap/extension-document": "^2.9.1",
"@tiptap/extension-dropcursor": "^2.9.1",
"@tiptap/extension-hard-break": "^2.9.1",
"@tiptap/extension-heading": "^2.9.1",
"@tiptap/extension-highlight": "^2.9.1",
"@tiptap/extension-horizontal-rule": "^2.9.1",
"@tiptap/extension-italic": "^2.9.1",
"@tiptap/extension-link": "^2.9.1",
@@ -65,84 +80,38 @@
"@tiptap/extension-paragraph": "^2.9.1",
"@tiptap/extension-placeholder": "^2.9.1",
"@tiptap/extension-strike": "^2.9.1",
"@tiptap/extension-subscript": "^2.9.1",
"@tiptap/extension-superscript": "^2.9.1",
"@tiptap/extension-task-item": "^2.9.1",
"@tiptap/extension-task-list": "^2.9.1",
"@tiptap/extension-text": "^2.9.1",
"@tiptap/extension-text-style": "^2.9.1",
"@tiptap/extension-underline": "^2.9.1",
"@tiptap/pm": "^2.9.1",
"@tiptap/react": "^2.9.1",
"@tiptap/suggestion": "^2.9.1",
"axios": "^1.7.7",
"better-sqlite3": "^11.5.0",
"bufferutil": "^4.0.8",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"cmdk": "^1.0.0",
"date-fns": "^3.6.0",
"electron-updater": "^6.1.7",
"form-data": "^4.0.1",
"cmdk": "^1.0.4",
"electron-squirrel-startup": "^1.0.1",
"fractional-indexing-jittered": "^0.9.1",
"is-hotkey": "^0.2.0",
"js-sha256": "^0.11.0",
"kysely": "^0.27.4",
"lodash": "^4.17.21",
"lowlight": "^3.1.0",
"lucide-react": "^0.453.0",
"lucide-react": "^0.460.0",
"mime-types": "^2.1.35",
"public-ip": "^7.0.1",
"re-resizable": "^6.10.0",
"re-resizable": "^6.10.1",
"react": "^18.3.1",
"react-day-picker": "^8.10.1",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dom": "^18.3.1",
"react-hook-form": "^7.53.1",
"react-hook-form": "^7.53.2",
"react-intersection-observer": "^9.13.1",
"react-router-dom": "^6.27.0",
"react-router-dom": "^6.28.0",
"react-virtualized-auto-sizer": "^1.0.24",
"react-window": "^1.8.10",
"tailwind-merge": "^2.5.4",
"tailwindcss-animate": "^1.0.7",
"ts-pattern": "^5.5.0",
"unzipper": "^0.12.3",
"ws": "^8.18.0"
},
"devDependencies": {
"@electron-toolkit/eslint-config-prettier": "^2.0.0",
"@electron-toolkit/eslint-config-ts": "^2.0.0",
"@electron-toolkit/tsconfig": "^1.0.1",
"@electron/fuses": "^1.8.0",
"@types/better-sqlite3": "^7.6.11",
"@types/diff": "^5.2.3",
"@types/is-hotkey": "^0.1.10",
"@types/lodash": "^4.17.12",
"@types/mime-types": "^2.1.4",
"@types/node": "^20.14.8",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@types/react-window": "^1.8.8",
"@types/unzipper": "^0.10.10",
"@types/ws": "^8.5.12",
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.62.0",
"@vitejs/plugin-react": "^4.3.1",
"autoprefixer": "^10.4.20",
"electron": "^31.0.2",
"electron-builder": "^24.13.3",
"electron-vite": "^2.3.0",
"eslint": "^8.57.0",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-react": "^7.34.3",
"node-abi": "^3.71.0",
"postcss": "^8.4.47",
"prettier": "^3.3.2",
"prettier-plugin-tailwindcss": "^0.6.8",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"tailwindcss": "^3.4.14",
"vite": "^5.3.1",
"vite-plugin-static-copy": "^2.0.0"
"utf-8-validate": "^5.0.10"
}
}

View File

@@ -1,28 +1,31 @@
import { app, shell, BrowserWindow, ipcMain, protocol } from 'electron';
import { join } from 'path';
import { electronApp, optimizer, is } from '@electron-toolkit/utils';
import { app, BrowserWindow, ipcMain, protocol } from 'electron';
import path from 'path';
import { eventBus } from '@/shared/lib/event-bus';
import { avatarService } from '@/main/services/avatar-service';
import { fileService } from '@/main/services/file-service';
import { assetService } from '@/main/services/asset-service';
import { MutationMap } from '@/shared/mutations';
import { MutationInput } from '@/shared/mutations';
import { QueryMap } from '@/shared/queries';
import { mutationService } from '@/main/services/mutation-service';
import { queryService } from '@/main/services/query-service';
import { QueryInput } from '@/shared/queries';
import { CommandMap } from '@/shared/commands';
import { CommandInput } from '@/shared/commands';
import { commandService } from '@/main/services/command-service';
import { databaseService } from '@/main/data/database-service';
import { socketService } from '@/main/services/socket-service';
import { syncService } from '@/main/services/sync-service';
import { avatarService } from '@/main/services/avatar-service';
import { fileService } from '@/main/services/file-service';
import { MutationInput, MutationMap } from '@/shared/mutations';
import { QueryInput, QueryMap } from '@/shared/queries';
import { assetService } from '@/main/services/asset-service';
import { radarService } from '@/main/services/radar-service';
import { mutationService } from '@/main/services/mutation-service';
import { queryService } from '@/main/services/query-service';
import { CommandInput } from '@/shared/commands';
import { commandService } from '@/main/services/command-service';
import { CommandMap } from '@/shared/commands';
let subscriptionId: string | null = null;
const icon = join(__dirname, '../assets/icon.png');
app.setName('Colanode');
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
if (require('electron-squirrel-startup')) {
app.quit();
}
const createWindow = async (): Promise<void> => {
const createWindow = async () => {
await databaseService.init();
assetService.checkAssets();
socketService.init();
@@ -32,31 +35,20 @@ const createWindow = async (): Promise<void> => {
// Create the browser window.
const mainWindow = new BrowserWindow({
fullscreen: true,
show: false,
autoHideMenuBar: true,
...(process.platform === 'linux' ? { icon } : {}),
webPreferences: {
preload: join(__dirname, '../preload/index.js'),
sandbox: false,
preload: path.join(__dirname, 'preload.js'),
},
});
mainWindow.on('ready-to-show', () => {
mainWindow.show();
});
mainWindow.webContents.setWindowOpenHandler((details) => {
shell.openExternal(details.url);
return { action: 'deny' };
});
// HMR for renderer base on electron-vite cli.
// Load the remote URL for development or the local html file for production.
const electronRendererUrl = process.env['ELECTRON_RENDERER_URL'];
if (is.dev && electronRendererUrl) {
mainWindow.loadURL(electronRendererUrl);
// and load the index.html of the app.
if (MAIN_WINDOW_VITE_DEV_SERVER_URL) {
mainWindow.loadURL(MAIN_WINDOW_VITE_DEV_SERVER_URL);
// Open the DevTools.
mainWindow.webContents.openDevTools();
} else {
mainWindow.loadFile(join(__dirname, '../renderer/index.html'));
mainWindow.loadFile(
path.join(__dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`)
);
}
if (subscriptionId === null) {
@@ -95,45 +87,32 @@ const createWindow = async (): Promise<void> => {
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(() => {
// Set app user model id for windows
electronApp.setAppUserModelId('com.colanode.desktop');
// Default open or close DevTools by F12 in development
// and ignore CommandOrControl + R in production.
// see https://github.com/alex8088/electron-toolkit/tree/master/packages/utils
app.on('browser-window-created', (_, window) => {
optimizer.watchWindowShortcuts(window);
});
// IPC test
ipcMain.on('ping', () => console.log('pong'));
createWindow();
app.on('activate', function () {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
});
app.on('ready', createWindow);
// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', () => {
if (subscriptionId) {
eventBus.unsubscribe(subscriptionId);
subscriptionId = null;
}
if (process.platform !== 'darwin') {
if (subscriptionId) {
eventBus.unsubscribe(subscriptionId);
subscriptionId = null;
}
app.quit();
}
});
// In this file you can include the rest of your app"s specific main process
// code. You can also put them in separate files and require them here.
app.on('activate', () => {
// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and import them here.
ipcMain.handle(
'execute-mutation',
async <T extends MutationInput>(

View File

@@ -6,6 +6,7 @@ import { appDatabaseMigrations } from '@/main/data/app/migrations';
import { workspaceDatabaseMigrations } from '@/main/data/workspace/migrations';
import { appDatabasePath, getWorkspaceDirectoryPath } from '@/main/utils';
import SQLite from 'better-sqlite3';
class DatabaseService {
private initPromise: Promise<void> | null = null;
private readonly workspaceDatabases: Map<

View File

@@ -45,17 +45,17 @@ class NodeService {
await databaseService.getWorkspaceDatabase(userId);
await workspaceDatabase.transaction().execute(async (transaction) => {
for (const input of inputs) {
const model = registry.getModel(input.attributes.type);
if (!model.schema.safeParse(input.attributes).success) {
for (const inputItem of inputs) {
const model = registry.getModel(inputItem.attributes.type);
if (!model.schema.safeParse(inputItem.attributes).success) {
throw new Error('Invalid attributes');
}
let ancestors: Node[] = [];
if (input.attributes.parentId) {
if (inputItem.attributes.parentId) {
const ancestorRows = await fetchNodeAncestors(
transaction,
input.attributes.parentId
inputItem.attributes.parentId
);
ancestors = ancestorRows.map(mapNode);
}
@@ -68,19 +68,19 @@ class NodeService {
ancestors
);
if (!model.canCreate(context, input.attributes)) {
if (!model.canCreate(context, inputItem.attributes)) {
throw new Error('Insufficient permissions');
}
const ydoc = new YDoc(input.id);
ydoc.updateAttributes(input.attributes);
const ydoc = new YDoc(inputItem.id);
ydoc.updateAttributes(inputItem.attributes);
const createdAt = new Date().toISOString();
const versionId = generateId(IdType.Version);
const changeData: LocalCreateNodeChangeData = {
type: 'node_create',
id: input.id,
id: inputItem.id,
state: ydoc.getEncodedState(),
createdAt: createdAt,
createdBy: context.userId,
@@ -91,8 +91,8 @@ class NodeService {
.insertInto('nodes')
.returningAll()
.values({
id: input.id,
attributes: JSON.stringify(input.attributes),
id: inputItem.id,
attributes: JSON.stringify(inputItem.attributes),
state: ydoc.getState(),
created_at: createdAt,
created_by: context.userId,
@@ -119,11 +119,11 @@ class NodeService {
createdChangeIds.push(createdChange.id);
}
if (input.upload) {
if (inputItem.upload) {
const createdUploadRow = await transaction
.insertInto('uploads')
.returningAll()
.values(input.upload)
.values(inputItem.upload)
.executeTakeFirst();
if (createdUploadRow) {
@@ -137,11 +137,11 @@ class NodeService {
}
}
if (input.download) {
if (inputItem.download) {
const createdDownloadRow = await transaction
.insertInto('downloads')
.returningAll()
.values(input.download)
.values(inputItem.download)
.executeTakeFirst();
if (createdDownloadRow) {

View File

@@ -44,7 +44,7 @@ class QueryService {
}
private async checkForQueryChanges(event: Event): Promise<void> {
if (event.type !== 'query_result_updated') {
if (event.type === 'query_result_updated') {
return;
}

View File

@@ -3,7 +3,7 @@ import { workspaceService } from '@/main/services/workspace-service';
import { deviceService } from '@/main/services/device-service';
// one minute
const EVENT_LOOP_INTERVAL = 1000 * 5;
const EVENT_LOOP_INTERVAL = 1000 * 60;
class SyncService {
private initiated: boolean = false;

View File

@@ -0,0 +1,54 @@
// See the Electron documentation for details on how to use preload scripts:
// https://www.electronjs.org/docs/latest/tutorial/process-model#preload-scripts
import { CommandMap } from '@/shared/commands';
import { CommandInput } from '@/shared/commands';
import { MutationMap } from '@/shared/mutations';
import { MutationInput } from '@/shared/mutations';
import { QueryInput, QueryMap } from '@/shared/queries';
import { contextBridge, ipcRenderer } from 'electron';
import { eventBus } from '@/shared/lib/event-bus';
import { Event } from '@/shared/types/events';
contextBridge.exposeInMainWorld('colanode', {
init: () => ipcRenderer.invoke('init'),
logout: (accountId: string) => ipcRenderer.invoke('logout', accountId),
executeMutation: <T extends MutationInput>(
input: T
): Promise<MutationMap[T['type']]['output']> => {
return ipcRenderer.invoke('execute-mutation', input);
},
executeQuery: <T extends QueryInput>(
input: T
): Promise<QueryMap[T['type']]['output']> => {
return ipcRenderer.invoke('execute-query', input);
},
executeQueryAndSubscribe: <T extends QueryInput>(
id: string,
input: T
): Promise<QueryMap[T['type']]['output']> => {
return ipcRenderer.invoke('execute-query-and-subscribe', id, input);
},
unsubscribeQuery: (id: string): Promise<void> => {
return ipcRenderer.invoke('unsubscribe-query', id);
},
executeCommand: <T extends CommandInput>(
input: T
): Promise<CommandMap[T['type']]['output']> => {
return ipcRenderer.invoke('execute-command', input);
},
});
contextBridge.exposeInMainWorld('eventBus', {
subscribe: (callback: (event: Event) => void) => eventBus.subscribe(callback),
unsubscribe: (id: string) => eventBus.unsubscribe(id),
publish: (event: Event) => eventBus.publish(event),
});
ipcRenderer.on('event', (_, event) => {
eventBus.publish(event);
});

View File

@@ -1,68 +0,0 @@
import { contextBridge, ipcRenderer } from 'electron';
import { electronAPI } from '@electron-toolkit/preload';
import { eventBus } from '@/shared/lib/event-bus';
import { MutationInput, MutationMap } from '@/shared/mutations';
import { QueryInput, QueryMap } from '@/shared/queries';
import { Event } from '@/shared/types/events';
import { CommandMap } from '@/shared/commands';
import { CommandInput } from '@/shared/commands';
// Use `contextBridge` APIs to expose Electron APIs to
// renderer only if context isolation is enabled, otherwise
// just add to the DOM global.
if (process.contextIsolated) {
try {
contextBridge.exposeInMainWorld('electron', electronAPI);
contextBridge.exposeInMainWorld('colanode', {
init: () => ipcRenderer.invoke('init'),
logout: (accountId: string) => ipcRenderer.invoke('logout', accountId),
executeMutation: <T extends MutationInput>(
input: T
): Promise<MutationMap[T['type']]['output']> => {
return ipcRenderer.invoke('execute-mutation', input);
},
executeQuery: <T extends QueryInput>(
input: T
): Promise<QueryMap[T['type']]['output']> => {
return ipcRenderer.invoke('execute-query', input);
},
executeQueryAndSubscribe: <T extends QueryInput>(
id: string,
input: T
): Promise<QueryMap[T['type']]['output']> => {
return ipcRenderer.invoke('execute-query-and-subscribe', id, input);
},
unsubscribeQuery: (id: string): Promise<void> => {
return ipcRenderer.invoke('unsubscribe-query', id);
},
executeCommand: <T extends CommandInput>(
input: T
): Promise<CommandMap[T['type']]['output']> => {
return ipcRenderer.invoke('execute-command', input);
},
});
contextBridge.exposeInMainWorld('eventBus', {
subscribe: (callback: (event: Event) => void) =>
eventBus.subscribe(callback),
unsubscribe: (id: string) => eventBus.unsubscribe(id),
publish: (event: Event) => eventBus.publish(event),
});
ipcRenderer.on('event', (_, event) => {
eventBus.publish(event);
});
} catch (error) {
console.error(error);
}
} else {
// @ts-ignore (define in dts)
window.electron = electronAPI;
// @ts-ignore (define in dts)
window.api = api;
}

View File

@@ -0,0 +1 @@
import './renderer/root';

View File

@@ -1 +0,0 @@
/// <reference types="vite/client" />

View File

@@ -1,28 +1,23 @@
import { ElectronAPI } from '@electron-toolkit/preload';
import { EventBus } from '@/lib/event-bus';
import { MutationMap, MutationInput } from '@/shared/mutations';
import { QueryMap, QueryInput } from '@/shared/queries';
import { CommandMap, CommandInput } from '@/shared/commands';
import { CommandMap } from '@/shared/commands';
import { QueryInput } from '@/shared/queries';
import { MutationInput, MutationMap } from '@/shared/mutations';
import { QueryMap } from '@/shared/queries';
import { CommandInput } from '@/shared/commands';
import { EventBus } from '@/shared/lib/event-bus';
interface ColanodeAPI {
export interface ColanodeApi {
init: () => Promise<void>;
logout: (accountId: string) => Promise<void>;
executeMutation: <T extends MutationInput>(
input: T
) => Promise<MutationMap[T['type']]['output']>;
executeQuery: <T extends QueryInput>(
input: T
) => Promise<QueryMap[T['type']]['output']>;
executeQueryAndSubscribe: <T extends QueryInput>(
id: string,
input: T
) => Promise<QueryMap[T['type']]['output']>;
unsubscribeQuery: (id: string) => Promise<void>;
executeCommand: <T extends CommandInput>(
input: T
) => Promise<CommandMap[T['type']]['output']>;
@@ -30,8 +25,7 @@ interface ColanodeAPI {
declare global {
interface Window {
electron: ElectronAPI;
colanode: ColanodeAPI;
colanode: ColanodeApi;
eventBus: EventBus;
}
}

View File

@@ -1,9 +1,23 @@
{
"files": [],
"compilerOptions": {
"target": "ESNext",
"module": "commonjs",
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"noImplicitAny": true,
"sourceMap": true,
"baseUrl": ".",
"outDir": "dist",
"moduleResolution": "node",
"resolveJsonModule": true,
"jsx": "react-jsx",
"paths": {
"@/*": ["src/*"]
}
},
"references": [
{ "path": "../../packages/core/tsconfig.json" },
{ "path": "../../packages/crdt/tsconfig.json" },
{ "path": "./tsconfig.node.json" },
{ "path": "./tsconfig.web.json" }
{ "path": "../../packages/crdt/tsconfig.json" }
]
}

View File

@@ -1,17 +0,0 @@
{
"extends": "@electron-toolkit/tsconfig/tsconfig.node.json",
"include": [
"electron.vite.config.*",
"src/main/**/*",
"src/preload/**/*",
"src/shared/**/*"
],
"compilerOptions": {
"composite": true,
"types": ["electron-vite/node"],
"paths": {
"@/shared/*": ["./src/shared/*"],
"@/main/*": ["./src/main/*"]
}
}
}

View File

@@ -1,17 +0,0 @@
{
"extends": "@electron-toolkit/tsconfig/tsconfig.web.json",
"include": [
"src/renderer/env.d.ts",
"src/renderer/**/*",
"src/preload/*.d.ts",
"src/shared/**/*"
],
"compilerOptions": {
"composite": true,
"jsx": "react-jsx",
"paths": {
"@/renderer/*": ["./src/renderer/*"],
"@/shared/*": ["./src/shared/*"]
}
}
}

View File

@@ -0,0 +1,35 @@
import { defineConfig } from 'vite';
// https://vitejs.dev/config
export default defineConfig(async () => {
const { viteStaticCopy } = await import('vite-plugin-static-copy');
return {
resolve: {
alias: {
'@': '/src',
},
},
build: {
rollupOptions: {
external: [
'better-sqlite3',
'kysely',
'unzipper',
'mime-types',
'node-gyp-build',
'asynckit',
],
},
},
plugins: [
viteStaticCopy({
targets: [
{
src: 'assets/**/*',
dest: 'assets',
},
],
}),
],
};
});

View File

@@ -0,0 +1,10 @@
import { defineConfig } from 'vite';
// https://vitejs.dev/config
export default defineConfig({
resolve: {
alias: {
'@': '/src',
},
},
});

View File

@@ -0,0 +1,10 @@
import { defineConfig } from 'vite';
// https://vitejs.dev/config
export default defineConfig({
resolve: {
alias: {
'@': '/src',
},
},
});

View File

@@ -10,7 +10,7 @@
],
"scripts": {
"compile": "tsc --noEmit -p tsconfig.json",
"build": "pnpm run compile && tsup-node",
"build": "npm run compile && tsup-node",
"clean": "del-cli dist isolate tsconfig.tsbuildinfo",
"lint": "eslint . --max-warnings 0",
"dev": "nodemon --env-file .env dist/index.js"
@@ -32,8 +32,8 @@
"dependencies": {
"@aws-sdk/client-s3": "^3.675.0",
"@aws-sdk/s3-request-presigner": "^3.675.0",
"@colanode/core": "workspace:*",
"@colanode/crdt": "workspace:*",
"@colanode/core": "^1.0.0",
"@colanode/crdt": "^1.0.0",
"axios": "^1.7.7",
"bcrypt": "^5.1.1",
"bullmq": "^5.21.1",

17797
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,10 @@
"description": "Colanode monorepo",
"version": "1.0.0",
"private": true,
"packageManager": "pnpm@9.0.4+sha256.caa915eaae9d9aefccf50ee8aeda25a2f8684d8f9d5c6e367eaf176d97c1f89e",
"workspaces": [
"packages/*",
"apps/*"
],
"author": "Hakan Shehu",
"repository": {
"type": "git",

View File

@@ -24,7 +24,7 @@
"vitest": "^1.6.0"
},
"dependencies": {
"@colanode/core": "workspace:*",
"@colanode/core": "^1.0.0",
"diff": "^7.0.0",
"js-base64": "^3.7.7",
"yjs": "^13.6.20",

12609
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +0,0 @@
packages:
- "apps/*"
- "packages/*"