@superblocksteam/workspace-sync 0.0.0-stage → 2.0.0-SNAPSHOT.e3d0ac5dd38ba62e
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.txt +87 -0
- package/README.md +51 -2
- package/dist/atomic-write.d.ts +10 -0
- package/dist/atomic-write.d.ts.map +1 -0
- package/dist/atomic-write.js +35 -0
- package/dist/atomic-write.js.map +1 -0
- package/dist/branch-write-guard.d.ts +32 -0
- package/dist/branch-write-guard.d.ts.map +1 -0
- package/dist/branch-write-guard.js +38 -0
- package/dist/branch-write-guard.js.map +1 -0
- package/dist/control.d.ts +237 -0
- package/dist/control.d.ts.map +1 -0
- package/dist/control.js +242 -0
- package/dist/control.js.map +1 -0
- package/dist/dbfs-transport.d.ts +54 -0
- package/dist/dbfs-transport.d.ts.map +1 -0
- package/dist/dbfs-transport.js +21 -0
- package/dist/dbfs-transport.js.map +1 -0
- package/dist/download.d.ts +54 -0
- package/dist/download.d.ts.map +1 -0
- package/dist/download.js +475 -0
- package/dist/download.js.map +1 -0
- package/dist/draft-helpers.d.ts +127 -0
- package/dist/draft-helpers.d.ts.map +1 -0
- package/dist/draft-helpers.js +840 -0
- package/dist/draft-helpers.js.map +1 -0
- package/dist/draft-journal-store.d.ts +30 -0
- package/dist/draft-journal-store.d.ts.map +1 -0
- package/dist/draft-journal-store.js +7 -0
- package/dist/draft-journal-store.js.map +1 -0
- package/dist/draft-state-report-metrics.d.ts +5 -0
- package/dist/draft-state-report-metrics.d.ts.map +1 -0
- package/dist/draft-state-report-metrics.js +36 -0
- package/dist/draft-state-report-metrics.js.map +1 -0
- package/dist/file-draft-journal-store.d.ts +26 -0
- package/dist/file-draft-journal-store.d.ts.map +1 -0
- package/dist/file-draft-journal-store.js +123 -0
- package/dist/file-draft-journal-store.js.map +1 -0
- package/dist/flush-scheduler.d.ts +24 -0
- package/dist/flush-scheduler.d.ts.map +1 -0
- package/dist/flush-scheduler.js +36 -0
- package/dist/flush-scheduler.js.map +1 -0
- package/dist/hash-cache.d.ts +16 -0
- package/dist/hash-cache.d.ts.map +1 -0
- package/dist/hash-cache.js +62 -0
- package/dist/hash-cache.js.map +1 -0
- package/dist/hash-dir-tree.d.ts +10 -0
- package/dist/hash-dir-tree.d.ts.map +1 -0
- package/dist/hash-dir-tree.js +53 -0
- package/dist/hash-dir-tree.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/list-dir.d.ts +31 -0
- package/dist/list-dir.d.ts.map +1 -0
- package/dist/list-dir.js +153 -0
- package/dist/list-dir.js.map +1 -0
- package/dist/logger.d.ts +16 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +16 -0
- package/dist/logger.js.map +1 -0
- package/dist/native-draft-session.d.ts +28 -0
- package/dist/native-draft-session.d.ts.map +1 -0
- package/dist/native-draft-session.js +154 -0
- package/dist/native-draft-session.js.map +1 -0
- package/dist/rpc-client.d.ts +7 -0
- package/dist/rpc-client.d.ts.map +1 -0
- package/dist/rpc-client.js +2 -0
- package/dist/rpc-client.js.map +1 -0
- package/dist/safe-directory-reader.d.ts +28 -0
- package/dist/safe-directory-reader.d.ts.map +1 -0
- package/dist/safe-directory-reader.js +136 -0
- package/dist/safe-directory-reader.js.map +1 -0
- package/dist/server-tree.d.ts +56 -0
- package/dist/server-tree.d.ts.map +1 -0
- package/dist/server-tree.js +179 -0
- package/dist/server-tree.js.map +1 -0
- package/dist/session-draft-journal-store.d.ts +42 -0
- package/dist/session-draft-journal-store.d.ts.map +1 -0
- package/dist/session-draft-journal-store.js +177 -0
- package/dist/session-draft-journal-store.js.map +1 -0
- package/dist/snapshot/take-snapshot.d.ts +41 -0
- package/dist/snapshot/take-snapshot.d.ts.map +1 -0
- package/dist/snapshot/take-snapshot.js +244 -0
- package/dist/snapshot/take-snapshot.js.map +1 -0
- package/dist/snapshot/types.d.ts +25 -0
- package/dist/snapshot/types.d.ts.map +1 -0
- package/dist/snapshot/types.js +3 -0
- package/dist/snapshot/types.js.map +1 -0
- package/dist/snapshot/upload-snapshot.d.ts +33 -0
- package/dist/snapshot/upload-snapshot.d.ts.map +1 -0
- package/dist/snapshot/upload-snapshot.js +113 -0
- package/dist/snapshot/upload-snapshot.js.map +1 -0
- package/dist/socket-dbfs-transport.d.ts +32 -0
- package/dist/socket-dbfs-transport.d.ts.map +1 -0
- package/dist/socket-dbfs-transport.js +129 -0
- package/dist/socket-dbfs-transport.js.map +1 -0
- package/dist/sync-credential-provider.d.ts +27 -0
- package/dist/sync-credential-provider.d.ts.map +1 -0
- package/dist/sync-credential-provider.js +22 -0
- package/dist/sync-credential-provider.js.map +1 -0
- package/dist/sync-engine.d.ts +141 -0
- package/dist/sync-engine.d.ts.map +1 -0
- package/dist/sync-engine.js +447 -0
- package/dist/sync-engine.js.map +1 -0
- package/dist/sync-paths.d.ts +61 -0
- package/dist/sync-paths.d.ts.map +1 -0
- package/dist/sync-paths.js +107 -0
- package/dist/sync-paths.js.map +1 -0
- package/dist/sync-telemetry.d.ts +12 -0
- package/dist/sync-telemetry.d.ts.map +1 -0
- package/dist/sync-telemetry.js +33 -0
- package/dist/sync-telemetry.js.map +1 -0
- package/dist/testing.d.ts +81 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +192 -0
- package/dist/testing.js.map +1 -0
- package/dist/workspace-state-store.d.ts +52 -0
- package/dist/workspace-state-store.d.ts.map +1 -0
- package/dist/workspace-state-store.js +45 -0
- package/dist/workspace-state-store.js.map +1 -0
- package/package.json +60 -3
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { DbfsTransport } from "./dbfs-transport.js";
|
|
2
|
+
import type { DirectoryState } from "./snapshot/types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Reads the whole tree `rootHash` from DBFS. Directories download skips
|
|
5
|
+
* (`node_modules`, `dist`, ...) are listed with empty contents and not
|
|
6
|
+
* descended into, so a polluted historical tree costs one listing.
|
|
7
|
+
*/
|
|
8
|
+
export declare function readServerTree(transport: Pick<DbfsTransport, "getDirectory">, rootHash: string): Promise<DirectoryState>;
|
|
9
|
+
/** A tree flattened to slash-separated paths of files and symlinks. */
|
|
10
|
+
export type FlatTree = Map<string, {
|
|
11
|
+
type: "-";
|
|
12
|
+
hash: string;
|
|
13
|
+
executable: boolean;
|
|
14
|
+
} | {
|
|
15
|
+
type: "l";
|
|
16
|
+
target: string;
|
|
17
|
+
}>;
|
|
18
|
+
/**
|
|
19
|
+
* Flattens a tree, leaving out download-skipped directories so a server tree
|
|
20
|
+
* and a local tree compare like for like.
|
|
21
|
+
*/
|
|
22
|
+
export declare function flattenTree(tree: {
|
|
23
|
+
contents: ReadonlyArray<{
|
|
24
|
+
type: "-";
|
|
25
|
+
name: string;
|
|
26
|
+
hash: string;
|
|
27
|
+
executable: boolean;
|
|
28
|
+
} | {
|
|
29
|
+
type: "l";
|
|
30
|
+
name: string;
|
|
31
|
+
target: string;
|
|
32
|
+
} | {
|
|
33
|
+
type: "d";
|
|
34
|
+
name: string;
|
|
35
|
+
contents: ReadonlyArray<unknown>;
|
|
36
|
+
}>;
|
|
37
|
+
}): FlatTree;
|
|
38
|
+
/** What {@link downloadServerTree} wrote. */
|
|
39
|
+
export interface DownloadStats {
|
|
40
|
+
files: number;
|
|
41
|
+
bytes: number;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Makes the local directory `root` match the server tree, touching only what
|
|
45
|
+
* differs from `local` (the local tree's hashed state, or undefined when the
|
|
46
|
+
* root is empty). Preserves the exec bit and symlinks, and never deletes
|
|
47
|
+
* sync-excluded directories such as `node_modules`. Returns how many files it
|
|
48
|
+
* wrote and their bytes.
|
|
49
|
+
*/
|
|
50
|
+
export declare function downloadServerTree({ transport, tree, local, root, }: {
|
|
51
|
+
transport: Pick<DbfsTransport, "getContents">;
|
|
52
|
+
tree: DirectoryState;
|
|
53
|
+
local: DirectoryState | undefined;
|
|
54
|
+
root: string;
|
|
55
|
+
}): Promise<DownloadStats>;
|
|
56
|
+
//# sourceMappingURL=server-tree.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-tree.d.ts","sourceRoot":"","sources":["../src/server-tree.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAe,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACtE,OAAO,KAAK,EAAsB,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAe9E;;;;GAIG;AACH,wBAAsB,cAAc,CAClC,SAAS,EAAE,IAAI,CAAC,aAAa,EAAE,cAAc,CAAC,EAC9C,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,cAAc,CAAC,CA2CzB;AAED,uEAAuE;AACvE,MAAM,MAAM,QAAQ,GAAG,GAAG,CACxB,MAAM,EACJ;IAAE,IAAI,EAAE,GAAG,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,OAAO,CAAA;CAAE,GAChD;IAAE,IAAI,EAAE,GAAG,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAChC,CAAC;AAEF;;;GAGG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE;IAChC,QAAQ,EAAE,aAAa,CACnB;QAAE,IAAI,EAAE,GAAG,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,OAAO,CAAA;KAAE,GAC9D;QAAE,IAAI,EAAE,GAAG,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAC3C;QAAE,IAAI,EAAE,GAAG,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,aAAa,CAAC,OAAO,CAAC,CAAA;KAAE,CAChE,CAAC;CACH,GAAG,QAAQ,CAsBX;AAED,6CAA6C;AAC7C,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAWD;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CAAC,EACvC,SAAS,EACT,IAAI,EACJ,KAAK,EACL,IAAI,GACL,EAAE;IACD,SAAS,EAAE,IAAI,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC;IAC9C,IAAI,EAAE,cAAc,CAAC;IACrB,KAAK,EAAE,cAAc,GAAG,SAAS,CAAC;IAClC,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,OAAO,CAAC,aAAa,CAAC,CAkGzB"}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import * as fsp from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { codeModeDownloadSkippedDirectories, codeModeSyncExcludedDirectories, } from "@superblocksteam/shared";
|
|
4
|
+
import { isSnapshotExcludedName } from "./sync-paths.js";
|
|
5
|
+
const DIRECTORY_CONCURRENCY = 16;
|
|
6
|
+
const CONTENT_BATCH = 256;
|
|
7
|
+
function isPrivateSnapshotEntry(entry) {
|
|
8
|
+
return ((entry.name !== ".superblocks" || entry.type !== "d") &&
|
|
9
|
+
isSnapshotExcludedName(entry.name));
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Reads the whole tree `rootHash` from DBFS. Directories download skips
|
|
13
|
+
* (`node_modules`, `dist`, ...) are listed with empty contents and not
|
|
14
|
+
* descended into, so a polluted historical tree costs one listing.
|
|
15
|
+
*/
|
|
16
|
+
export async function readServerTree(transport, rootHash) {
|
|
17
|
+
const pending = [];
|
|
18
|
+
const root = { hash: rootHash, contents: [] };
|
|
19
|
+
const visit = async (target) => {
|
|
20
|
+
const entries = await transport.getDirectory(target.hash);
|
|
21
|
+
for (const entry of entries) {
|
|
22
|
+
if (isPrivateSnapshotEntry(entry))
|
|
23
|
+
continue;
|
|
24
|
+
if (entry.type !== "d") {
|
|
25
|
+
target.contents.push(entry);
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
const child = { ...entry, contents: [] };
|
|
29
|
+
target.contents.push(child);
|
|
30
|
+
if (!codeModeDownloadSkippedDirectories.includes(entry.name))
|
|
31
|
+
pending.push(() => visit(child));
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
pending.push(() => visit(root));
|
|
35
|
+
// A bounded pool: workers keep pulling until no listing is left.
|
|
36
|
+
let active = 0;
|
|
37
|
+
await new Promise((resolve, reject) => {
|
|
38
|
+
let failed = false;
|
|
39
|
+
const pump = () => {
|
|
40
|
+
if (failed)
|
|
41
|
+
return;
|
|
42
|
+
if (pending.length === 0 && active === 0)
|
|
43
|
+
return resolve();
|
|
44
|
+
while (active < DIRECTORY_CONCURRENCY && pending.length > 0) {
|
|
45
|
+
const task = pending.shift();
|
|
46
|
+
active++;
|
|
47
|
+
task().then(() => {
|
|
48
|
+
active--;
|
|
49
|
+
pump();
|
|
50
|
+
}, (error) => {
|
|
51
|
+
failed = true;
|
|
52
|
+
reject(error);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
pump();
|
|
57
|
+
});
|
|
58
|
+
return root;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Flattens a tree, leaving out download-skipped directories so a server tree
|
|
62
|
+
* and a local tree compare like for like.
|
|
63
|
+
*/
|
|
64
|
+
export function flattenTree(tree) {
|
|
65
|
+
const flat = new Map();
|
|
66
|
+
const walk = (entries, prefix) => {
|
|
67
|
+
for (const entry of entries) {
|
|
68
|
+
if (isPrivateSnapshotEntry(entry))
|
|
69
|
+
continue;
|
|
70
|
+
const entryPath = prefix + entry.name;
|
|
71
|
+
if (entry.type === "d") {
|
|
72
|
+
if (codeModeDownloadSkippedDirectories.includes(entry.name))
|
|
73
|
+
continue;
|
|
74
|
+
walk(entry.contents, `${entryPath}/`);
|
|
75
|
+
}
|
|
76
|
+
else if (entry.type === "l") {
|
|
77
|
+
flat.set(entryPath, { type: "l", target: entry.target });
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
flat.set(entryPath, {
|
|
81
|
+
type: "-",
|
|
82
|
+
hash: entry.hash,
|
|
83
|
+
executable: entry.executable,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
walk(tree.contents, "");
|
|
89
|
+
return flat;
|
|
90
|
+
}
|
|
91
|
+
function fileMode(executable) {
|
|
92
|
+
return executable ? 0o755 : 0o644;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Makes the local directory `root` match the server tree, touching only what
|
|
96
|
+
* differs from `local` (the local tree's hashed state, or undefined when the
|
|
97
|
+
* root is empty). Preserves the exec bit and symlinks, and never deletes
|
|
98
|
+
* sync-excluded directories such as `node_modules`. Returns how many files it
|
|
99
|
+
* wrote and their bytes.
|
|
100
|
+
*/
|
|
101
|
+
export async function downloadServerTree({ transport, tree, local, root, }) {
|
|
102
|
+
await fsp.mkdir(root, { recursive: true });
|
|
103
|
+
const files = [];
|
|
104
|
+
const apply = async (server, existing, directory, prefix) => {
|
|
105
|
+
const localByName = new Map((existing ?? []).map((entry) => [entry.name, entry]));
|
|
106
|
+
for (const entry of server) {
|
|
107
|
+
let current = localByName.get(entry.name);
|
|
108
|
+
localByName.delete(entry.name);
|
|
109
|
+
if (codeModeDownloadSkippedDirectories.includes(entry.name) ||
|
|
110
|
+
isPrivateSnapshotEntry(entry))
|
|
111
|
+
continue;
|
|
112
|
+
const localPath = path.join(directory, entry.name);
|
|
113
|
+
const dbfsPath = prefix + entry.name;
|
|
114
|
+
if (current !== undefined && current.type !== entry.type) {
|
|
115
|
+
await fsp.rm(localPath, { recursive: true, force: true });
|
|
116
|
+
current = undefined;
|
|
117
|
+
}
|
|
118
|
+
switch (entry.type) {
|
|
119
|
+
case "-": {
|
|
120
|
+
const previous = current;
|
|
121
|
+
if (previous?.hash !== entry.hash) {
|
|
122
|
+
files.push({
|
|
123
|
+
path: dbfsPath,
|
|
124
|
+
hash: entry.hash,
|
|
125
|
+
localPath,
|
|
126
|
+
executable: entry.executable,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
else if (previous.executable !== entry.executable) {
|
|
130
|
+
await fsp.chmod(localPath, fileMode(entry.executable));
|
|
131
|
+
}
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
case "l": {
|
|
135
|
+
const previous = current;
|
|
136
|
+
if (previous?.target !== entry.target) {
|
|
137
|
+
await fsp.rm(localPath, { force: true });
|
|
138
|
+
await fsp.symlink(entry.target, localPath);
|
|
139
|
+
}
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
case "d": {
|
|
143
|
+
const previous = current;
|
|
144
|
+
if (previous === undefined)
|
|
145
|
+
await fsp.mkdir(localPath, { recursive: true });
|
|
146
|
+
if (previous?.hash !== entry.hash)
|
|
147
|
+
await apply(entry.contents, previous?.contents, localPath, `${dbfsPath}/`);
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
for (const entry of localByName.values()) {
|
|
153
|
+
if (isSnapshotExcludedName(entry.name) ||
|
|
154
|
+
(entry.type === "d" &&
|
|
155
|
+
codeModeSyncExcludedDirectories.includes(entry.name)))
|
|
156
|
+
continue;
|
|
157
|
+
await fsp.rm(path.join(directory, entry.name), {
|
|
158
|
+
recursive: true,
|
|
159
|
+
force: true,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
if (local?.hash !== tree.hash)
|
|
164
|
+
await apply(tree.contents, local?.contents, root, "");
|
|
165
|
+
let bytes = 0;
|
|
166
|
+
for (let idx = 0; idx < files.length; idx += CONTENT_BATCH) {
|
|
167
|
+
const batch = files.slice(idx, idx + CONTENT_BATCH);
|
|
168
|
+
const contents = await transport.getContents(tree.hash, batch.map(({ path: filePath, hash }) => ({ path: filePath, hash })));
|
|
169
|
+
for (const [index, file] of batch.entries()) {
|
|
170
|
+
const mode = fileMode(file.executable);
|
|
171
|
+
const content = contents[index];
|
|
172
|
+
bytes += content.length;
|
|
173
|
+
await fsp.writeFile(file.localPath, content, { mode });
|
|
174
|
+
await fsp.chmod(file.localPath, mode);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return { files: files.length, bytes };
|
|
178
|
+
}
|
|
179
|
+
//# sourceMappingURL=server-tree.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-tree.js","sourceRoot":"","sources":["../src/server-tree.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { DraftJournalRecord, DraftJournalStore } from "./draft-journal-store.js";
|
|
2
|
+
import type { StdISocketRPCClient } from "./rpc-client.js";
|
|
3
|
+
/** The pointer file's name inside the session state directory. */
|
|
4
|
+
export declare const SESSION_DRAFT_JOURNAL_FILE = "native-draft-journal.json";
|
|
5
|
+
/**
|
|
6
|
+
* Content-addressed blob storage for journal file contents. DBFS file contents
|
|
7
|
+
* satisfy it; hashes are DBFS content hashes (`hashFileContents`).
|
|
8
|
+
*/
|
|
9
|
+
export interface DraftJournalBlobStore {
|
|
10
|
+
/** Stores every buffer and returns their content hashes, in order. */
|
|
11
|
+
put(contents: readonly Buffer[]): Promise<string[]>;
|
|
12
|
+
/** The bytes stored under `hash`. */
|
|
13
|
+
get(hash: string): Promise<Buffer>;
|
|
14
|
+
}
|
|
15
|
+
/** {@link DraftJournalBlobStore} over DBFS `putMany` / `fileContents.get`. */
|
|
16
|
+
export declare function createSocketDraftJournalBlobStore(rpcClient: StdISocketRPCClient): DraftJournalBlobStore;
|
|
17
|
+
/**
|
|
18
|
+
* Keeps the journal as a small pointer record in session storage, with every
|
|
19
|
+
* file's contents stored as a DBFS blob referenced by content hash. A
|
|
20
|
+
* microVM's local disk then never holds the base64 copy of the tree, and a
|
|
21
|
+
* fresh process over the same state directory can still accept or discard the
|
|
22
|
+
* drafts of a session that was killed.
|
|
23
|
+
*
|
|
24
|
+
* Blobs are put before the pointer that references them is written, so a
|
|
25
|
+
* pointer on disk always resolves.
|
|
26
|
+
*/
|
|
27
|
+
export declare class SessionDraftJournalStore implements DraftJournalStore {
|
|
28
|
+
private readonly options;
|
|
29
|
+
private readonly stored;
|
|
30
|
+
constructor(options: {
|
|
31
|
+
/** Session state directory; must be outside the application root. */
|
|
32
|
+
stateDir: string;
|
|
33
|
+
blobs: DraftJournalBlobStore;
|
|
34
|
+
});
|
|
35
|
+
get file(): string;
|
|
36
|
+
read(): Promise<DraftJournalRecord | undefined>;
|
|
37
|
+
write(record: DraftJournalRecord): Promise<void>;
|
|
38
|
+
clear(): Promise<void>;
|
|
39
|
+
private put;
|
|
40
|
+
private fetch;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=session-draft-journal-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-draft-journal-store.d.ts","sourceRoot":"","sources":["../src/session-draft-journal-store.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EACV,kBAAkB,EAClB,iBAAiB,EAClB,MAAM,0BAA0B,CAAC;AAElC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAG3D,kEAAkE;AAClE,eAAO,MAAM,0BAA0B,8BAA8B,CAAC;AAoBtE;;;GAGG;AACH,MAAM,WAAW,qBAAqB;IACpC,sEAAsE;IACtE,GAAG,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACpD,qCAAqC;IACrC,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACpC;AAED,8EAA8E;AAC9E,wBAAgB,iCAAiC,CAC/C,SAAS,EAAE,mBAAmB,GAC7B,qBAAqB,CAwBvB;AAuBD;;;;;;;;;GASG;AACH,qBAAa,wBAAyB,YAAW,iBAAiB;IAI9D,OAAO,CAAC,QAAQ,CAAC,OAAO;IAH1B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqB;IAE5C,YACmB,OAAO,EAAE;QACxB,qEAAqE;QACrE,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,EAAE,qBAAqB,CAAC;KAC9B,EACC;IAEJ,IAAI,IAAI,IAAI,MAAM,CAEjB;IAEK,IAAI,IAAI,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAAC,CAwCpD;IAEK,KAAK,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAmBrD;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAE3B;YAEa,GAAG;YAuBH,KAAK;CAmBpB"}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { hashFileContents, unwrapResponseDto } from "@superblocksteam/shared";
|
|
5
|
+
import { writeFileAtomic } from "./atomic-write.js";
|
|
6
|
+
import { assertOutsideRoot } from "./file-draft-journal-store.js";
|
|
7
|
+
import { SafeDirectoryReader } from "./safe-directory-reader.js";
|
|
8
|
+
/** The pointer file's name inside the session state directory. */
|
|
9
|
+
export const SESSION_DRAFT_JOURNAL_FILE = "native-draft-journal.json";
|
|
10
|
+
const MAX_POINTER_BYTES = 8 * 1024 * 1024;
|
|
11
|
+
const PUT_BATCH_ITEMS = 64;
|
|
12
|
+
const PUT_BATCH_BYTES = 8 * 1024 * 1024;
|
|
13
|
+
const GET_CONCURRENCY = 16;
|
|
14
|
+
const pointerSchema = z.object({
|
|
15
|
+
version: z.literal(1),
|
|
16
|
+
root: z.string(),
|
|
17
|
+
scope: z.string(),
|
|
18
|
+
id: z.string(),
|
|
19
|
+
/** Relative path -> DBFS content hash. */
|
|
20
|
+
before: z.record(z.string()),
|
|
21
|
+
originals: z.record(z.string()),
|
|
22
|
+
phase: z.enum(["running", "complete"]),
|
|
23
|
+
hash: z.string().optional(),
|
|
24
|
+
});
|
|
25
|
+
/** {@link DraftJournalBlobStore} over DBFS `putMany` / `fileContents.get`. */
|
|
26
|
+
export function createSocketDraftJournalBlobStore(rpcClient) {
|
|
27
|
+
return {
|
|
28
|
+
async put(contents) {
|
|
29
|
+
const hashes = [];
|
|
30
|
+
for (const batch of batches(contents)) {
|
|
31
|
+
const response = await unwrapResponseDto(rpcClient.call.v1.dbfs.putMany({
|
|
32
|
+
items: batch.map((bytes) => ({
|
|
33
|
+
type: "fileContents",
|
|
34
|
+
contents: bytes.toString("base64"),
|
|
35
|
+
})),
|
|
36
|
+
}));
|
|
37
|
+
hashes.push(...response.items.map((item) => item.hash));
|
|
38
|
+
}
|
|
39
|
+
return hashes;
|
|
40
|
+
},
|
|
41
|
+
async get(hash) {
|
|
42
|
+
const response = await unwrapResponseDto(rpcClient.call.v1.dbfs.fileContents.get({ hash }));
|
|
43
|
+
return Buffer.from(response.contents, "base64");
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/** Splits `contents` into putMany-sized batches by item count and bytes. */
|
|
48
|
+
function* batches(contents) {
|
|
49
|
+
let batch = [];
|
|
50
|
+
let bytes = 0;
|
|
51
|
+
for (const item of contents) {
|
|
52
|
+
if (batch.length > 0 &&
|
|
53
|
+
(batch.length >= PUT_BATCH_ITEMS || bytes + item.length > PUT_BATCH_BYTES)) {
|
|
54
|
+
yield batch;
|
|
55
|
+
batch = [];
|
|
56
|
+
bytes = 0;
|
|
57
|
+
}
|
|
58
|
+
batch.push(item);
|
|
59
|
+
bytes += item.length;
|
|
60
|
+
}
|
|
61
|
+
if (batch.length > 0)
|
|
62
|
+
yield batch;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Keeps the journal as a small pointer record in session storage, with every
|
|
66
|
+
* file's contents stored as a DBFS blob referenced by content hash. A
|
|
67
|
+
* microVM's local disk then never holds the base64 copy of the tree, and a
|
|
68
|
+
* fresh process over the same state directory can still accept or discard the
|
|
69
|
+
* drafts of a session that was killed.
|
|
70
|
+
*
|
|
71
|
+
* Blobs are put before the pointer that references them is written, so a
|
|
72
|
+
* pointer on disk always resolves.
|
|
73
|
+
*/
|
|
74
|
+
export class SessionDraftJournalStore {
|
|
75
|
+
options;
|
|
76
|
+
stored = new Set();
|
|
77
|
+
constructor(options) {
|
|
78
|
+
this.options = options;
|
|
79
|
+
}
|
|
80
|
+
get file() {
|
|
81
|
+
return path.join(this.options.stateDir, SESSION_DRAFT_JOURNAL_FILE);
|
|
82
|
+
}
|
|
83
|
+
async read() {
|
|
84
|
+
const reader = await SafeDirectoryReader.open(this.options.stateDir).catch((error) => {
|
|
85
|
+
if (error.code === "ENOENT")
|
|
86
|
+
return undefined;
|
|
87
|
+
throw error;
|
|
88
|
+
});
|
|
89
|
+
if (!reader)
|
|
90
|
+
return undefined;
|
|
91
|
+
let contents;
|
|
92
|
+
try {
|
|
93
|
+
contents = await reader.readFile(SESSION_DRAFT_JOURNAL_FILE, MAX_POINTER_BYTES);
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
await reader.close();
|
|
97
|
+
}
|
|
98
|
+
if (!contents)
|
|
99
|
+
return undefined;
|
|
100
|
+
const pointer = pointerSchema.parse(JSON.parse(contents.toString("utf8")));
|
|
101
|
+
const blobs = await this.fetch([
|
|
102
|
+
...Object.values(pointer.before),
|
|
103
|
+
...Object.values(pointer.originals),
|
|
104
|
+
]);
|
|
105
|
+
const resolve = (files) => new Map(Object.entries(files).map(([relative, hash]) => [
|
|
106
|
+
relative,
|
|
107
|
+
blobs.get(hash),
|
|
108
|
+
]));
|
|
109
|
+
return {
|
|
110
|
+
root: pointer.root,
|
|
111
|
+
scope: pointer.scope,
|
|
112
|
+
id: pointer.id,
|
|
113
|
+
before: resolve(pointer.before),
|
|
114
|
+
originals: resolve(pointer.originals),
|
|
115
|
+
phase: pointer.phase,
|
|
116
|
+
...(pointer.hash === undefined ? {} : { hash: pointer.hash }),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
async write(record) {
|
|
120
|
+
await fs.mkdir(this.options.stateDir, { recursive: true, mode: 0o700 });
|
|
121
|
+
assertOutsideRoot(await fs.realpath(record.root), await fs.realpath(this.options.stateDir));
|
|
122
|
+
const before = await this.put(record.before);
|
|
123
|
+
const originals = await this.put(record.originals);
|
|
124
|
+
const pointer = {
|
|
125
|
+
version: 1,
|
|
126
|
+
root: record.root,
|
|
127
|
+
scope: record.scope,
|
|
128
|
+
id: record.id,
|
|
129
|
+
before,
|
|
130
|
+
originals,
|
|
131
|
+
phase: record.phase,
|
|
132
|
+
hash: record.hash,
|
|
133
|
+
};
|
|
134
|
+
await writeFileAtomic(this.file, JSON.stringify(pointer));
|
|
135
|
+
}
|
|
136
|
+
async clear() {
|
|
137
|
+
await fs.rm(this.file, { force: true });
|
|
138
|
+
}
|
|
139
|
+
async put(files) {
|
|
140
|
+
const references = {};
|
|
141
|
+
const missing = new Map();
|
|
142
|
+
for (const [relative, bytes] of files) {
|
|
143
|
+
const hash = await hashFileContents(bytes);
|
|
144
|
+
references[relative] = hash;
|
|
145
|
+
if (!this.stored.has(hash))
|
|
146
|
+
missing.set(hash, bytes);
|
|
147
|
+
}
|
|
148
|
+
if (missing.size > 0) {
|
|
149
|
+
const expected = [...missing.keys()];
|
|
150
|
+
const hashes = await this.options.blobs.put([...missing.values()]);
|
|
151
|
+
if (hashes.length !== expected.length ||
|
|
152
|
+
hashes.some((hash, index) => hash !== expected[index]))
|
|
153
|
+
throw new Error("DBFS returned unexpected hashes for journal blobs");
|
|
154
|
+
for (const hash of expected)
|
|
155
|
+
this.stored.add(hash);
|
|
156
|
+
}
|
|
157
|
+
return references;
|
|
158
|
+
}
|
|
159
|
+
async fetch(hashes) {
|
|
160
|
+
const unique = [...new Set(hashes)];
|
|
161
|
+
const blobs = new Map();
|
|
162
|
+
let next = 0;
|
|
163
|
+
const worker = async () => {
|
|
164
|
+
while (next < unique.length) {
|
|
165
|
+
const hash = unique[next++];
|
|
166
|
+
const bytes = await this.options.blobs.get(hash);
|
|
167
|
+
if ((await hashFileContents(bytes)) !== hash)
|
|
168
|
+
throw new Error("Native draft journal blob does not match its hash");
|
|
169
|
+
blobs.set(hash, bytes);
|
|
170
|
+
this.stored.add(hash);
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
await Promise.all(Array.from({ length: Math.min(GET_CONCURRENCY, unique.length) }, worker));
|
|
174
|
+
return blobs;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
//# sourceMappingURL=session-draft-journal-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-draft-journal-store.js","sourceRoot":"","sources":["../src/session-draft-journal-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { DirectorySnapshot } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Takes a snapshot of a local directory. The current implementation stores the snapshot in memory.
|
|
4
|
+
* This is not scalable for large directories and is subject to change.
|
|
5
|
+
*
|
|
6
|
+
* This function now captures the DRAFT state (current filesystem state).
|
|
7
|
+
* For committed state, use snapshotLocalDirectoryCommittedState.
|
|
8
|
+
*
|
|
9
|
+
* @param localDirectoryPath - The path to the local directory to snapshot.
|
|
10
|
+
* @returns A promise that resolves to a snapshot of the local directory.
|
|
11
|
+
*/
|
|
12
|
+
export declare function snapshotLocalDirectory(localDirectoryPath: string, rootDirectoryPath?: string): Promise<DirectorySnapshot>;
|
|
13
|
+
/**
|
|
14
|
+
* Takes a snapshot of a local directory showing the COMMITTED state.
|
|
15
|
+
* This function:
|
|
16
|
+
* - Uses .sbswap.edit content from the drafts directory instead of current files (for edited files)
|
|
17
|
+
* - Excludes files that have .sbswap.add markers in the drafts directory (draft additions)
|
|
18
|
+
* - Excludes .sbswap.edit and .sbswap.add marker files themselves
|
|
19
|
+
*
|
|
20
|
+
* @param localDirectoryPath - The path to the local directory to snapshot.
|
|
21
|
+
* @param appRootPath - The app root path (defaults to localDirectoryPath for top-level calls).
|
|
22
|
+
* @param editMarkerMap - Map of edit markers (for internal recursive use).
|
|
23
|
+
* @param addMarkerSet - Set of add markers (for internal recursive use).
|
|
24
|
+
* @returns A promise that resolves to a snapshot of the committed state.
|
|
25
|
+
*/
|
|
26
|
+
export declare function snapshotLocalDirectoryCommittedState(localDirectoryPath: string, appRootPath?: string, editMarkerMap?: Map<string, Buffer>, addMarkerSet?: Set<string>): Promise<DirectorySnapshot>;
|
|
27
|
+
/**
|
|
28
|
+
* Takes a snapshot of a local directory, prioritizing .sbswap files for content.
|
|
29
|
+
* .sbswap files themselves are not included in the snapshot output.
|
|
30
|
+
*
|
|
31
|
+
* @param localDirectoryPath - The path to the local directory to snapshot.
|
|
32
|
+
* @returns A promise that resolves to a snapshot of the local directory, reflecting draft states.
|
|
33
|
+
*/
|
|
34
|
+
export declare function snapshotLocalDirectoryWithDrafts(localDirectoryPath: string): Promise<DirectorySnapshot>;
|
|
35
|
+
/**
|
|
36
|
+
* Flattens a snapshot into relative path -> content hash. A symlink maps to its
|
|
37
|
+
* target, so retargeting it counts as a change. Directories are left out; their
|
|
38
|
+
* files carry the change.
|
|
39
|
+
*/
|
|
40
|
+
export declare function snapshotPathHashes(snapshot: DirectorySnapshot): Map<string, string>;
|
|
41
|
+
//# sourceMappingURL=take-snapshot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"take-snapshot.d.ts","sourceRoot":"","sources":["../../src/snapshot/take-snapshot.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,iBAAiB,EAAoB,MAAM,YAAY,CAAC;AAEtE;;;;;;;;;GASG;AACH,wBAAsB,sBAAsB,CAC1C,kBAAkB,EAAE,MAAM,EAC1B,iBAAiB,SAAqB,GACrC,OAAO,CAAC,iBAAiB,CAAC,CAqD5B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,oCAAoC,CACxD,kBAAkB,EAAE,MAAM,EAC1B,WAAW,CAAC,EAAE,MAAM,EACpB,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EACnC,YAAY,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GACzB,OAAO,CAAC,iBAAiB,CAAC,CAgH5B;AA4DD;;;;;;GAMG;AACH,wBAAsB,gCAAgC,CACpD,kBAAkB,EAAE,MAAM,GACzB,OAAO,CAAC,iBAAiB,CAAC,CAE5B;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,iBAAiB,GAC1B,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAiBrB"}
|