mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
feat(storage): replication (#11696)
Adds experimental session sync (`@earendil-works/pi-durable/sync`). Devices upload and pull a session's commits through a remote, reading them from the Chord store's outbox and applying them with `applyBatch`. If two devices continue the same session, the one that uploads second forks to a new session. I took out the extra sequence indexes from the earlier version of this branch, since the Chord outbox already persists every commit, so this no longer touches storage. --------- Co-authored-by: Armin Ronacher <armin.ronacher@active-4.com>
This commit is contained in:
parent
d7a988d18d
commit
e5f3b41506
19 changed files with 2927 additions and 0 deletions
59
packages/chord/test/sync/node-files.ts
Normal file
59
packages/chord/test/sync/node-files.ts
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
// `SyncFileSystem` over Node's file system, for the sync tests (a host passes its own environment, such as Pi Durable's
|
||||
// `NodeExecutionEnv`).
|
||||
|
||||
import { appendFile, mkdir, open, readdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import type { FileResult, SyncFileInfo, SyncFileReader, SyncFileSystem } from "../../src/sync/index.ts";
|
||||
|
||||
/** An error with a `code`: `not_found` for a missing file or directory, as `SyncFileSystem` reports it. */
|
||||
class FileError extends Error {
|
||||
readonly code: string;
|
||||
constructor(code: string, cause: unknown) {
|
||||
super(cause instanceof Error ? cause.message : String(cause), { cause });
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
|
||||
async function attempt<T>(operation: () => Promise<T>): Promise<FileResult<T>> {
|
||||
try {
|
||||
return { ok: true, value: await operation() };
|
||||
} catch (error) {
|
||||
const code = (error as NodeJS.ErrnoException).code === "ENOENT" ? "not_found" : "unknown";
|
||||
return { ok: false, error: new FileError(code, error) };
|
||||
}
|
||||
}
|
||||
|
||||
export const nodeFiles: SyncFileSystem = {
|
||||
joinPath: async (parts) => ({ ok: true, value: join(...parts) }),
|
||||
readTextFile: (path) => attempt(() => readFile(path, "utf8")),
|
||||
readBinaryFile: (path) => attempt(async () => new Uint8Array(await readFile(path))),
|
||||
openBinaryReader: (path) =>
|
||||
attempt(async (): Promise<SyncFileReader> => {
|
||||
const file = await open(path, "r");
|
||||
return {
|
||||
read: (offset, length) =>
|
||||
attempt(async () => {
|
||||
const buffer = new Uint8Array(length);
|
||||
const { bytesRead } = await file.read(buffer, 0, length, offset);
|
||||
return buffer.subarray(0, bytesRead);
|
||||
}),
|
||||
close: async () => {
|
||||
await file.close();
|
||||
},
|
||||
};
|
||||
}),
|
||||
writeFile: (path, content) => attempt(() => writeFile(path, content)),
|
||||
appendFile: (path, content) => attempt(() => appendFile(path, content)),
|
||||
renameFile: (source, destination) => attempt(() => rename(source, destination)),
|
||||
listDir: (path) =>
|
||||
attempt(async () =>
|
||||
(await readdir(path, { withFileTypes: true })).map(
|
||||
(entry): SyncFileInfo => ({
|
||||
name: entry.name,
|
||||
kind: entry.isDirectory() ? "directory" : entry.isSymbolicLink() ? "symlink" : "file",
|
||||
}),
|
||||
),
|
||||
),
|
||||
createDir: (path, options) => attempt(async () => void (await mkdir(path, options))),
|
||||
remove: (path, options) => attempt(() => rm(path, options)),
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue