nano-git 0.2.1 → 0.2.2
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/dist/workdir/change-log.d.mts +25 -0
- package/dist/workdir/change-log.mjs +13 -7
- package/dist/workdir/core.d.mts +23 -1
- package/dist/workdir/file-backend.d.mts +22 -0
- package/dist/workdir/file-backend.mjs +366 -0
- package/dist/workdir/file.d.mts +2 -0
- package/dist/workdir/file.mjs +2 -0
- package/dist/workdir/ids.d.mts +7 -0
- package/dist/workdir/memory-backend.d.mts +17 -0
- package/dist/workdir/memory-backend.mjs +146 -8
- package/dist/workdir/memory.d.mts +3 -2
- package/dist/workdir/memory.mjs +3 -2
- package/dist/workdir/nodes.d.mts +57 -0
- package/dist/workdir/overlay.d.mts +14 -0
- package/dist/workdir/session-id.mjs +18 -0
- package/dist/workdir/session-internal.mjs +10 -10
- package/dist/workdir/session.d.mts +13 -1
- package/dist/workdir/session.mjs +232 -205
- package/dist/workdir/sqlite-backend.d.mts +30 -0
- package/dist/workdir/sqlite-backend.mjs +363 -0
- package/dist/workdir/sqlite.d.mts +2 -0
- package/dist/workdir/sqlite.mjs +2 -0
- package/dist/workdir/state-store.d.mts +44 -0
- package/dist/workdir/write-tree.mjs +4 -4
- package/package.json +7 -3
package/dist/workdir/session.mjs
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { VirtualNotDirectoryError, VirtualNotFileError, VirtualNotSymlinkError, VirtualPathAlreadyExistsError, VirtualPathNotFoundError, VirtualRevertNotSupportedError } from "../core/errors.mjs";
|
|
2
|
+
import { mapInternalChangesToVirtualChanges } from "./change-log.mjs";
|
|
2
3
|
import { VIRTUAL_ROOT_NODE_ID, createNodeId } from "./ids.mjs";
|
|
3
4
|
import { overlayBindEntry, overlayRenameEntry, overlayTombstoneEntry } from "./overlay.mjs";
|
|
4
|
-
import { cloneSessionNodeForCopy,
|
|
5
|
-
import { createVirtualWorkdirMemoryState } from "./memory-backend.mjs";
|
|
5
|
+
import { cloneSessionNodeForCopy, revertNodeState } from "./nodes.mjs";
|
|
6
6
|
import { modeToVirtualEntryKind, readRepoBlobContent } from "./origin.mjs";
|
|
7
7
|
import { assertValidVirtualPath, baseName, normalizeDirectoryPath, parentPath } from "./path.mjs";
|
|
8
8
|
import { listDirectoryChildren, resolveChild, resolvePath } from "./session-internal.mjs";
|
|
9
9
|
import { writeTreeFromSession } from "./write-tree.mjs";
|
|
10
|
+
import { createVirtualWorkdirMemoryStateStore } from "./memory-backend.mjs";
|
|
10
11
|
//#region src/workdir/session.ts
|
|
11
12
|
/**
|
|
12
13
|
* VirtualWorkdirSession 行为编排
|
|
@@ -25,12 +26,22 @@ import { writeTreeFromSession } from "./write-tree.mjs";
|
|
|
25
26
|
* ```
|
|
26
27
|
*/
|
|
27
28
|
function createVirtualWorkdirSession(source, options) {
|
|
28
|
-
return
|
|
29
|
+
return openVirtualWorkdirSession(source, createVirtualWorkdirMemoryStateStore(options.baseTree));
|
|
29
30
|
}
|
|
30
|
-
|
|
31
|
+
/**
|
|
32
|
+
* 基于已有状态存储打开 session
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```ts
|
|
36
|
+
* const store = createVirtualWorkdirMemoryStateStore(tree);
|
|
37
|
+
* const session = openVirtualWorkdirSession(repo.objects, store);
|
|
38
|
+
* expect(session.baseTree).toBe(tree);
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
function openVirtualWorkdirSession(source, state) {
|
|
31
42
|
return {
|
|
32
43
|
get baseTree() {
|
|
33
|
-
return state.
|
|
44
|
+
return state.readBaseTree();
|
|
34
45
|
},
|
|
35
46
|
exists(path) {
|
|
36
47
|
if (path === "") return true;
|
|
@@ -80,240 +91,256 @@ function buildSessionApi(source, state) {
|
|
|
80
91
|
throw new VirtualPathNotFoundError(path);
|
|
81
92
|
},
|
|
82
93
|
mkdir(path) {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
94
|
+
runInWriteTransaction(state, () => {
|
|
95
|
+
assertValidVirtualPath(path);
|
|
96
|
+
const parent = parentPath(path);
|
|
97
|
+
const name = baseName(path);
|
|
98
|
+
const parentResolved = parent !== null ? resolvePath(source, state, parent) : {
|
|
99
|
+
found: true,
|
|
100
|
+
node: getRootNode(state)
|
|
101
|
+
};
|
|
102
|
+
if (!parentResolved.found || parentResolved.node === null) throw new VirtualPathNotFoundError(parent ?? path);
|
|
103
|
+
const parentNode = parentResolved.node;
|
|
104
|
+
if (parentNode.state.kind !== "directory") throw new VirtualNotDirectoryError(parent ?? path);
|
|
105
|
+
const existing = resolveChild(source, state, parentNode, parent ?? "", name);
|
|
106
|
+
if (existing.found && existing.node !== null) throw new VirtualPathAlreadyExistsError(path);
|
|
107
|
+
const nodeId = createNodeId();
|
|
108
|
+
const newNode = {
|
|
109
|
+
id: nodeId,
|
|
110
|
+
origin: { kind: "none" },
|
|
111
|
+
state: {
|
|
112
|
+
kind: "directory",
|
|
113
|
+
overlay: {
|
|
114
|
+
addedEntries: /* @__PURE__ */ new Map(),
|
|
115
|
+
deletedNames: /* @__PURE__ */ new Set()
|
|
116
|
+
}
|
|
104
117
|
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
118
|
+
};
|
|
119
|
+
state.setNode(newNode);
|
|
120
|
+
updateParentOverlay(state, parentNode.id, overlayBindEntry(parentNode.state.overlay, name, nodeId));
|
|
121
|
+
state.appendChange({
|
|
122
|
+
op: "add",
|
|
123
|
+
path
|
|
124
|
+
});
|
|
112
125
|
});
|
|
113
126
|
},
|
|
114
127
|
writeFile(path, content, options) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
if (existing.node
|
|
129
|
-
|
|
130
|
-
const isNew = !existing.found || existing.node === null;
|
|
131
|
-
const nodeId = existing.found ? existing.node.id : createNodeId();
|
|
132
|
-
const fileNode = {
|
|
133
|
-
id: nodeId,
|
|
134
|
-
origin: existing.found ? existing.node.origin : { kind: "none" },
|
|
135
|
-
state: {
|
|
136
|
-
kind: "file",
|
|
137
|
-
mode,
|
|
138
|
-
content
|
|
128
|
+
runInWriteTransaction(state, () => {
|
|
129
|
+
assertValidVirtualPath(path);
|
|
130
|
+
const mode = options?.mode ?? "100644";
|
|
131
|
+
const parent = parentPath(path);
|
|
132
|
+
const name = baseName(path);
|
|
133
|
+
const parentResolved = parent !== null ? resolvePath(source, state, parent) : {
|
|
134
|
+
found: true,
|
|
135
|
+
node: getRootNode(state)
|
|
136
|
+
};
|
|
137
|
+
if (!parentResolved.found || parentResolved.node === null) throw new VirtualPathNotFoundError(parent ?? path);
|
|
138
|
+
const parentNode = parentResolved.node;
|
|
139
|
+
if (parentNode.state.kind !== "directory") throw new VirtualNotDirectoryError(parent ?? path);
|
|
140
|
+
const existing = resolveChild(source, state, parentNode, parent ?? "", name);
|
|
141
|
+
if (existing.found && existing.node !== null) {
|
|
142
|
+
if (existing.node.state.kind === "directory") throw new VirtualNotFileError(path);
|
|
139
143
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
144
|
+
const isNew = !existing.found || existing.node === null;
|
|
145
|
+
const nodeId = existing.found ? existing.node.id : createNodeId();
|
|
146
|
+
const fileNode = {
|
|
147
|
+
id: nodeId,
|
|
148
|
+
origin: existing.found ? existing.node.origin : { kind: "none" },
|
|
149
|
+
state: {
|
|
150
|
+
kind: "file",
|
|
151
|
+
mode,
|
|
152
|
+
content
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
state.setNode(fileNode);
|
|
156
|
+
updateParentOverlay(state, parentNode.id, overlayBindEntry(parentNode.state.overlay, name, nodeId));
|
|
157
|
+
state.appendChange({
|
|
158
|
+
op: isNew ? "add" : "modify",
|
|
159
|
+
path
|
|
160
|
+
});
|
|
146
161
|
});
|
|
147
162
|
},
|
|
148
163
|
writeLink(path, target) {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
if (existing.node
|
|
162
|
-
|
|
163
|
-
const isNew = !existing.found || existing.node === null;
|
|
164
|
-
const nodeId = existing.found ? existing.node.id : createNodeId();
|
|
165
|
-
const linkNode = {
|
|
166
|
-
id: nodeId,
|
|
167
|
-
origin: existing.found ? existing.node.origin : { kind: "none" },
|
|
168
|
-
state: {
|
|
169
|
-
kind: "symlink",
|
|
170
|
-
mode: "120000",
|
|
171
|
-
target: Buffer.from(target)
|
|
164
|
+
runInWriteTransaction(state, () => {
|
|
165
|
+
assertValidVirtualPath(path);
|
|
166
|
+
const parent = parentPath(path);
|
|
167
|
+
const name = baseName(path);
|
|
168
|
+
const parentResolved = parent !== null ? resolvePath(source, state, parent) : {
|
|
169
|
+
found: true,
|
|
170
|
+
node: getRootNode(state)
|
|
171
|
+
};
|
|
172
|
+
if (!parentResolved.found || parentResolved.node === null) throw new VirtualPathNotFoundError(parent ?? path);
|
|
173
|
+
const parentNode = parentResolved.node;
|
|
174
|
+
if (parentNode.state.kind !== "directory") throw new VirtualNotDirectoryError(parent ?? path);
|
|
175
|
+
const existing = resolveChild(source, state, parentNode, parent ?? "", name);
|
|
176
|
+
if (existing.found && existing.node !== null) {
|
|
177
|
+
if (existing.node.state.kind === "directory") throw new VirtualNotFileError(path);
|
|
172
178
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
+
const isNew = !existing.found || existing.node === null;
|
|
180
|
+
const nodeId = existing.found ? existing.node.id : createNodeId();
|
|
181
|
+
const linkNode = {
|
|
182
|
+
id: nodeId,
|
|
183
|
+
origin: existing.found ? existing.node.origin : { kind: "none" },
|
|
184
|
+
state: {
|
|
185
|
+
kind: "symlink",
|
|
186
|
+
mode: "120000",
|
|
187
|
+
target: Buffer.from(target)
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
state.setNode(linkNode);
|
|
191
|
+
updateParentOverlay(state, parentNode.id, overlayBindEntry(parentNode.state.overlay, name, nodeId));
|
|
192
|
+
state.appendChange({
|
|
193
|
+
op: isNew ? "add" : "modify",
|
|
194
|
+
path
|
|
195
|
+
});
|
|
179
196
|
});
|
|
180
197
|
},
|
|
181
198
|
delete(path) {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
199
|
+
runInWriteTransaction(state, () => {
|
|
200
|
+
assertValidVirtualPath(path);
|
|
201
|
+
const parent = parentPath(path);
|
|
202
|
+
const name = baseName(path);
|
|
203
|
+
const parentResolved = parent !== null ? resolvePath(source, state, parent) : {
|
|
204
|
+
found: true,
|
|
205
|
+
node: getRootNode(state)
|
|
206
|
+
};
|
|
207
|
+
if (!parentResolved.found || parentResolved.node === null) throw new VirtualPathNotFoundError(parent ?? path);
|
|
208
|
+
const parentNode = parentResolved.node;
|
|
209
|
+
if (parentNode.state.kind !== "directory") throw new VirtualNotDirectoryError(parent ?? path);
|
|
210
|
+
const existing = resolveChild(source, state, parentNode, parent ?? "", name);
|
|
211
|
+
if (!existing.found || existing.node === null) throw new VirtualPathNotFoundError(path);
|
|
212
|
+
updateParentOverlay(state, parentNode.id, overlayTombstoneEntry(parentNode.state.overlay, name));
|
|
213
|
+
state.appendChange({
|
|
214
|
+
op: "delete",
|
|
215
|
+
path
|
|
216
|
+
});
|
|
198
217
|
});
|
|
199
218
|
},
|
|
200
219
|
rename(from, to) {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
220
|
+
runInWriteTransaction(state, () => {
|
|
221
|
+
assertValidVirtualPath(from);
|
|
222
|
+
assertValidVirtualPath(to);
|
|
223
|
+
if (from === to) return;
|
|
224
|
+
const fromParent = parentPath(from);
|
|
225
|
+
const fromName = baseName(from);
|
|
226
|
+
const fromParentResolved = fromParent !== null ? resolvePath(source, state, fromParent) : {
|
|
227
|
+
found: true,
|
|
228
|
+
node: getRootNode(state)
|
|
229
|
+
};
|
|
230
|
+
if (!fromParentResolved.found || fromParentResolved.node === null) throw new VirtualPathNotFoundError(from);
|
|
231
|
+
const fromParentNode = fromParentResolved.node;
|
|
232
|
+
if (fromParentNode.state.kind !== "directory") throw new VirtualNotDirectoryError(from);
|
|
233
|
+
const fromChild = resolveChild(source, state, fromParentNode, fromParent ?? "", fromName);
|
|
234
|
+
if (!fromChild.found || fromChild.node === null) throw new VirtualPathNotFoundError(from);
|
|
235
|
+
const sourceNode = fromChild.node;
|
|
236
|
+
const toParent = parentPath(to);
|
|
237
|
+
const toName = baseName(to);
|
|
238
|
+
const toParentResolved = toParent !== null ? resolvePath(source, state, toParent) : {
|
|
239
|
+
found: true,
|
|
240
|
+
node: getRootNode(state)
|
|
241
|
+
};
|
|
242
|
+
if (!toParentResolved.found || toParentResolved.node === null) throw new VirtualPathNotFoundError(to);
|
|
243
|
+
const toParentNode = toParentResolved.node;
|
|
244
|
+
if (toParentNode.state.kind !== "directory") throw new VirtualNotDirectoryError(to);
|
|
245
|
+
const toExisting = resolveChild(source, state, toParentNode, toParent ?? "", toName);
|
|
246
|
+
if (toExisting.found && toExisting.node !== null) throw new VirtualPathAlreadyExistsError(to);
|
|
247
|
+
if (sourceNode.state.kind === "directory") {
|
|
248
|
+
const toPath = to;
|
|
249
|
+
const fromPath = from;
|
|
250
|
+
if (toPath.startsWith(fromPath + "/") || toPath === fromPath) throw new Error(`Cannot rename '${from}' to '${to}': destination is a subdirectory of source`);
|
|
251
|
+
}
|
|
252
|
+
if (fromParentNode.id === toParentNode.id) updateParentOverlay(state, fromParentNode.id, overlayRenameEntry(fromParentNode.state.overlay, fromName, toName, sourceNode.id));
|
|
253
|
+
else {
|
|
254
|
+
updateParentOverlay(state, fromParentNode.id, overlayTombstoneEntry(fromParentNode.state.overlay, fromName));
|
|
255
|
+
updateParentOverlay(state, toParentNode.id, overlayBindEntry(toParentNode.state.overlay, toName, sourceNode.id));
|
|
256
|
+
}
|
|
257
|
+
state.appendChange({
|
|
258
|
+
op: "rename",
|
|
259
|
+
from,
|
|
260
|
+
to
|
|
261
|
+
});
|
|
241
262
|
});
|
|
242
263
|
},
|
|
243
264
|
copy(from, to) {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
265
|
+
runInWriteTransaction(state, () => {
|
|
266
|
+
assertValidVirtualPath(from);
|
|
267
|
+
assertValidVirtualPath(to);
|
|
268
|
+
if (from === to) throw new VirtualPathAlreadyExistsError(to);
|
|
269
|
+
const fromParent = parentPath(from);
|
|
270
|
+
const fromName = baseName(from);
|
|
271
|
+
const fromParentResolved = fromParent !== null ? resolvePath(source, state, fromParent) : {
|
|
272
|
+
found: true,
|
|
273
|
+
node: getRootNode(state)
|
|
274
|
+
};
|
|
275
|
+
if (!fromParentResolved.found || fromParentResolved.node === null) throw new VirtualPathNotFoundError(from);
|
|
276
|
+
const fromParentNode = fromParentResolved.node;
|
|
277
|
+
if (fromParentNode.state.kind !== "directory") throw new VirtualNotDirectoryError(from);
|
|
278
|
+
const fromChild = resolveChild(source, state, fromParentNode, fromParent ?? "", fromName);
|
|
279
|
+
if (!fromChild.found || fromChild.node === null) throw new VirtualPathNotFoundError(from);
|
|
280
|
+
const sourceNode = fromChild.node;
|
|
281
|
+
const toParent = parentPath(to);
|
|
282
|
+
const toName = baseName(to);
|
|
283
|
+
const toParentResolved = toParent !== null ? resolvePath(source, state, toParent) : {
|
|
284
|
+
found: true,
|
|
285
|
+
node: getRootNode(state)
|
|
286
|
+
};
|
|
287
|
+
if (!toParentResolved.found || toParentResolved.node === null) throw new VirtualPathNotFoundError(to);
|
|
288
|
+
const toParentNode = toParentResolved.node;
|
|
289
|
+
if (toParentNode.state.kind !== "directory") throw new VirtualNotDirectoryError(to);
|
|
290
|
+
const toExisting = resolveChild(source, state, toParentNode, toParent ?? "", toName);
|
|
291
|
+
if (toExisting.found && toExisting.node !== null) throw new VirtualPathAlreadyExistsError(to);
|
|
292
|
+
const newNodeId = cloneNodeGraphForCopy(source, state, sourceNode, from);
|
|
293
|
+
updateParentOverlay(state, toParentNode.id, overlayBindEntry(toParentNode.state.overlay, toName, newNodeId));
|
|
294
|
+
state.appendChange({
|
|
295
|
+
op: "copy",
|
|
296
|
+
from,
|
|
297
|
+
to
|
|
298
|
+
});
|
|
276
299
|
});
|
|
277
300
|
},
|
|
278
301
|
revert(path) {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
302
|
+
runInWriteTransaction(state, () => {
|
|
303
|
+
assertValidVirtualPath(path);
|
|
304
|
+
const resolved = resolvePath(source, state, path);
|
|
305
|
+
if (!resolved.found || resolved.node === null) throw new VirtualPathNotFoundError(path);
|
|
306
|
+
const node = resolved.node;
|
|
307
|
+
const reverted = revertNodeState(node);
|
|
308
|
+
if (reverted === node) throw new VirtualRevertNotSupportedError(path);
|
|
309
|
+
state.setNode(reverted);
|
|
310
|
+
state.appendChange({
|
|
311
|
+
op: "revert",
|
|
312
|
+
path
|
|
313
|
+
});
|
|
289
314
|
});
|
|
290
315
|
},
|
|
291
316
|
writeTree() {
|
|
292
317
|
return writeTreeFromSession(source, state);
|
|
293
318
|
},
|
|
294
319
|
reset(baseTree) {
|
|
295
|
-
state
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
state.changeLog.clear();
|
|
320
|
+
runInWriteTransaction(state, () => {
|
|
321
|
+
state.reset(baseTree);
|
|
322
|
+
});
|
|
299
323
|
},
|
|
300
324
|
listChanges() {
|
|
301
|
-
return state.
|
|
325
|
+
return mapInternalChangesToVirtualChanges(state.listChangeRecords());
|
|
302
326
|
}
|
|
303
327
|
};
|
|
304
328
|
}
|
|
305
329
|
function getRootNode(state) {
|
|
306
|
-
const root = state.
|
|
307
|
-
if (root ===
|
|
330
|
+
const root = state.getNode(VIRTUAL_ROOT_NODE_ID);
|
|
331
|
+
if (root === null) throw new Error("Virtual workdir session is missing root node");
|
|
308
332
|
return root;
|
|
309
333
|
}
|
|
334
|
+
function runInWriteTransaction(state, fn) {
|
|
335
|
+
return state.transact(fn);
|
|
336
|
+
}
|
|
310
337
|
/**
|
|
311
338
|
* 更新父节点的 overlay(创建新节点对象替代 Map 中的旧引用)
|
|
312
339
|
*/
|
|
313
340
|
function updateParentOverlay(state, parentId, newOverlay) {
|
|
314
|
-
const parentNode = state.
|
|
315
|
-
if (
|
|
316
|
-
state.
|
|
341
|
+
const parentNode = state.getNode(parentId);
|
|
342
|
+
if (parentNode === null || parentNode.state.kind !== "directory") throw new Error("updateParentOverlay: parent is not a directory");
|
|
343
|
+
state.setNode({
|
|
317
344
|
...parentNode,
|
|
318
345
|
state: {
|
|
319
346
|
...parentNode.state,
|
|
@@ -357,17 +384,17 @@ function statDirectoryNode(node) {
|
|
|
357
384
|
function cloneNodeGraphForCopy(source, state, node, path) {
|
|
358
385
|
const newNodeId = createNodeId();
|
|
359
386
|
const cloned = cloneSessionNodeForCopy(node, newNodeId);
|
|
360
|
-
state.
|
|
387
|
+
state.setNode(cloned);
|
|
361
388
|
if (node.state.kind !== "directory" || cloned.state.kind !== "directory") return newNodeId;
|
|
362
389
|
let overlay = cloned.state.overlay;
|
|
363
390
|
const children = listDirectoryChildren(source, state, node, path);
|
|
364
391
|
for (const child of children) {
|
|
365
|
-
const childNode = state.
|
|
366
|
-
if (childNode ===
|
|
392
|
+
const childNode = state.getNode(child.nodeId);
|
|
393
|
+
if (childNode === null) continue;
|
|
367
394
|
const clonedChildId = cloneNodeGraphForCopy(source, state, childNode, path === "" ? child.name : `${path}/${child.name}`);
|
|
368
395
|
overlay = overlayBindEntry(overlay, child.name, clonedChildId);
|
|
369
396
|
}
|
|
370
|
-
state.
|
|
397
|
+
state.setNode({
|
|
371
398
|
...cloned,
|
|
372
399
|
state: {
|
|
373
400
|
kind: "directory",
|
|
@@ -377,4 +404,4 @@ function cloneNodeGraphForCopy(source, state, node, path) {
|
|
|
377
404
|
return newNodeId;
|
|
378
405
|
}
|
|
379
406
|
//#endregion
|
|
380
|
-
export { createVirtualWorkdirSession };
|
|
407
|
+
export { createVirtualWorkdirSession, openVirtualWorkdirSession };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { VirtualWorkdirBackend } from "./core.mjs";
|
|
2
|
+
import { Database } from "bun:sqlite";
|
|
3
|
+
|
|
4
|
+
//#region src/workdir/sqlite-backend.d.ts
|
|
5
|
+
/** 创建 SQLite Virtual Workdir backend 的可选参数 */
|
|
6
|
+
interface CreateSqliteVirtualWorkdirBackendOptions {
|
|
7
|
+
/** 开启 WAL 模式,默认 true */
|
|
8
|
+
readonly walMode?: boolean;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* SQLite Virtual Workdir backend(含资源释放能力)
|
|
12
|
+
*/
|
|
13
|
+
interface SqliteVirtualWorkdirBackend extends VirtualWorkdirBackend {
|
|
14
|
+
/** 释放 SQLite 数据库连接 */
|
|
15
|
+
[Symbol.dispose](): void;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* 创建基于 SQLite 的 Virtual Workdir backend
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```ts
|
|
22
|
+
* using backend = createSqliteVirtualWorkdirBackend(":memory:");
|
|
23
|
+
* const sessionId = backend.createSession({ baseTree: tree });
|
|
24
|
+
* const session = backend.openSession(repo.objects, sessionId);
|
|
25
|
+
* expect(session.baseTree).toBe(tree);
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
declare function createSqliteVirtualWorkdirBackend(dbPath: string, options?: CreateSqliteVirtualWorkdirBackendOptions): SqliteVirtualWorkdirBackend;
|
|
29
|
+
//#endregion
|
|
30
|
+
export { CreateSqliteVirtualWorkdirBackendOptions, SqliteVirtualWorkdirBackend, createSqliteVirtualWorkdirBackend };
|