mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-10-25 10:33:21 +00:00
Adopt Commonlib 0.2.0 with injected device storage
This commit is contained in:
1 parent
e56098eb57
commit
96ad2737a7
60 files changed
+1499
-541
No files matched your search
@@ -719,8 +719,8 @@ src/apps/cli/
|
||||
│ └── ServiceFileAccessImpl.ts
|
||||
├── services/
|
||||
│ ├── NodeKeyValueDBService.ts
|
||||
│ ├── NodeServiceHub.ts
|
||||
│ └── NodeSettingService.ts
|
||||
│ ├── NodeLocalStorage.ts
|
||||
│ └── NodeServiceHub.ts
|
||||
├── test/
|
||||
│ ├── test-e2e-two-vaults-common.sh
|
||||
│ ├── test-e2e-two-vaults-matrix.sh
|
||||
|
||||
@@ -88,6 +88,27 @@ describe("CLI database preparation", () => {
|
||||
await expect(main(standardIo, runner)).rejects.toThrow(`__EXIT__:${exitCode}`);
|
||||
}
|
||||
|
||||
it("loads legacy device values and persists configuration through the service-owned store", async () => {
|
||||
const localStoragePath = join(directory, ".livesync", "runtime", "local-storage.json");
|
||||
await mkdir(join(directory, ".livesync", "runtime"), { recursive: true });
|
||||
await writeFile(localStoragePath, JSON.stringify({ ["__proto__"]: "legacy value" }));
|
||||
let legacyValue: string | null | undefined;
|
||||
const runner = vi.fn<CliCommandRunner>(async (_options, { core }) => {
|
||||
legacyValue = await core.services.setting.deviceValues.getValue("__proto__");
|
||||
expect(core.services.config).toBe(core.services.setting.config);
|
||||
await core.services.config.setValue("bootstrap-probe", "config value");
|
||||
return false;
|
||||
});
|
||||
|
||||
await start("ls", runner);
|
||||
|
||||
expect(legacyValue).toBe("legacy value");
|
||||
const stored = JSON.parse(await readFile(localStoragePath, "utf-8")) as Record<string, string>;
|
||||
expect(Object.prototype.hasOwnProperty.call(stored, "__proto__")).toBe(true);
|
||||
expect(stored["__proto__"]).toBe("legacy value");
|
||||
expect(Object.values(stored)).toContain("config value");
|
||||
});
|
||||
|
||||
it.each([REMOTE_P2P, REMOTE_COUCHDB])(
|
||||
"restores configured P2P automation before the host connects (%s)",
|
||||
async (remoteType) => {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useP2PSettingsPreparation } from "@/serviceFeatures/useP2PSettingsPreparation";
|
||||
import { NodeServiceContext, NodeServiceHub } from "./services/NodeServiceHub";
|
||||
import { configureNodeLocalStorage, ensureGlobalNodeLocalStorage } from "./services/NodeLocalStorage";
|
||||
import { LiveSyncBaseCore, type StartupDatabaseOptions } from "@/LiveSyncBaseCore";
|
||||
import { initialiseServiceModulesCLI } from "./serviceModules/CLIServiceModules";
|
||||
import {
|
||||
@@ -78,7 +77,6 @@ const VAULT_SYNC_MODES: Readonly<Partial<Record<CLICommand, CLIVaultSyncMode>>>
|
||||
},
|
||||
};
|
||||
|
||||
ensureGlobalNodeLocalStorage();
|
||||
defaultLoggerEnv.minLogLevel = LOG_LEVEL_DEBUG;
|
||||
|
||||
/** Injectable command boundary used by CLI integration probes. */
|
||||
@@ -400,8 +398,6 @@ export async function main(
|
||||
const settingsPath = options.settingsPath
|
||||
? path.resolve(options.settingsPath)
|
||||
: path.join(databasePath, SETTINGS_FILE);
|
||||
configureNodeLocalStorage(path.join(databasePath, ".livesync", "runtime", "local-storage.json"));
|
||||
|
||||
// Resolve vault path: mirror positional argument takes priority,
|
||||
// then --vault flag, otherwise fall back to databasePath.
|
||||
const vaultPath =
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"buildRun": "npm run build && npm run cli --",
|
||||
"build:docker": "docker build -f Dockerfile -t livesync-cli ../../..",
|
||||
"check": "tsc -p tsconfig.json",
|
||||
"test:unit": "cd ../../.. && npx vitest run --config vitest.config.unit.ts src/apps/cli/main.unit.spec.ts src/apps/cli/main.bootstrap.unit.spec.ts src/apps/cli/settingsPersistence.unit.spec.ts src/apps/cli/commands/utils.unit.spec.ts src/apps/cli/commands/runCommand.unit.spec.ts src/apps/cli/commands/daemonCommand.unit.spec.ts src/apps/cli/commands/p2p.unit.spec.ts src/apps/cli/deploy/install.unit.spec.ts src/apps/cli/adapters/NodeFileSystemAdapter.unit.spec.ts",
|
||||
"test:unit": "cd ../../.. && npx vitest run --config vitest.config.unit.ts src/apps/cli/main.unit.spec.ts src/apps/cli/main.bootstrap.unit.spec.ts src/apps/cli/settingsPersistence.unit.spec.ts src/apps/cli/services/NodeLocalStorage.unit.spec.ts src/apps/cli/commands/utils.unit.spec.ts src/apps/cli/commands/runCommand.unit.spec.ts src/apps/cli/commands/daemonCommand.unit.spec.ts src/apps/cli/commands/p2p.unit.spec.ts src/apps/cli/deploy/install.unit.spec.ts src/apps/cli/adapters/NodeFileSystemAdapter.unit.spec.ts",
|
||||
"test:e2e:two-vaults": "bash test/test-e2e-two-vaults-with-docker-linux.sh",
|
||||
"test:e2e:two-vaults:common": "bash test/test-e2e-two-vaults-common.sh",
|
||||
"test:e2e:two-vaults:matrix": "bash test/test-e2e-two-vaults-matrix.sh",
|
||||
|
||||
@@ -1,115 +1,86 @@
|
||||
import { createDeletableValueStore, sanitiseValueStoreError, ValueStoreError } from "@vrtmrz/value-store";
|
||||
import type { ValueDeleter, ValueStore } from "@vrtmrz/value-store";
|
||||
import { fs as nodeFs, path as nodePath } from "@vrtmrz/livesync-commonlib/node";
|
||||
import { compatGlobal } from "@vrtmrz/livesync-commonlib/compat/common/coreEnvFunctions";
|
||||
|
||||
type LocalStorageShape = {
|
||||
getItem(key: string): string | null;
|
||||
setItem(key: string, value: string): void;
|
||||
removeItem(key: string): void;
|
||||
clear(): void;
|
||||
};
|
||||
type NodeLocalStorage = ValueStore & ValueDeleter;
|
||||
|
||||
class PersistentNodeLocalStorage {
|
||||
private storagePath: string | undefined;
|
||||
private localStore: Record<string, string> = {};
|
||||
class JsonFileValueBackend {
|
||||
private values: Map<string, string>;
|
||||
|
||||
configure(storagePath: string) {
|
||||
if (this.storagePath === storagePath) {
|
||||
return;
|
||||
}
|
||||
this.storagePath = storagePath;
|
||||
this.loadFromFile();
|
||||
constructor(private readonly storagePath: string) {
|
||||
this.values = this.loadValues();
|
||||
}
|
||||
|
||||
private loadFromFile() {
|
||||
if (!this.storagePath) {
|
||||
this.localStore = {};
|
||||
get(key: string): string | null {
|
||||
return this.values.get(key) ?? null;
|
||||
}
|
||||
|
||||
set(key: string, value: string): void {
|
||||
const nextValues = new Map(this.values);
|
||||
nextValues.set(key, value);
|
||||
this.writeValues(nextValues);
|
||||
this.values = nextValues;
|
||||
}
|
||||
|
||||
delete(key: string): void {
|
||||
if (!this.values.has(key)) {
|
||||
return;
|
||||
}
|
||||
const nextValues = new Map(this.values);
|
||||
nextValues.delete(key);
|
||||
this.writeValues(nextValues);
|
||||
this.values = nextValues;
|
||||
}
|
||||
|
||||
private loadValues(): Map<string, string> {
|
||||
let contents: string;
|
||||
try {
|
||||
const loaded = JSON.parse(nodeFs.readFileSync(this.storagePath, "utf-8")) as Record<string, string>;
|
||||
this.localStore = { ...loaded };
|
||||
contents = nodeFs.readFileSync(this.storagePath, "utf-8");
|
||||
} catch (error) {
|
||||
if (isMissingFileError(error)) {
|
||||
return new Map();
|
||||
}
|
||||
throw sanitiseValueStoreError(error);
|
||||
}
|
||||
|
||||
let document: unknown;
|
||||
try {
|
||||
document = JSON.parse(contents);
|
||||
} catch {
|
||||
this.localStore = {};
|
||||
throw new ValueStoreError("invalid-data");
|
||||
}
|
||||
if (document === null || typeof document !== "object" || Array.isArray(document)) {
|
||||
throw new ValueStoreError("invalid-data");
|
||||
}
|
||||
|
||||
const values = new Map<string, string>();
|
||||
for (const [key, value] of Object.entries(document)) {
|
||||
if (typeof value !== "string") {
|
||||
throw new ValueStoreError("invalid-data");
|
||||
}
|
||||
values.set(key, value);
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
private flushToFile() {
|
||||
if (!this.storagePath) {
|
||||
return;
|
||||
}
|
||||
private writeValues(values: ReadonlyMap<string, string>): void {
|
||||
nodeFs.mkdirSync(nodePath.dirname(this.storagePath), { recursive: true });
|
||||
nodeFs.writeFileSync(this.storagePath, JSON.stringify(this.localStore, null, 2), "utf-8");
|
||||
}
|
||||
|
||||
getItem(key: string): string | null {
|
||||
return this.localStore[key] ?? null;
|
||||
}
|
||||
|
||||
setItem(key: string, value: string) {
|
||||
this.localStore[key] = value;
|
||||
this.flushToFile();
|
||||
}
|
||||
|
||||
removeItem(key: string) {
|
||||
if (!(key in this.localStore)) {
|
||||
return;
|
||||
}
|
||||
delete this.localStore[key];
|
||||
this.flushToFile();
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.localStore = {};
|
||||
this.flushToFile();
|
||||
nodeFs.writeFileSync(this.storagePath, JSON.stringify(Object.fromEntries(values), null, 2), "utf-8");
|
||||
}
|
||||
}
|
||||
|
||||
const persistentNodeLocalStorage = new PersistentNodeLocalStorage();
|
||||
|
||||
function createNodeLocalStorageShim(): LocalStorageShape {
|
||||
return {
|
||||
getItem(key: string) {
|
||||
return persistentNodeLocalStorage.getItem(key);
|
||||
},
|
||||
setItem(key: string, value: string) {
|
||||
persistentNodeLocalStorage.setItem(key, value);
|
||||
},
|
||||
removeItem(key: string) {
|
||||
persistentNodeLocalStorage.removeItem(key);
|
||||
},
|
||||
clear() {
|
||||
persistentNodeLocalStorage.clear();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function ensureGlobalNodeLocalStorage() {
|
||||
const g = compatGlobal as unknown as Record<string, unknown>;
|
||||
if (
|
||||
!("localStorage" in g) ||
|
||||
typeof (g.localStorage as Record<string, unknown> | undefined)?.getItem !== "function"
|
||||
) {
|
||||
g.localStorage = createNodeLocalStorageShim();
|
||||
function isMissingFileError(error: unknown): boolean {
|
||||
if (typeof error !== "object" || error === null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
const code = Object.getOwnPropertyDescriptor(error, "code");
|
||||
return code !== undefined && Object.prototype.hasOwnProperty.call(code, "value") && code.value === "ENOENT";
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function configureNodeLocalStorage(storagePath: string) {
|
||||
persistentNodeLocalStorage.configure(storagePath);
|
||||
ensureGlobalNodeLocalStorage();
|
||||
}
|
||||
|
||||
export function getNodeLocalStorageItem(key: string): string {
|
||||
return persistentNodeLocalStorage.getItem(key) ?? "";
|
||||
}
|
||||
|
||||
export function setNodeLocalStorageItem(key: string, value: string) {
|
||||
persistentNodeLocalStorage.setItem(key, value);
|
||||
}
|
||||
|
||||
export function deleteNodeLocalStorageItem(key: string) {
|
||||
persistentNodeLocalStorage.removeItem(key);
|
||||
}
|
||||
|
||||
export function clearNodeLocalStorage() {
|
||||
persistentNodeLocalStorage.clear();
|
||||
export function createNodeLocalStorage(storagePath: string): NodeLocalStorage {
|
||||
return createDeletableValueStore(new JsonFileValueBackend(storagePath));
|
||||
}
|
||||
@@ -1,58 +1,127 @@
|
||||
import { fs, os, path } from "@vrtmrz/livesync-commonlib/node";
|
||||
import { ValueStoreError } from "@vrtmrz/value-store";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import {
|
||||
clearNodeLocalStorage,
|
||||
configureNodeLocalStorage,
|
||||
ensureGlobalNodeLocalStorage,
|
||||
getNodeLocalStorageItem,
|
||||
setNodeLocalStorageItem,
|
||||
} from "./NodeLocalStorage";
|
||||
import { createNodeLocalStorage } from "./NodeLocalStorage";
|
||||
|
||||
describe("NodeLocalStorage", () => {
|
||||
const tempDirs: string[] = [];
|
||||
|
||||
afterEach(() => {
|
||||
clearNodeLocalStorage();
|
||||
for (const tempDir of tempDirs.splice(0)) {
|
||||
fs.rmSync(tempDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("persists values to the configured file", () => {
|
||||
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "livesync-node-local-storage-"));
|
||||
tempDirs.push(tempDir);
|
||||
const storagePath = path.join(tempDir, "runtime", "local-storage.json");
|
||||
|
||||
configureNodeLocalStorage(storagePath);
|
||||
setNodeLocalStorageItem("checkpoint", "42");
|
||||
|
||||
const saved = JSON.parse(fs.readFileSync(storagePath, "utf-8")) as Record<string, string>;
|
||||
expect(saved.checkpoint).toBe("42");
|
||||
});
|
||||
|
||||
it("reloads persisted values when configured again", () => {
|
||||
it("loads and preserves legacy flat string data, including empty values and exact keys", async () => {
|
||||
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "livesync-node-local-storage-"));
|
||||
tempDirs.push(tempDir);
|
||||
const storagePath = path.join(tempDir, "runtime", "local-storage.json");
|
||||
const specialKey = "__proto__";
|
||||
const specialValue = "line one\n雪";
|
||||
|
||||
fs.mkdirSync(path.dirname(storagePath), { recursive: true });
|
||||
fs.writeFileSync(storagePath, JSON.stringify({ persisted: "value" }, null, 2), "utf-8");
|
||||
fs.writeFileSync(storagePath, JSON.stringify({ [specialKey]: specialValue, empty: "" }), "utf-8");
|
||||
|
||||
configureNodeLocalStorage(storagePath);
|
||||
const store = createNodeLocalStorage(storagePath);
|
||||
|
||||
expect(getNodeLocalStorageItem("persisted")).toBe("value");
|
||||
await expect(store.getValue(specialKey)).resolves.toBe(specialValue);
|
||||
await expect(store.getValue("empty", "default")).resolves.toBe("");
|
||||
const saved = JSON.parse(fs.readFileSync(storagePath, "utf-8")) as Record<string, string>;
|
||||
expect(saved[specialKey]).toBe(specialValue);
|
||||
expect(saved.empty).toBe("");
|
||||
});
|
||||
|
||||
it("installs a global localStorage shim backed by the same store", () => {
|
||||
it("persists strings and objects, reloads them, and deletes keys", async () => {
|
||||
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "livesync-node-local-storage-"));
|
||||
tempDirs.push(tempDir);
|
||||
const storagePath = path.join(tempDir, "runtime", "local-storage.json");
|
||||
const store = createNodeLocalStorage(storagePath);
|
||||
const object = { enabled: false, entries: ["value", null, 0] };
|
||||
|
||||
configureNodeLocalStorage(storagePath);
|
||||
ensureGlobalNodeLocalStorage();
|
||||
await store.setValue("empty", "");
|
||||
await store.setValue("text", "stored value");
|
||||
await store.setObject("object", object);
|
||||
|
||||
globalThis.localStorage.setItem("shared", "state");
|
||||
const reloaded = createNodeLocalStorage(storagePath);
|
||||
await expect(reloaded.getValue("empty", "default")).resolves.toBe("");
|
||||
await expect(reloaded.getValue("text")).resolves.toBe("stored value");
|
||||
await expect(reloaded.getObject("object")).resolves.toEqual(object);
|
||||
|
||||
expect(getNodeLocalStorageItem("shared")).toBe("state");
|
||||
await reloaded.delete("text");
|
||||
await expect(reloaded.getValue("text")).resolves.toBeNull();
|
||||
const saved = JSON.parse(fs.readFileSync(storagePath, "utf-8")) as Record<string, string>;
|
||||
expect(saved.text).toBeUndefined();
|
||||
expect(saved.object).toBe(JSON.stringify(object));
|
||||
});
|
||||
|
||||
it("treats a missing file as empty and creates its parent on the first write", async () => {
|
||||
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "livesync-node-local-storage-"));
|
||||
tempDirs.push(tempDir);
|
||||
const storagePath = path.join(tempDir, "runtime", "local-storage.json");
|
||||
const store = createNodeLocalStorage(storagePath);
|
||||
|
||||
await expect(store.getValue("absent")).resolves.toBeNull();
|
||||
expect(fs.existsSync(storagePath)).toBe(false);
|
||||
await store.setValue("created", "value");
|
||||
expect(fs.existsSync(storagePath)).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps separate store instances isolated by their file paths", async () => {
|
||||
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "livesync-node-local-storage-"));
|
||||
tempDirs.push(tempDir);
|
||||
const first = createNodeLocalStorage(path.join(tempDir, "first", "local-storage.json"));
|
||||
const second = createNodeLocalStorage(path.join(tempDir, "second", "local-storage.json"));
|
||||
|
||||
await first.setValue("shared-key", "first");
|
||||
await second.setValue("shared-key", "second");
|
||||
|
||||
await expect(first.getValue("shared-key")).resolves.toBe("first");
|
||||
await expect(second.getValue("shared-key")).resolves.toBe("second");
|
||||
});
|
||||
|
||||
it("surfaces malformed and unreadable files as sanitised value-store errors", async () => {
|
||||
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "livesync-node-local-storage-"));
|
||||
tempDirs.push(tempDir);
|
||||
const malformedPath = path.join(tempDir, "malformed.json");
|
||||
fs.writeFileSync(malformedPath, "{ malformed", "utf-8");
|
||||
|
||||
let malformedError: unknown;
|
||||
try {
|
||||
createNodeLocalStorage(malformedPath);
|
||||
} catch (error) {
|
||||
malformedError = error;
|
||||
}
|
||||
expect(malformedError).toBeInstanceOf(ValueStoreError);
|
||||
expect(malformedError).toMatchObject({ code: "invalid-data" });
|
||||
expect(fs.readFileSync(malformedPath, "utf-8")).toBe("{ malformed");
|
||||
|
||||
const unreadablePath = path.join(tempDir, "unreadable.json");
|
||||
fs.mkdirSync(unreadablePath);
|
||||
expect(() => createNodeLocalStorage(unreadablePath)).toThrowError(ValueStoreError);
|
||||
});
|
||||
|
||||
it("does not change its visible values when a write fails", async () => {
|
||||
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "livesync-node-local-storage-"));
|
||||
tempDirs.push(tempDir);
|
||||
const storagePath = path.join(tempDir, "runtime", "local-storage.json");
|
||||
const store = createNodeLocalStorage(storagePath);
|
||||
await store.setValue("retained", "before failure");
|
||||
|
||||
fs.rmSync(storagePath);
|
||||
fs.mkdirSync(storagePath);
|
||||
|
||||
await expect(store.setValue("new", "write fails")).rejects.toBeInstanceOf(ValueStoreError);
|
||||
await expect(store.getValue("retained")).resolves.toBe("before failure");
|
||||
await expect(store.getValue("new")).resolves.toBeNull();
|
||||
});
|
||||
|
||||
it("does not install or modify a global localStorage value", () => {
|
||||
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "livesync-node-local-storage-"));
|
||||
tempDirs.push(tempDir);
|
||||
const before = Object.getOwnPropertyDescriptor(globalThis, "localStorage");
|
||||
|
||||
createNodeLocalStorage(path.join(tempDir, "runtime", "local-storage.json"));
|
||||
|
||||
expect(Object.getOwnPropertyDescriptor(globalThis, "localStorage")).toEqual(before);
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { AppLifecycleServiceDependencies } from "@vrtmrz/livesync-commonlib/compat/services/base/AppLifecycleService";
|
||||
import type { ServiceContext } from "@vrtmrz/livesync-commonlib/context";
|
||||
import { ConfigServiceBrowserCompat } from "@vrtmrz/livesync-commonlib/compat/services/implements/browser/ConfigServiceBrowserCompat";
|
||||
import type {
|
||||
ComponentHasResult,
|
||||
SvelteDialogManager,
|
||||
@@ -11,6 +10,7 @@ import { InjectableAppLifecycleService } from "@vrtmrz/livesync-commonlib/compat
|
||||
import { InjectableConflictService } from "@vrtmrz/livesync-commonlib/compat/services/implements/injectable/InjectableConflictService";
|
||||
import { InjectableDatabaseEventService } from "@vrtmrz/livesync-commonlib/compat/services/implements/injectable/InjectableDatabaseEventService";
|
||||
import { InjectableFileProcessingService } from "@vrtmrz/livesync-commonlib/compat/services/implements/injectable/InjectableFileProcessingService";
|
||||
import { InjectableSettingService } from "@vrtmrz/livesync-commonlib/compat/services/implements/injectable/InjectableSettingService";
|
||||
import { PathServiceCompat } from "@vrtmrz/livesync-commonlib/compat/services/implements/injectable/InjectablePathService";
|
||||
import { InjectableRemoteService } from "@vrtmrz/livesync-commonlib/compat/services/implements/injectable/InjectableRemoteService";
|
||||
import { InjectableReplicationService } from "@vrtmrz/livesync-commonlib/compat/services/implements/injectable/InjectableReplicationService";
|
||||
@@ -21,7 +21,7 @@ import { InjectableVaultServiceCompat } from "@vrtmrz/livesync-commonlib/compat/
|
||||
import { ControlService } from "@vrtmrz/livesync-commonlib/compat/services/base/ControlService";
|
||||
import { HeadlessAPIService } from "@vrtmrz/livesync-commonlib/compat/services/implements/headless/HeadlessAPIService";
|
||||
import { NodeKeyValueDBService } from "./NodeKeyValueDBService";
|
||||
import { NodeSettingService } from "./NodeSettingService";
|
||||
import { createNodeLocalStorage } from "./NodeLocalStorage";
|
||||
import { DatabaseService } from "@vrtmrz/livesync-commonlib/compat/services/base/DatabaseService";
|
||||
import type { ObsidianLiveSyncSettings } from "@vrtmrz/livesync-commonlib/compat/common/types";
|
||||
import { path as nodePath } from "@vrtmrz/livesync-commonlib/node";
|
||||
@@ -96,11 +96,11 @@ export class NodeServiceHub<T extends NodeServiceContext> extends InjectableServ
|
||||
const conflict = new InjectableConflictService(context);
|
||||
const fileProcessing = new InjectableFileProcessingService(context);
|
||||
|
||||
const setting = new NodeSettingService(
|
||||
context,
|
||||
{ APIService: API, onDisplayLanguageChanged: setLang },
|
||||
localStoragePath
|
||||
);
|
||||
const setting = new InjectableSettingService<T>(context, {
|
||||
APIService: API,
|
||||
onDisplayLanguageChanged: setLang,
|
||||
deviceValues: createNodeLocalStorage(localStoragePath),
|
||||
});
|
||||
|
||||
const appLifecycle = new NodeAppLifecycleService<T>(context, {
|
||||
settingService: setting,
|
||||
@@ -132,10 +132,7 @@ export class NodeServiceHub<T extends NodeServiceContext> extends InjectableServ
|
||||
setting,
|
||||
});
|
||||
|
||||
const config = new ConfigServiceBrowserCompat<T>(context, {
|
||||
settingService: setting,
|
||||
APIService: API,
|
||||
});
|
||||
const config = setting.config;
|
||||
|
||||
const replicator = new InjectableReplicatorService(context, {
|
||||
settingService: setting,
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
import { EVENT_SETTING_SAVED } from "@vrtmrz/livesync-commonlib/compat/events/coreEvents";
|
||||
import { EVENT_REQUEST_RELOAD_SETTING_TAB } from "@/common/events";
|
||||
import { handlers } from "@vrtmrz/livesync-commonlib/compat/services/lib/HandlerUtils";
|
||||
import type { ObsidianLiveSyncSettings } from "@vrtmrz/livesync-commonlib/compat/common/types";
|
||||
import type { ServiceContext } from "@vrtmrz/livesync-commonlib/context";
|
||||
import { SettingService, type SettingServiceDependencies } from "@vrtmrz/livesync-commonlib/compat/services/base/SettingService";
|
||||
import {
|
||||
configureNodeLocalStorage,
|
||||
deleteNodeLocalStorageItem,
|
||||
getNodeLocalStorageItem,
|
||||
setNodeLocalStorageItem,
|
||||
} from "./NodeLocalStorage";
|
||||
|
||||
export class NodeSettingService<T extends ServiceContext> extends SettingService<T> {
|
||||
constructor(context: T, dependencies: SettingServiceDependencies, storagePath: string) {
|
||||
super(context, dependencies);
|
||||
configureNodeLocalStorage(storagePath);
|
||||
this.onSettingSaved.addHandler((settings) => {
|
||||
this.context.events.emitEvent(EVENT_SETTING_SAVED, settings);
|
||||
return Promise.resolve(true);
|
||||
});
|
||||
this.onSettingLoaded.addHandler((settings) => {
|
||||
this.context.events.emitEvent(EVENT_REQUEST_RELOAD_SETTING_TAB);
|
||||
return Promise.resolve(true);
|
||||
});
|
||||
}
|
||||
|
||||
protected setItem(key: string, value: string) {
|
||||
setNodeLocalStorageItem(key, value);
|
||||
}
|
||||
|
||||
protected getItem(key: string): string {
|
||||
return getNodeLocalStorageItem(key);
|
||||
}
|
||||
|
||||
protected deleteItem(key: string): void {
|
||||
deleteNodeLocalStorageItem(key);
|
||||
}
|
||||
|
||||
public saveData = handlers<{ saveData: (data: ObsidianLiveSyncSettings) => Promise<void> }>().binder("saveData");
|
||||
public loadData = handlers<{ loadData: () => Promise<ObsidianLiveSyncSettings | undefined> }>().binder("loadData");
|
||||
}
|
||||
Reference in new issue
Block a user