mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-09-01 16:27:07 +00:00
fix(cli): persist adaptive writer state
This commit is contained in:
@@ -25,6 +25,10 @@ type SerializableContainer =
|
|||||||
| {
|
| {
|
||||||
[NODE_KV_TYPED_KEY]: "ArrayBuffer";
|
[NODE_KV_TYPED_KEY]: "ArrayBuffer";
|
||||||
[NODE_KV_VALUES_KEY]: number[];
|
[NODE_KV_VALUES_KEY]: number[];
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
[NODE_KV_TYPED_KEY]: "BigInt";
|
||||||
|
[NODE_KV_VALUES_KEY]: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||||
@@ -32,6 +36,12 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function serializeForNodeKV(value: unknown): unknown {
|
function serializeForNodeKV(value: unknown): unknown {
|
||||||
|
if (typeof value === "bigint") {
|
||||||
|
return {
|
||||||
|
[NODE_KV_TYPED_KEY]: "BigInt",
|
||||||
|
[NODE_KV_VALUES_KEY]: value.toString(10),
|
||||||
|
} satisfies SerializableContainer;
|
||||||
|
}
|
||||||
if (value instanceof Set) {
|
if (value instanceof Set) {
|
||||||
return {
|
return {
|
||||||
[NODE_KV_TYPED_KEY]: "Set",
|
[NODE_KV_TYPED_KEY]: "Set",
|
||||||
@@ -78,6 +88,9 @@ function deserializeFromNodeKV(value: unknown): unknown {
|
|||||||
if (taggedType === "ArrayBuffer" && Array.isArray(taggedValues)) {
|
if (taggedType === "ArrayBuffer" && Array.isArray(taggedValues)) {
|
||||||
return Uint8Array.from(taggedValues).buffer;
|
return Uint8Array.from(taggedValues).buffer;
|
||||||
}
|
}
|
||||||
|
if (taggedType === "BigInt" && typeof taggedValues === "string" && /^-?(?:0|[1-9]\d*)$/u.test(taggedValues)) {
|
||||||
|
return BigInt(taggedValues);
|
||||||
|
}
|
||||||
|
|
||||||
return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, deserializeFromNodeKV(v)]));
|
return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, deserializeFromNodeKV(v)]));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,39 @@
|
|||||||
import { describe, expect, it, vi } from "vitest";
|
import { describe, expect, it, vi } from "vitest";
|
||||||
import { createServiceContext } from "@vrtmrz/livesync-commonlib/compat/services/base/ServiceBase";
|
import { createServiceContext } from "@vrtmrz/livesync-commonlib/compat/services/base/ServiceBase";
|
||||||
|
import { fsPromises as fs, os, path } from "@vrtmrz/livesync-commonlib/node";
|
||||||
import type { NodeKeyValueDBDependencies } from "./NodeKeyValueDBService";
|
import type { NodeKeyValueDBDependencies } from "./NodeKeyValueDBService";
|
||||||
import { NodeKeyValueDBService } from "./NodeKeyValueDBService";
|
import { NodeKeyValueDBService } from "./NodeKeyValueDBService";
|
||||||
|
|
||||||
|
function createInitialisableDependencies(): {
|
||||||
|
dependencies: NodeKeyValueDBDependencies;
|
||||||
|
initialise: () => Promise<boolean>;
|
||||||
|
} {
|
||||||
|
let initialise: (() => Promise<boolean>) | undefined;
|
||||||
|
const dependencies = {
|
||||||
|
appLifecycle: {
|
||||||
|
onSettingLoaded: {
|
||||||
|
addHandler: vi.fn((handler: () => Promise<boolean>) => {
|
||||||
|
initialise = handler;
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
databaseEvents: {
|
||||||
|
onResetDatabase: { addHandler: vi.fn() },
|
||||||
|
onDatabaseInitialisation: { addHandler: vi.fn() },
|
||||||
|
onUnloadDatabase: { addHandler: vi.fn() },
|
||||||
|
onCloseDatabase: { addHandler: vi.fn() },
|
||||||
|
},
|
||||||
|
vault: {},
|
||||||
|
} as unknown as NodeKeyValueDBDependencies;
|
||||||
|
return {
|
||||||
|
dependencies,
|
||||||
|
initialise: async () => {
|
||||||
|
if (!initialise) throw new Error("Initialisation handler was not registered");
|
||||||
|
return await initialise();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
describe("NodeKeyValueDBService.openSimpleStore", () => {
|
describe("NodeKeyValueDBService.openSimpleStore", () => {
|
||||||
it("creates a namespaced store handle before the backing database is initialised", () => {
|
it("creates a namespaced store handle before the backing database is initialised", () => {
|
||||||
const dependencies = {
|
const dependencies = {
|
||||||
@@ -44,4 +75,28 @@ describe("NodeKeyValueDBService.openSimpleStore", () => {
|
|||||||
|
|
||||||
await expect(store.get("key")).rejects.toThrow("KeyValueDB is not initialized yet");
|
await expect(store.get("key")).rejects.toThrow("KeyValueDB is not initialized yet");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("preserves bigint values used by Adaptive Journal writer state", async () => {
|
||||||
|
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "livesync-node-kv-bigint-"));
|
||||||
|
const filePath = path.join(tempDir, "keyvalue-db.json");
|
||||||
|
const writerState = {
|
||||||
|
writerEpoch: 9007199254740993n,
|
||||||
|
nested: [0n, { sequence: 18446744073709551615n }],
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const firstLifecycle = createInitialisableDependencies();
|
||||||
|
const first = new NodeKeyValueDBService(createServiceContext(), firstLifecycle.dependencies, filePath);
|
||||||
|
await expect(firstLifecycle.initialise()).resolves.toBe(true);
|
||||||
|
await first.openSimpleStore("adaptive").set("writer-state", writerState);
|
||||||
|
|
||||||
|
const secondLifecycle = createInitialisableDependencies();
|
||||||
|
const second = new NodeKeyValueDBService(createServiceContext(), secondLifecycle.dependencies, filePath);
|
||||||
|
await expect(secondLifecycle.initialise()).resolves.toBe(true);
|
||||||
|
|
||||||
|
await expect(second.openSimpleStore("adaptive").get("writer-state")).resolves.toEqual(writerState);
|
||||||
|
} finally {
|
||||||
|
await fs.rm(tempDir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user