diff --git a/.gitignore b/.gitignore index 2875d13c..ca750fec 100644 --- a/.gitignore +++ b/.gitignore @@ -7,8 +7,8 @@ node_modules # Rust build folder backend/target -# Obsidian plugin build folder -plugin/dist +obsidian-plugin/dist +sync-client/dist backend/db.sqlite3* backend/config.yml diff --git a/plugin/eslint.config.mjs b/eslint.config.mjs similarity index 82% rename from plugin/eslint.config.mjs rename to eslint.config.mjs index 2c697f4a..96de58af 100644 --- a/plugin/eslint.config.mjs +++ b/eslint.config.mjs @@ -4,10 +4,16 @@ import unusedImports from "eslint-plugin-unused-imports"; export default tseslint.config({ plugins: { - "unused-imports": unusedImports, + "unused-imports": unusedImports }, extends: [eslint.configs.recommended, tseslint.configs.all], - ignores: ["**/types.ts", "**/*.test.ts"], + ignores: [ + "**/types.ts", + "**/*.test.ts", + "**/dist/**/*", + "**/*.mjs", + "**/*.js" + ], rules: { "no-unused-vars": "off", "@typescript-eslint/no-unused-vars": "off", @@ -20,8 +26,8 @@ export default tseslint.config({ "@typescript-eslint/max-params": [ "error", { - max: 5, - }, + max: 5 + } ], "unused-imports/no-unused-imports": "error", "@typescript-eslint/no-magic-numbers": "off", @@ -33,14 +39,14 @@ export default tseslint.config({ vars: "all", varsIgnorePattern: "^_", args: "after-used", - argsIgnorePattern: "^_", - }, - ], + argsIgnorePattern: "^_" + } + ] }, languageOptions: { parserOptions: { projectService: true, - tsconfigRootDir: import.meta.dirname, - }, - }, + tsconfigRootDir: import.meta.dirname + } + } }); diff --git a/obsidian-plugin/manifest.json b/obsidian-plugin/manifest.json new file mode 100644 index 00000000..7b7ca8c8 --- /dev/null +++ b/obsidian-plugin/manifest.json @@ -0,0 +1,10 @@ +{ + "id": "vault-link", + "name": "VaultLink", + "version": "0.0.30", + "minAppVersion": "0.0.0", + "description": "Self-hosted synchronization and collaboration for your Vault.", + "author": "Andras Schmelczer", + "authorUrl": "https://schmelczer.dev", + "isDesktopOnly": false +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..a04f32de --- /dev/null +++ b/package.json @@ -0,0 +1,26 @@ +{ + "name": "my-workspace", + "private": true, + "workspaces": [ + "sync-client", + "obsidian-plugin" + ], + "prettier": { + "trailingComma": "none", + "tabWidth": 4, + "useTabs": true, + "endOfLine": "lf" + }, + "scripts": { + "build": "npm run build --workspaces", + "dev": "npm run dev --workspaces", + "test": "npm run test --workspaces", + "lint": "eslint --fix sync-client obsidian-plugin; prettier --write \"sync-client/**/*.(ts|scss|json|html)\" \"obsidian-plugin/**/*.(ts|scss|json|html)\"" + }, + "devDependencies": { + "prettier": "^3.4.2", + "eslint": "9.17.0", + "typescript-eslint": "8.18.0", + "eslint-plugin-unused-imports": "^4.1.4" + } +} \ No newline at end of file diff --git a/plugin/src/events/file-event-handler.ts b/plugin/src/events/file-event-handler.ts deleted file mode 100644 index 3c6261d2..00000000 --- a/plugin/src/events/file-event-handler.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { TAbstractFile } from "obsidian"; - -export interface FileEventHandler { - onCreate: (path: TAbstractFile) => Promise; - onDelete: (path: TAbstractFile) => Promise; - onRename: (path: TAbstractFile, oldPath: string) => Promise; - onModify: (path: TAbstractFile) => Promise; -} diff --git a/sync-client/package.json b/sync-client/package.json new file mode 100644 index 00000000..bca033d3 --- /dev/null +++ b/sync-client/package.json @@ -0,0 +1,28 @@ +{ + "name": "sync-client", + "version": "1.0.0", + "main": "dist/index.js", + "types": "dist/types/src/index.d.ts", + "scripts": { + "dev": "webpack watch --mode development", + "build": "webpack --mode production", + "test": "NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" jest" + }, + "devDependencies": { + "tslib": "2.4.0", + "typescript": "5.7.2", + "sync_lib": "file:../backend/sync_lib/pkg", + "@types/jest": "^29.5.14", + "@types/node": "^16.11.6", + "jest": "^29.7.0", + "ts-jest": "^29.2.5", + "p-queue": "^8.0.1", + "fetch-retry": "^6.0.0", + "byte-base64": "^1.1.0", + "openapi-fetch": "0.13.3", + "openapi-typescript": "7.4.4", + "ts-loader": "^9.5.1", + "webpack": "^5.97.1", + "webpack-cli": "^6.0.1" + } +} diff --git a/sync-client/src/index.ts b/sync-client/src/index.ts new file mode 100644 index 00000000..219d3c34 --- /dev/null +++ b/sync-client/src/index.ts @@ -0,0 +1,48 @@ +export { applyRemoteChangesLocally } from "./sync-operations/apply-remote-changes-locally"; + +export { + type RelativePath, + type DocumentId, + type VaultUpdateId, + type DocumentMetadata +} from "./database/document-metadata"; + +export { Database } from "./database/database"; + +export { + SyncService, + type CheckConnectionResult +} from "./services/sync-service"; + +export { Syncer } from "./sync-operations/syncer"; + +export { + SyncHistory, + SyncType, + SyncSource, + SyncStatus, + type HistoryStats, + type HistoryEntry +} from "./tracing/sync-history"; + +export { Logger, LogLevel } from "./tracing/logger"; + +export { type FileOperations } from "./file-operations"; + +import init from "sync_lib"; +import wasmBin from "sync_lib/sync_lib_bg.wasm"; + +export const initialize = async (): Promise => { + await init( + // eslint-disable-next-line + (wasmBin as any).default // it is loaded as a base64 string by webpack + ); +}; +export { + isFileTypeMergable, + mergeText, + bytesToBase64, + base64ToBytes, + merge, + isBinary +} from "sync_lib"; diff --git a/plugin/src/sync-operations/syncer.ts b/sync-client/src/sync-operations/syncer.ts similarity index 97% rename from plugin/src/sync-operations/syncer.ts rename to sync-client/src/sync-operations/syncer.ts index dea4b5db..deae7d22 100644 --- a/plugin/src/sync-operations/syncer.ts +++ b/sync-client/src/sync-operations/syncer.ts @@ -1,9 +1,9 @@ -import type { Database } from "src/database/database"; +import type { Database } from "../database/database"; import type { DocumentMetadata, RelativePath } from "src/database/document-metadata"; -import type { FileOperations } from "src/file-operations/file-operations"; +import type { FileOperations } from "src/file-operations"; import type { SyncService } from "src/services/sync-service"; import { Logger } from "src/tracing/logger"; import type { SyncHistory } from "src/tracing/sync-history"; @@ -102,7 +102,7 @@ export class Syncer { try { const allLocalFiles = await this.operations.listAllFiles(); - const locallyDeletedFiles = [ + let locallyDeletedFiles = [ ...this.database.getDocuments().entries() ].filter(([path, _]) => !allLocalFiles.includes(path)); @@ -126,7 +126,10 @@ export class Syncer { ); if (originalFile !== undefined) { // `originalFile` hasn't been deleted but it got moved instead - locallyDeletedFiles.remove(originalFile); + locallyDeletedFiles = + locallyDeletedFiles.filter( + (item) => item != originalFile + ); Logger.getInstance().debug( `Document ${relativePath} was not found under its current path in the database but was found under a different path ${originalFile[0]}, scheduling sync to move it` @@ -231,7 +234,9 @@ export class Syncer { this.history.addHistoryEntry({ status: SyncStatus.ERROR, relativePath, - message: `File size exceeds the maximum file size limit of ${this.database.getSettings().maxFileSizeMB}MB`, + message: `File size exceeds the maximum file size limit of ${ + this.database.getSettings().maxFileSizeMB + }MB`, type: SyncType.CREATE }); return; @@ -332,7 +337,9 @@ export class Syncer { this.history.addHistoryEntry({ status: SyncStatus.ERROR, relativePath, - message: `File size exceeds the maximum file size limit of ${this.database.getSettings().maxFileSizeMB}MB`, + message: `File size exceeds the maximum file size limit of ${ + this.database.getSettings().maxFileSizeMB + }MB`, type: SyncType.CREATE }); return; diff --git a/sync-client/tsconfig.json b/sync-client/tsconfig.json new file mode 100644 index 00000000..63ebf589 --- /dev/null +++ b/sync-client/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "composite": true, + "baseUrl": ".", + "target": "ES2022", + "noImplicitAny": true, + "moduleResolution": "node", + "strictNullChecks": true, + "esModuleInterop": true, + "lib": [ + "DOM", + "ESNext" + ] + } +} \ No newline at end of file diff --git a/sync-client/webpack.config.js b/sync-client/webpack.config.js new file mode 100644 index 00000000..144cb7ae --- /dev/null +++ b/sync-client/webpack.config.js @@ -0,0 +1,49 @@ +const path = require("path"); + +module.exports = (_env, _argv) => ({ + entry: "./src/index.ts", + devtool: "source-map", + module: { + rules: [ + { + test: /\.ts$/, + use: [ + { + loader: "ts-loader", + options: { + compilerOptions: { + declaration: true, + declarationDir: "./dist/types" + }, + transpileOnly: false + } + } + ] + }, + { + test: /\.wasm$/, + type: "asset/inline" + } + ] + }, + optimization: { + minimize: false + }, + resolve: { + extensions: [".ts", ".js"], + alias: { + root: __dirname, + src: path.resolve(__dirname, "src") + } + }, + output: { + clean: true, + filename: "index.js", + library: { + name: "SyncClient", + type: "umd" + }, + globalObject: "this", + path: path.resolve(__dirname, "dist") + } +});