mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-07-21 12:06:05 +00:00
refactor: align platform APIs with community review
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"extends": "../../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"paths": {
|
||||
"@/*": ["../../*"]
|
||||
}
|
||||
},
|
||||
"include": ["**/*.ts", "**/*.svelte"],
|
||||
"exclude": ["**/*.unit.spec.ts"]
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import type { MenuItem } from "../BrowserMenu";
|
||||
import type { MenuItem } from "@/apps/browser/BrowserMenu";
|
||||
|
||||
type Props = {
|
||||
item: MenuItem;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import type { MenuSeparator } from "../BrowserMenu";
|
||||
import type { MenuSeparator } from "@/apps/browser/BrowserMenu";
|
||||
|
||||
type Props = {
|
||||
item: MenuSeparator;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import type { Menu, MenuItem, MenuSeparator } from "../BrowserMenu";
|
||||
import type { Menu, MenuItem, MenuSeparator } from "@/apps/browser/BrowserMenu";
|
||||
import MenuItemView from "./MenuItemView.svelte";
|
||||
import MenuSeparatorView from "./MenuSeparatorView.svelte";
|
||||
|
||||
|
||||
@@ -31,7 +31,6 @@ export class NodeVaultAdapter implements IVaultAdapter<NodeFile> {
|
||||
const buffer = await fs.readFile(this.resolvePath(file.path));
|
||||
// Same correction as read() — ensure stat.size matches actual byte length.
|
||||
file.stat.size = buffer.length;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion -- required in environments where Buffer.buffer is ArrayBufferLike
|
||||
return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength) as ArrayBuffer;
|
||||
}
|
||||
|
||||
|
||||
@@ -91,3 +91,7 @@ export const VALID_COMMANDS = new Set([
|
||||
"remote-status",
|
||||
"init-settings",
|
||||
] as const);
|
||||
|
||||
export function isCLICommand(value: string): value is CLICommand {
|
||||
return (VALID_COMMANDS as ReadonlySet<string>).has(value);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { path, readline } from "@vrtmrz/livesync-commonlib/node";
|
||||
|
||||
export function toArrayBuffer(data: Buffer): ArrayBuffer {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion -- required in environments where Buffer.buffer is ArrayBufferLike
|
||||
return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength) as ArrayBuffer;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
#!/usr/bin/env node
|
||||
// eslint-disable -- This is the entry point for the CLI application.
|
||||
import * as polyfill from "werift";
|
||||
import { RTCPeerConnection } from "werift";
|
||||
import { main } from "./main";
|
||||
import { compatGlobal } from "@vrtmrz/livesync-commonlib/compat/common/coreEnvFunctions";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Polyfill
|
||||
const rtcPolyfillCtor = (polyfill as any).RTCPeerConnection;
|
||||
if (
|
||||
typeof (compatGlobal as unknown as Record<string, unknown>).RTCPeerConnection === "undefined" &&
|
||||
typeof rtcPolyfillCtor === "function"
|
||||
typeof RTCPeerConnection === "function"
|
||||
) {
|
||||
// Fill only the standard WebRTC global in Node CLI runtime.
|
||||
(compatGlobal as unknown as Record<string, unknown>).RTCPeerConnection = rtcPolyfillCtor;
|
||||
(compatGlobal as unknown as Record<string, unknown>).RTCPeerConnection = RTCPeerConnection;
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
|
||||
@@ -24,46 +24,48 @@ type PurgeMultiResult = {
|
||||
documentWasRemovedCompletely: boolean;
|
||||
};
|
||||
type PurgeMultiParam = [docId: string, rev$$1: string];
|
||||
type PurgeLogDocument = {
|
||||
purgeSeq: number;
|
||||
purges: Array<{ docId: string; rev: string; purgeSeq: number }>;
|
||||
};
|
||||
|
||||
function appendPurgeSeqs(db: PouchDB.Database, docs: PurgeMultiParam[]) {
|
||||
return (
|
||||
db
|
||||
.get("_local/purges")
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Internal method patching.
|
||||
.then(function (doc: any) {
|
||||
for (const [docId, rev$$1] of docs) {
|
||||
const purgeSeq = doc.purgeSeq + 1;
|
||||
doc.purges.push({
|
||||
docId,
|
||||
rev: rev$$1,
|
||||
purgeSeq,
|
||||
});
|
||||
return db
|
||||
.get<PurgeLogDocument>("_local/purges")
|
||||
.then(function (doc) {
|
||||
for (const [docId, rev$$1] of docs) {
|
||||
const purgeSeq = doc.purgeSeq + 1;
|
||||
doc.purges.push({
|
||||
docId,
|
||||
rev: rev$$1,
|
||||
purgeSeq,
|
||||
});
|
||||
//@ts-ignore : missing type def
|
||||
if (doc.purges.length > db.purged_infos_limit) {
|
||||
//@ts-ignore : missing type def
|
||||
if (doc.purges.length > db.purged_infos_limit) {
|
||||
//@ts-ignore : missing type def
|
||||
doc.purges.splice(0, doc.purges.length - db.purged_infos_limit);
|
||||
}
|
||||
doc.purgeSeq = purgeSeq;
|
||||
doc.purges.splice(0, doc.purges.length - db.purged_infos_limit);
|
||||
}
|
||||
return doc;
|
||||
})
|
||||
.catch(function (err) {
|
||||
if (err.status !== 404) {
|
||||
throw err;
|
||||
}
|
||||
return {
|
||||
_id: "_local/purges",
|
||||
purges: docs.map(([docId, rev$$1], idx) => ({
|
||||
docId,
|
||||
rev: rev$$1,
|
||||
purgeSeq: idx,
|
||||
})),
|
||||
purgeSeq: docs.length,
|
||||
};
|
||||
})
|
||||
.then(function (doc) {
|
||||
return db.put(doc);
|
||||
})
|
||||
);
|
||||
doc.purgeSeq = purgeSeq;
|
||||
}
|
||||
return doc;
|
||||
})
|
||||
.catch(function (err) {
|
||||
if (err.status !== 404) {
|
||||
throw err;
|
||||
}
|
||||
return {
|
||||
_id: "_local/purges",
|
||||
purges: docs.map(([docId, rev$$1], idx) => ({
|
||||
docId,
|
||||
rev: rev$$1,
|
||||
purgeSeq: idx,
|
||||
})),
|
||||
purgeSeq: docs.length,
|
||||
};
|
||||
})
|
||||
.then(function (doc) {
|
||||
return db.put(doc);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -88,7 +90,7 @@ PouchDB.prototype.purgeMulti = adapterFun(
|
||||
);
|
||||
}
|
||||
//@ts-ignore
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias -- The adapter task callbacks must retain this PouchDB instance.
|
||||
const self = this;
|
||||
const tasks = docs.map(
|
||||
(param) => () =>
|
||||
|
||||
+11
-8
@@ -2,7 +2,12 @@ import { NodeServiceContext, NodeServiceHub } from "./services/NodeServiceHub";
|
||||
import { configureNodeLocalStorage, ensureGlobalNodeLocalStorage } from "./services/NodeLocalStorage";
|
||||
import { LiveSyncBaseCore } from "@/LiveSyncBaseCore";
|
||||
import { initialiseServiceModulesCLI } from "./serviceModules/CLIServiceModules";
|
||||
import { DEFAULT_SETTINGS, LOG_LEVEL_VERBOSE, type LOG_LEVEL, type ObsidianLiveSyncSettings } from "@vrtmrz/livesync-commonlib/compat/common/types";
|
||||
import {
|
||||
DEFAULT_SETTINGS,
|
||||
LOG_LEVEL_VERBOSE,
|
||||
type LOG_LEVEL,
|
||||
type ObsidianLiveSyncSettings,
|
||||
} from "@vrtmrz/livesync-commonlib/compat/common/types";
|
||||
import type { InjectableServiceHub } from "@vrtmrz/livesync-commonlib/compat/services/implements/injectable/InjectableServiceHub";
|
||||
import type { InjectableSettingService } from "@vrtmrz/livesync-commonlib/compat/services/implements/injectable/InjectableSettingService";
|
||||
import {
|
||||
@@ -14,7 +19,7 @@ import {
|
||||
LOG_LEVEL_NOTICE,
|
||||
} from "octagonal-wheels/common/logger";
|
||||
import { runCommand } from "./commands/runCommand";
|
||||
import { VALID_COMMANDS } from "./commands/types";
|
||||
import { isCLICommand } from "./commands/types";
|
||||
import type { CLICommand, CLIOptions } from "./commands/types";
|
||||
import { getPathFromUXFileInfo } from "@vrtmrz/livesync-commonlib/compat/common/typeUtils";
|
||||
import { stripAllPrefixes } from "@vrtmrz/livesync-commonlib/compat/string_and_binary/path";
|
||||
@@ -185,9 +190,8 @@ export function parseArgs(): CLIOptions {
|
||||
break;
|
||||
default: {
|
||||
if (!databasePath) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Set checking
|
||||
if (command === "daemon" && VALID_COMMANDS.has(token as any)) {
|
||||
command = token as CLICommand;
|
||||
if (command === "daemon" && isCLICommand(token)) {
|
||||
command = token;
|
||||
break;
|
||||
}
|
||||
if (command === "init-settings") {
|
||||
@@ -197,9 +201,8 @@ export function parseArgs(): CLIOptions {
|
||||
databasePath = token;
|
||||
break;
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Set checking
|
||||
if (command === "daemon" && VALID_COMMANDS.has(token as any)) {
|
||||
command = token as CLICommand;
|
||||
if (command === "daemon" && isCLICommand(token)) {
|
||||
command = token;
|
||||
break;
|
||||
}
|
||||
commandArgs.push(token);
|
||||
|
||||
@@ -20,7 +20,6 @@ import { InjectableTweakValueService } from "@vrtmrz/livesync-commonlib/compat/s
|
||||
import { InjectableVaultServiceCompat } from "@vrtmrz/livesync-commonlib/compat/services/implements/injectable/InjectableVaultService";
|
||||
import { ControlService } from "@vrtmrz/livesync-commonlib/compat/services/base/ControlService";
|
||||
import { HeadlessAPIService } from "@vrtmrz/livesync-commonlib/compat/services/implements/headless/HeadlessAPIService";
|
||||
import type { ServiceInstances } from "@vrtmrz/livesync-commonlib/compat/services/ServiceHub";
|
||||
import { NodeKeyValueDBService } from "./NodeKeyValueDBService";
|
||||
import { NodeSettingService } from "./NodeSettingService";
|
||||
import { DatabaseService } from "@vrtmrz/livesync-commonlib/compat/services/base/DatabaseService";
|
||||
@@ -171,7 +170,7 @@ export class NodeServiceHub<T extends NodeServiceContext> extends InjectableServ
|
||||
APIService: API,
|
||||
});
|
||||
|
||||
const serviceInstancesToInit: Required<ServiceInstances<T>> = {
|
||||
const serviceInstancesToInit = {
|
||||
appLifecycle,
|
||||
conflict,
|
||||
database,
|
||||
@@ -191,7 +190,6 @@ export class NodeServiceHub<T extends NodeServiceContext> extends InjectableServ
|
||||
keyValueDB: keyValueDB as unknown as KeyValueDBService<T>,
|
||||
control,
|
||||
};
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- (Forcibly )
|
||||
super(context, serviceInstancesToInit as any);
|
||||
super(context, serviceInstancesToInit);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,15 @@ import { compatGlobal, _activeDocument } from "@vrtmrz/livesync-commonlib/compat
|
||||
const historyStore = new VaultHistoryStore();
|
||||
let app: LiveSyncWebApp | null = null;
|
||||
|
||||
type LiveSyncWebAppDebugApi = {
|
||||
getApp: () => LiveSyncWebApp | null;
|
||||
historyStore: VaultHistoryStore;
|
||||
};
|
||||
|
||||
type LiveSyncWebAppGlobal = typeof compatGlobal & {
|
||||
livesyncApp?: LiveSyncWebAppDebugApi;
|
||||
};
|
||||
|
||||
function getRequiredElement<T extends HTMLElement>(id: string): T {
|
||||
const element = _activeDocument.getElementById(id);
|
||||
if (!element) {
|
||||
@@ -131,8 +140,7 @@ compatGlobal.addEventListener("load", () => {
|
||||
compatGlobal.addEventListener("beforeunload", () => {
|
||||
void app?.shutdown();
|
||||
});
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- patching
|
||||
(compatGlobal as any).livesyncApp = {
|
||||
(compatGlobal as LiveSyncWebAppGlobal).livesyncApp = {
|
||||
getApp: () => app,
|
||||
historyStore,
|
||||
};
|
||||
|
||||
+10
-8
@@ -6,7 +6,7 @@
|
||||
import type { BrowserServiceHub } from "@vrtmrz/livesync-commonlib/compat/services/BrowserServices";
|
||||
import { LiveSyncBaseCore } from "@/LiveSyncBaseCore";
|
||||
import { ServiceContext } from "@vrtmrz/livesync-commonlib/compat/services/base/ServiceBase";
|
||||
import { initialiseServiceModulesFSAPI } from "./serviceModules/FSAPIServiceModules";
|
||||
import { initialiseServiceModulesFSAPI, type FSAPIServiceModules } from "./serviceModules/FSAPIServiceModules";
|
||||
import type { ObsidianLiveSyncSettings } from "@vrtmrz/livesync-commonlib/compat/common/types";
|
||||
import type { BrowserAPIService } from "@vrtmrz/livesync-commonlib/compat/services/implements/browser/BrowserAPIService";
|
||||
import type { InjectableSettingService } from "@vrtmrz/livesync-commonlib/compat/services/implements/injectable/InjectableSettingService";
|
||||
@@ -53,6 +53,7 @@ class LiveSyncWebApp {
|
||||
private rootHandle: FileSystemDirectoryHandle;
|
||||
private core: LiveSyncBaseCore<ServiceContext, never> | null = null;
|
||||
private serviceHub: BrowserServiceHub<ServiceContext> | null = null;
|
||||
private platformServiceModules: FSAPIServiceModules | null = null;
|
||||
|
||||
constructor(rootHandle: FileSystemDirectoryHandle) {
|
||||
this.rootHandle = rootHandle;
|
||||
@@ -113,7 +114,9 @@ class LiveSyncWebApp {
|
||||
this.core = new LiveSyncBaseCore<ServiceContext, never>(
|
||||
this.serviceHub,
|
||||
(core, serviceHub) => {
|
||||
return initialiseServiceModulesFSAPI(this.rootHandle, core, serviceHub);
|
||||
const serviceModules = initialiseServiceModulesFSAPI(this.rootHandle, core, serviceHub);
|
||||
this.platformServiceModules = serviceModules;
|
||||
return serviceModules;
|
||||
},
|
||||
(core) => [
|
||||
// new ModuleObsidianEvents(this, core),
|
||||
@@ -208,9 +211,8 @@ class LiveSyncWebApp {
|
||||
}
|
||||
|
||||
// Scan the directory to populate file cache
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Accessing private service modules
|
||||
const fileAccess = (this.core as any)._serviceModules?.storageAccess?.vaultAccess;
|
||||
if (fileAccess?.fsapiAdapter) {
|
||||
const fileAccess = this.platformServiceModules?.vaultAccess;
|
||||
if (fileAccess) {
|
||||
console.log("[Scanning] Scanning vault directory...");
|
||||
await fileAccess.fsapiAdapter.scanDirectory();
|
||||
const files = await fileAccess.fsapiAdapter.getFiles();
|
||||
@@ -227,13 +229,13 @@ class LiveSyncWebApp {
|
||||
console.log("[Shutdown] Shutting down...");
|
||||
|
||||
// Stop file watching
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Accessing private service modules
|
||||
const storageEventManager = (this.core as any)._serviceModules?.storageAccess?.storageEventManager;
|
||||
if (storageEventManager?.cleanup) {
|
||||
const storageEventManager = this.platformServiceModules?.storageEventManager;
|
||||
if (storageEventManager) {
|
||||
await storageEventManager.cleanup();
|
||||
}
|
||||
|
||||
await this.core.services.control.onUnload();
|
||||
this.platformServiceModules = null;
|
||||
console.log("[Shutdown] Complete");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,25 @@ import type { FileEventItemSentinel } from "@vrtmrz/livesync-commonlib/compat/ma
|
||||
import type { FSAPIFile, FSAPIFolder } from "@/apps/webapp/adapters/FSAPITypes";
|
||||
import { compatGlobal } from "@vrtmrz/livesync-commonlib/compat/common/coreEnvFunctions";
|
||||
|
||||
type FileSystemObserverRecord = {
|
||||
changedHandle?: FileSystemFileHandle | FileSystemDirectoryHandle;
|
||||
relativePathComponents?: readonly string[];
|
||||
type: "appeared" | "disappeared" | "modified" | "moved" | "unknown" | "errored";
|
||||
};
|
||||
|
||||
type FileSystemObserverInstance = {
|
||||
observe(handle: FileSystemDirectoryHandle, options: { recursive: boolean }): Promise<void>;
|
||||
disconnect(): void;
|
||||
};
|
||||
|
||||
type FileSystemObserverConstructor = new (
|
||||
callback: (records: readonly FileSystemObserverRecord[]) => void | Promise<void>
|
||||
) => FileSystemObserverInstance;
|
||||
|
||||
type GlobalWithFileSystemObserver = typeof compatGlobal & {
|
||||
FileSystemObserver?: FileSystemObserverConstructor;
|
||||
};
|
||||
|
||||
/**
|
||||
* FileSystem API-specific type guard adapter
|
||||
*/
|
||||
@@ -89,7 +108,8 @@ class FSAPIPersistenceAdapter implements IStorageEventPersistenceAdapter {
|
||||
|
||||
const result = await new Promise<(FileEventItem | FileEventItemSentinel)[] | null>((resolve, reject) => {
|
||||
const request = store.get(this.snapshotKey);
|
||||
request.onsuccess = () => resolve(request.result || null);
|
||||
request.onsuccess = () =>
|
||||
resolve((request.result as (FileEventItem | FileEventItemSentinel)[] | undefined) ?? null);
|
||||
request.onerror = () => reject(request.error);
|
||||
});
|
||||
|
||||
@@ -155,26 +175,21 @@ class FSAPIConverterAdapter implements IStorageEventConverterAdapter<FSAPIFile>
|
||||
* FileSystem API-specific watch adapter using FileSystemObserver (Chrome only)
|
||||
*/
|
||||
class FSAPIWatchAdapter implements IStorageEventWatchAdapter {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Accessing private service modules
|
||||
private observer: any = null; // FileSystemObserver type
|
||||
private observer: FileSystemObserverInstance | null = null;
|
||||
|
||||
constructor(private rootHandle: FileSystemDirectoryHandle) {}
|
||||
|
||||
async beginWatch(handlers: IStorageEventWatchHandlers): Promise<void> {
|
||||
// Use FileSystemObserver if available (Chrome 124+)
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Accessing global FileSystemObserver
|
||||
if (typeof (compatGlobal as any).FileSystemObserver === "undefined") {
|
||||
const FileSystemObserver = (compatGlobal as GlobalWithFileSystemObserver).FileSystemObserver;
|
||||
if (!FileSystemObserver) {
|
||||
console.log("[FSAPIWatchAdapter] FileSystemObserver not available, file watching disabled");
|
||||
console.log("[FSAPIWatchAdapter] Consider using Chrome 124+ for real-time file watching");
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Accessing private service modules
|
||||
const FileSystemObserver = (compatGlobal as any).FileSystemObserver;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Accessing private service modules
|
||||
this.observer = new FileSystemObserver(async (records: any[]) => {
|
||||
this.observer = new FileSystemObserver(async (records) => {
|
||||
for (const record of records) {
|
||||
const changedHandle = record.changedHandle;
|
||||
const relativePathComponents = record.relativePathComponents;
|
||||
|
||||
@@ -11,6 +11,11 @@ import { StorageEventManagerFSAPI } from "@/apps/webapp/managers/StorageEventMan
|
||||
import type { ServiceModules } from "@vrtmrz/livesync-commonlib/compat/interfaces/ServiceModule";
|
||||
import { ServiceFileHandler } from "@/serviceModules/FileHandler";
|
||||
|
||||
export interface FSAPIServiceModules extends ServiceModules {
|
||||
vaultAccess: FileAccessFSAPI;
|
||||
storageEventManager: StorageEventManagerFSAPI;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize service modules for FileSystem API webapp version
|
||||
* This is the webapp equivalent of ObsidianLiveSyncPlugin.initialiseServiceModules
|
||||
@@ -24,7 +29,7 @@ export function initialiseServiceModulesFSAPI(
|
||||
rootHandle: FileSystemDirectoryHandle,
|
||||
core: LiveSyncBaseCore<ServiceContext, never>,
|
||||
services: InjectableServiceHub<ServiceContext>
|
||||
): ServiceModules {
|
||||
): FSAPIServiceModules {
|
||||
const storageAccessManager = new StorageAccessManager();
|
||||
|
||||
// FileSystem API-specific file access
|
||||
@@ -104,5 +109,7 @@ export function initialiseServiceModulesFSAPI(
|
||||
fileHandler,
|
||||
databaseFileAccess,
|
||||
storageAccess,
|
||||
vaultAccess,
|
||||
storageEventManager,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user