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.
@@ -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, createRootDirectoryNode, revertNodeState } from "./nodes.mjs";
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 buildSessionApi(source, createVirtualWorkdirMemoryState(options.baseTree));
29
+ return openVirtualWorkdirSession(source, createVirtualWorkdirMemoryStateStore(options.baseTree));
29
30
  }
30
- function buildSessionApi(source, state) {
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.baseTree;
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
- assertValidVirtualPath(path);
84
- const parent = parentPath(path);
85
- const name = baseName(path);
86
- const parentResolved = parent !== null ? resolvePath(source, state, parent) : {
87
- found: true,
88
- node: getRootNode(state)
89
- };
90
- if (!parentResolved.found || parentResolved.node === null) throw new VirtualPathNotFoundError(parent ?? path);
91
- const parentNode = parentResolved.node;
92
- if (parentNode.state.kind !== "directory") throw new VirtualNotDirectoryError(parent ?? path);
93
- const existing = resolveChild(source, state, parentNode, parent ?? "", name);
94
- if (existing.found && existing.node !== null) throw new VirtualPathAlreadyExistsError(path);
95
- const nodeId = createNodeId();
96
- const newNode = {
97
- id: nodeId,
98
- origin: { kind: "none" },
99
- state: {
100
- kind: "directory",
101
- overlay: {
102
- addedEntries: /* @__PURE__ */ new Map(),
103
- deletedNames: /* @__PURE__ */ new Set()
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
- state.nodes.set(nodeId, newNode);
108
- updateParentOverlay(state, parentNode.id, overlayBindEntry(parentNode.state.overlay, name, nodeId));
109
- state.changeLog.append({
110
- op: "add",
111
- path
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
- assertValidVirtualPath(path);
116
- const mode = options?.mode ?? "100644";
117
- const parent = parentPath(path);
118
- const name = baseName(path);
119
- const parentResolved = parent !== null ? resolvePath(source, state, parent) : {
120
- found: true,
121
- node: getRootNode(state)
122
- };
123
- if (!parentResolved.found || parentResolved.node === null) throw new VirtualPathNotFoundError(parent ?? path);
124
- const parentNode = parentResolved.node;
125
- if (parentNode.state.kind !== "directory") throw new VirtualNotDirectoryError(parent ?? path);
126
- const existing = resolveChild(source, state, parentNode, parent ?? "", name);
127
- if (existing.found && existing.node !== null) {
128
- if (existing.node.state.kind === "directory") throw new VirtualNotFileError(path);
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
- state.nodes.set(nodeId, fileNode);
142
- updateParentOverlay(state, parentNode.id, overlayBindEntry(parentNode.state.overlay, name, nodeId));
143
- state.changeLog.append({
144
- op: isNew ? "add" : "modify",
145
- path
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
- assertValidVirtualPath(path);
150
- const parent = parentPath(path);
151
- const name = baseName(path);
152
- const parentResolved = parent !== null ? resolvePath(source, state, parent) : {
153
- found: true,
154
- node: getRootNode(state)
155
- };
156
- if (!parentResolved.found || parentResolved.node === null) throw new VirtualPathNotFoundError(parent ?? path);
157
- const parentNode = parentResolved.node;
158
- if (parentNode.state.kind !== "directory") throw new VirtualNotDirectoryError(parent ?? path);
159
- const existing = resolveChild(source, state, parentNode, parent ?? "", name);
160
- if (existing.found && existing.node !== null) {
161
- if (existing.node.state.kind === "directory") throw new VirtualNotFileError(path);
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
- state.nodes.set(nodeId, linkNode);
175
- updateParentOverlay(state, parentNode.id, overlayBindEntry(parentNode.state.overlay, name, nodeId));
176
- state.changeLog.append({
177
- op: isNew ? "add" : "modify",
178
- path
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
- assertValidVirtualPath(path);
183
- const parent = parentPath(path);
184
- const name = baseName(path);
185
- const parentResolved = parent !== null ? resolvePath(source, state, parent) : {
186
- found: true,
187
- node: getRootNode(state)
188
- };
189
- if (!parentResolved.found || parentResolved.node === null) throw new VirtualPathNotFoundError(parent ?? path);
190
- const parentNode = parentResolved.node;
191
- if (parentNode.state.kind !== "directory") throw new VirtualNotDirectoryError(parent ?? path);
192
- const existing = resolveChild(source, state, parentNode, parent ?? "", name);
193
- if (!existing.found || existing.node === null) throw new VirtualPathNotFoundError(path);
194
- updateParentOverlay(state, parentNode.id, overlayTombstoneEntry(parentNode.state.overlay, name));
195
- state.changeLog.append({
196
- op: "delete",
197
- path
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
- assertValidVirtualPath(from);
202
- assertValidVirtualPath(to);
203
- if (from === to) return;
204
- const fromParent = parentPath(from);
205
- const fromName = baseName(from);
206
- const fromParentResolved = fromParent !== null ? resolvePath(source, state, fromParent) : {
207
- found: true,
208
- node: getRootNode(state)
209
- };
210
- if (!fromParentResolved.found || fromParentResolved.node === null) throw new VirtualPathNotFoundError(from);
211
- const fromParentNode = fromParentResolved.node;
212
- if (fromParentNode.state.kind !== "directory") throw new VirtualNotDirectoryError(from);
213
- const fromChild = resolveChild(source, state, fromParentNode, fromParent ?? "", fromName);
214
- if (!fromChild.found || fromChild.node === null) throw new VirtualPathNotFoundError(from);
215
- const sourceNode = fromChild.node;
216
- const toParent = parentPath(to);
217
- const toName = baseName(to);
218
- const toParentResolved = toParent !== null ? resolvePath(source, state, toParent) : {
219
- found: true,
220
- node: getRootNode(state)
221
- };
222
- if (!toParentResolved.found || toParentResolved.node === null) throw new VirtualPathNotFoundError(to);
223
- const toParentNode = toParentResolved.node;
224
- if (toParentNode.state.kind !== "directory") throw new VirtualNotDirectoryError(to);
225
- const toExisting = resolveChild(source, state, toParentNode, toParent ?? "", toName);
226
- if (toExisting.found && toExisting.node !== null) throw new VirtualPathAlreadyExistsError(to);
227
- if (sourceNode.state.kind === "directory") {
228
- const toPath = to;
229
- const fromPath = from;
230
- if (toPath.startsWith(fromPath + "/") || toPath === fromPath) throw new Error(`Cannot rename '${from}' to '${to}': destination is a subdirectory of source`);
231
- }
232
- if (fromParentNode.id === toParentNode.id) updateParentOverlay(state, fromParentNode.id, overlayRenameEntry(fromParentNode.state.overlay, fromName, toName, sourceNode.id));
233
- else {
234
- updateParentOverlay(state, fromParentNode.id, overlayTombstoneEntry(fromParentNode.state.overlay, fromName));
235
- updateParentOverlay(state, toParentNode.id, overlayBindEntry(toParentNode.state.overlay, toName, sourceNode.id));
236
- }
237
- state.changeLog.append({
238
- op: "rename",
239
- from,
240
- to
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
- assertValidVirtualPath(from);
245
- assertValidVirtualPath(to);
246
- if (from === to) throw new VirtualPathAlreadyExistsError(to);
247
- const fromParent = parentPath(from);
248
- const fromName = baseName(from);
249
- const fromParentResolved = fromParent !== null ? resolvePath(source, state, fromParent) : {
250
- found: true,
251
- node: getRootNode(state)
252
- };
253
- if (!fromParentResolved.found || fromParentResolved.node === null) throw new VirtualPathNotFoundError(from);
254
- const fromParentNode = fromParentResolved.node;
255
- if (fromParentNode.state.kind !== "directory") throw new VirtualNotDirectoryError(from);
256
- const fromChild = resolveChild(source, state, fromParentNode, fromParent ?? "", fromName);
257
- if (!fromChild.found || fromChild.node === null) throw new VirtualPathNotFoundError(from);
258
- const sourceNode = fromChild.node;
259
- const toParent = parentPath(to);
260
- const toName = baseName(to);
261
- const toParentResolved = toParent !== null ? resolvePath(source, state, toParent) : {
262
- found: true,
263
- node: getRootNode(state)
264
- };
265
- if (!toParentResolved.found || toParentResolved.node === null) throw new VirtualPathNotFoundError(to);
266
- const toParentNode = toParentResolved.node;
267
- if (toParentNode.state.kind !== "directory") throw new VirtualNotDirectoryError(to);
268
- const toExisting = resolveChild(source, state, toParentNode, toParent ?? "", toName);
269
- if (toExisting.found && toExisting.node !== null) throw new VirtualPathAlreadyExistsError(to);
270
- const newNodeId = cloneNodeGraphForCopy(source, state, sourceNode, from);
271
- updateParentOverlay(state, toParentNode.id, overlayBindEntry(toParentNode.state.overlay, toName, newNodeId));
272
- state.changeLog.append({
273
- op: "copy",
274
- from,
275
- to
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
- assertValidVirtualPath(path);
280
- const resolved = resolvePath(source, state, path);
281
- if (!resolved.found || resolved.node === null) throw new VirtualPathNotFoundError(path);
282
- const node = resolved.node;
283
- const reverted = revertNodeState(node);
284
- if (reverted === node) throw new VirtualRevertNotSupportedError(path);
285
- state.nodes.set(node.id, reverted);
286
- state.changeLog.append({
287
- op: "revert",
288
- path
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.baseTree = baseTree;
296
- state.nodes.clear();
297
- state.nodes.set(VIRTUAL_ROOT_NODE_ID, createRootDirectoryNode(baseTree));
298
- state.changeLog.clear();
320
+ runInWriteTransaction(state, () => {
321
+ state.reset(baseTree);
322
+ });
299
323
  },
300
324
  listChanges() {
301
- return state.changeLog.toVirtualChanges();
325
+ return mapInternalChangesToVirtualChanges(state.listChangeRecords());
302
326
  }
303
327
  };
304
328
  }
305
329
  function getRootNode(state) {
306
- const root = state.nodes.get(VIRTUAL_ROOT_NODE_ID);
307
- if (root === void 0) throw new Error("Virtual workdir session is missing root node");
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.nodes.get(parentId);
315
- if (!parentNode || parentNode.state.kind !== "directory") throw new Error("updateParentOverlay: parent is not a directory");
316
- state.nodes.set(parentId, {
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.nodes.set(newNodeId, cloned);
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.nodes.get(child.nodeId);
366
- if (childNode === void 0) continue;
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.nodes.set(newNodeId, {
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 };