nano-git 0.2.1 → 0.2.3

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,21 +1,159 @@
1
- import { VIRTUAL_ROOT_NODE_ID } from "./ids.mjs";
2
1
  import { createVirtualChangeLog } from "./change-log.mjs";
2
+ import { VIRTUAL_ROOT_NODE_ID } from "./ids.mjs";
3
3
  import { createRootDirectoryNode } from "./nodes.mjs";
4
+ import { createVirtualWorkdirSessionId } from "./session-id.mjs";
5
+ import { openVirtualWorkdirSession } from "./session.mjs";
4
6
  //#region src/workdir/memory-backend.ts
5
7
  /**
6
- * Virtual Workdir 会话内存状态(与 ODB 后端无关的纯状态容器)
8
+ * Virtual Workdir 内存状态存储
7
9
  */
8
10
  /**
9
- * 创建初始 session 状态(仅根目录节点,绑定 baseTree origin)
11
+ * 创建内存版状态存储
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * const store = createVirtualWorkdirMemoryStateStore(tree);
16
+ * expect(store.readBaseTree()).toBe(tree);
17
+ * ```
10
18
  */
11
- function createVirtualWorkdirMemoryState(baseTree) {
19
+ function createVirtualWorkdirMemoryStateStore(baseTree) {
20
+ const state = {
21
+ baseTree,
22
+ nodes: /* @__PURE__ */ new Map(),
23
+ changeLog: createVirtualChangeLog()
24
+ };
25
+ resetState(state, baseTree);
26
+ return {
27
+ kind: "memory",
28
+ transact(fn) {
29
+ const snapshot = snapshotState(state);
30
+ try {
31
+ return fn();
32
+ } catch (error) {
33
+ restoreState(state, snapshot);
34
+ throw error;
35
+ }
36
+ },
37
+ readBaseTree() {
38
+ return state.baseTree;
39
+ },
40
+ writeBaseTree(nextBaseTree) {
41
+ state.baseTree = nextBaseTree;
42
+ },
43
+ getNode(id) {
44
+ return state.nodes.get(id) ?? null;
45
+ },
46
+ setNode(node) {
47
+ state.nodes.set(node.id, node);
48
+ },
49
+ deleteNode(id) {
50
+ state.nodes.delete(id);
51
+ },
52
+ appendChange(record) {
53
+ state.changeLog.append(record);
54
+ },
55
+ listChangeRecords() {
56
+ return state.changeLog.snapshot();
57
+ },
58
+ clearChanges() {
59
+ state.changeLog.clear();
60
+ },
61
+ reset(nextBaseTree) {
62
+ resetState(state, nextBaseTree);
63
+ }
64
+ };
65
+ }
66
+ /**
67
+ * 创建内存版 Virtual Workdir backend
68
+ *
69
+ * @example
70
+ * ```ts
71
+ * const backend = createMemoryVirtualWorkdirBackend();
72
+ * const sessionId = backend.createSession({ baseTree: tree });
73
+ * const session = backend.openSession(repo.objects, sessionId);
74
+ * expect(session.baseTree).toBe(tree);
75
+ * ```
76
+ */
77
+ function createMemoryVirtualWorkdirBackend() {
78
+ const sessions = /* @__PURE__ */ new Map();
79
+ return {
80
+ kind: "memory",
81
+ createSession(options) {
82
+ const sessionId = createVirtualWorkdirSessionId();
83
+ sessions.set(sessionId, createVirtualWorkdirMemoryStateStore(options.baseTree));
84
+ return sessionId;
85
+ },
86
+ openSession(source, sessionId) {
87
+ const store = sessions.get(sessionId);
88
+ if (store === void 0) throw new Error(`Virtual workdir session not found: ${sessionId}`);
89
+ return openVirtualWorkdirSession(source, store);
90
+ },
91
+ deleteSession(sessionId) {
92
+ if (!sessions.delete(sessionId)) throw new Error(`Virtual workdir session not found: ${sessionId}`);
93
+ },
94
+ listSessions() {
95
+ return Array.from(sessions.keys());
96
+ }
97
+ };
98
+ }
99
+ function resetState(state, baseTree) {
100
+ state.baseTree = baseTree;
101
+ state.nodes.clear();
102
+ state.nodes.set(VIRTUAL_ROOT_NODE_ID, createRootDirectoryNode(baseTree));
103
+ state.changeLog.clear();
104
+ }
105
+ function snapshotState(state) {
12
106
  const nodes = /* @__PURE__ */ new Map();
13
- nodes.set(VIRTUAL_ROOT_NODE_ID, createRootDirectoryNode(baseTree));
107
+ for (const [nodeId, node] of state.nodes) nodes.set(nodeId, cloneSessionNode(node));
14
108
  return {
15
- baseTree,
109
+ baseTree: state.baseTree,
16
110
  nodes,
17
- changeLog: createVirtualChangeLog()
111
+ changes: state.changeLog.snapshot()
112
+ };
113
+ }
114
+ function restoreState(state, snapshot) {
115
+ state.baseTree = snapshot.baseTree;
116
+ state.nodes.clear();
117
+ for (const [nodeId, node] of snapshot.nodes) state.nodes.set(nodeId, cloneSessionNode(node));
118
+ state.changeLog.clear();
119
+ for (const record of snapshot.changes) state.changeLog.append(record);
120
+ }
121
+ function cloneSessionNode(node) {
122
+ if (node.state.kind === "directory") return {
123
+ id: node.id,
124
+ origin: node.origin,
125
+ state: {
126
+ kind: "directory",
127
+ overlay: {
128
+ addedEntries: new Map(node.state.overlay.addedEntries),
129
+ deletedNames: new Set(node.state.overlay.deletedNames)
130
+ }
131
+ }
132
+ };
133
+ if (node.state.kind === "file") return {
134
+ id: node.id,
135
+ origin: node.origin,
136
+ state: node.state.content === void 0 ? {
137
+ kind: "file",
138
+ mode: node.state.mode
139
+ } : {
140
+ kind: "file",
141
+ mode: node.state.mode,
142
+ content: Buffer.from(node.state.content)
143
+ }
144
+ };
145
+ return {
146
+ id: node.id,
147
+ origin: node.origin,
148
+ state: node.state.target === void 0 ? {
149
+ kind: "symlink",
150
+ mode: "120000"
151
+ } : {
152
+ kind: "symlink",
153
+ mode: "120000",
154
+ target: Buffer.from(node.state.target)
155
+ }
18
156
  };
19
157
  }
20
158
  //#endregion
21
- export { createVirtualWorkdirMemoryState };
159
+ export { createMemoryVirtualWorkdirBackend, createVirtualWorkdirMemoryStateStore };
@@ -1,2 +1,3 @@
1
- import { createVirtualWorkdirSession } from "./session.mjs";
2
- export { createVirtualWorkdirSession };
1
+ import { createMemoryVirtualWorkdirBackend } from "./memory-backend.mjs";
2
+ import { createVirtualWorkdirSession, openVirtualWorkdirSession } from "./session.mjs";
3
+ export { createMemoryVirtualWorkdirBackend, createVirtualWorkdirSession, openVirtualWorkdirSession };
@@ -1,2 +1,3 @@
1
- import { createVirtualWorkdirSession } from "./session.mjs";
2
- export { createVirtualWorkdirSession };
1
+ import { createVirtualWorkdirSession, openVirtualWorkdirSession } from "./session.mjs";
2
+ import { createMemoryVirtualWorkdirBackend } from "./memory-backend.mjs";
3
+ export { createMemoryVirtualWorkdirBackend, createVirtualWorkdirSession, openVirtualWorkdirSession };
@@ -0,0 +1,57 @@
1
+ import { SHA1 } from "../core/types.mjs";
2
+ import { NodeId } from "./ids.mjs";
3
+ import { DirectoryOverlay } from "./overlay.mjs";
4
+
5
+ //#region src/workdir/nodes.d.ts
6
+ /** Blob / 符号链接在 origin 与 state 中使用的 mode */
7
+ type BlobObjectMode = "100644" | "100755" | "120000";
8
+ /**
9
+ * 节点来源(repo 对象或纯 session 新建)
10
+ */
11
+ type NodeOrigin = {
12
+ readonly kind: "none";
13
+ } | {
14
+ readonly kind: "repo-tree";
15
+ readonly hash: SHA1;
16
+ } | {
17
+ readonly kind: "repo-blob";
18
+ readonly mode: BlobObjectMode;
19
+ readonly hash: SHA1;
20
+ };
21
+ /**
22
+ * 目录节点当前状态
23
+ *
24
+ * `overlay` 表达 session 层增删改;子项 nodeId 通过 overlay 合成与懒加载解析。
25
+ */
26
+ interface DirectoryNodeState {
27
+ readonly kind: "directory";
28
+ readonly overlay: DirectoryOverlay;
29
+ }
30
+ /**
31
+ * 文件节点当前状态(raw content overlay)
32
+ */
33
+ interface FileNodeState {
34
+ readonly kind: "file";
35
+ readonly mode: "100644" | "100755";
36
+ /** 未设置时表示未 materialize,可读 origin */
37
+ readonly content?: Buffer;
38
+ }
39
+ /**
40
+ * 符号链接节点当前状态
41
+ */
42
+ interface SymlinkNodeState {
43
+ readonly kind: "symlink";
44
+ readonly mode: "120000";
45
+ readonly target?: Buffer;
46
+ }
47
+ type SessionNodeState = DirectoryNodeState | FileNodeState | SymlinkNodeState;
48
+ /**
49
+ * 完整的会话节点记录
50
+ */
51
+ interface SessionNode {
52
+ readonly id: NodeId;
53
+ readonly origin: NodeOrigin;
54
+ readonly state: SessionNodeState;
55
+ }
56
+ //#endregion
57
+ export { SessionNode };
@@ -0,0 +1,14 @@
1
+ import { NodeId } from "./ids.mjs";
2
+
3
+ //#region src/workdir/overlay.d.ts
4
+ /**
5
+ * 目录 overlay 状态(挂在目录 SessionNode 上)
6
+ */
7
+ interface DirectoryOverlay {
8
+ /** session 新增或覆盖:条目名 -> 绑定的 nodeId */
9
+ readonly addedEntries: Map<string, NodeId>;
10
+ /** session 删除的 origin/先前条目名(tombstone) */
11
+ readonly deletedNames: Set<string>;
12
+ }
13
+ //#endregion
14
+ export { DirectoryOverlay };
@@ -0,0 +1,18 @@
1
+ //#region src/workdir/session-id.ts
2
+ let nextSessionCounter = 1;
3
+ /**
4
+ * 分配新的 session ID
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * const sessionId = createVirtualWorkdirSessionId();
9
+ * expect(String(sessionId).startsWith("session:")).toBe(true);
10
+ * ```
11
+ */
12
+ function createVirtualWorkdirSessionId() {
13
+ const id = `session:${nextSessionCounter}`;
14
+ nextSessionCounter += 1;
15
+ return id;
16
+ }
17
+ //#endregion
18
+ export { createVirtualWorkdirSessionId };
@@ -13,8 +13,8 @@ import { assertValidVirtualPath, joinPath, splitPathSegments } from "./path.mjs"
13
13
  * - origin 节点懒注册(ensureNodeFromTreeEntry)
14
14
  */
15
15
  function getRootNode(state) {
16
- const root = state.nodes.get(VIRTUAL_ROOT_NODE_ID);
17
- if (root === void 0) throw new Error("Virtual workdir session is missing root node");
16
+ const root = state.getNode(VIRTUAL_ROOT_NODE_ID);
17
+ if (root === null) throw new Error("Virtual workdir session is missing root node");
18
18
  return root;
19
19
  }
20
20
  /**
@@ -36,8 +36,8 @@ function resolvePath(source, state, path) {
36
36
  node: null
37
37
  };
38
38
  currentPath = joinPath(currentPath === "" ? null : currentPath, segment);
39
- const childNode = state.nodes.get(child.nodeId);
40
- if (childNode === void 0) return {
39
+ const childNode = state.getNode(child.nodeId);
40
+ if (childNode === null) return {
41
41
  found: false,
42
42
  node: null
43
43
  };
@@ -61,8 +61,8 @@ function resolveChild(source, state, parentNode, parentPath, name) {
61
61
  found: false,
62
62
  node: null
63
63
  };
64
- const childNode = state.nodes.get(child.nodeId);
65
- if (childNode === void 0) return {
64
+ const childNode = state.getNode(child.nodeId);
65
+ if (childNode === null) return {
66
66
  found: false,
67
67
  node: null
68
68
  };
@@ -76,7 +76,7 @@ function resolveChild(source, state, parentNode, parentPath, name) {
76
76
  */
77
77
  function ensureNodeFromTreeEntry(state, entry) {
78
78
  const id = originBackedNodeId(entry.hash);
79
- if (!state.nodes.has(id)) {
79
+ if (state.getNode(id) === null) {
80
80
  const origin = treeEntryToNodeOrigin(entry);
81
81
  let nodeState;
82
82
  if (entry.mode === "40000") nodeState = {
@@ -94,7 +94,7 @@ function ensureNodeFromTreeEntry(state, entry) {
94
94
  kind: "file",
95
95
  mode: entry.mode === "100755" ? "100755" : "100644"
96
96
  };
97
- state.nodes.set(id, {
97
+ state.setNode({
98
98
  id,
99
99
  origin,
100
100
  state: nodeState
@@ -127,8 +127,8 @@ function buildAddedModes(state, dirNode) {
127
127
  const addedModes = /* @__PURE__ */ new Map();
128
128
  for (const name of dirNode.state.overlay.addedEntries.keys()) {
129
129
  const nodeId = dirNode.state.overlay.addedEntries.get(name);
130
- const node = state.nodes.get(nodeId);
131
- if (node !== void 0) addedModes.set(name, sessionNodeMode(node));
130
+ const node = state.getNode(nodeId);
131
+ if (node !== null) addedModes.set(name, sessionNodeMode(node));
132
132
  }
133
133
  return addedModes;
134
134
  }
@@ -1,5 +1,6 @@
1
1
  import { ObjectDatabase } from "../core/types/odb.mjs";
2
2
  import { CreateVirtualWorkdirSessionOptions, VirtualWorkdirSession } from "./core.mjs";
3
+ import { VirtualWorkdirStateStore } from "./state-store.mjs";
3
4
 
4
5
  //#region src/workdir/session.d.ts
5
6
  /**
@@ -14,5 +15,16 @@ import { CreateVirtualWorkdirSessionOptions, VirtualWorkdirSession } from "./cor
14
15
  * ```
15
16
  */
16
17
  declare function createVirtualWorkdirSession(source: ObjectDatabase, options: CreateVirtualWorkdirSessionOptions): VirtualWorkdirSession;
18
+ /**
19
+ * 基于已有状态存储打开 session
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * const store = createVirtualWorkdirMemoryStateStore(tree);
24
+ * const session = openVirtualWorkdirSession(repo.objects, store);
25
+ * expect(session.baseTree).toBe(tree);
26
+ * ```
27
+ */
28
+ declare function openVirtualWorkdirSession(source: ObjectDatabase, state: VirtualWorkdirStateStore): VirtualWorkdirSession;
17
29
  //#endregion
18
- export { createVirtualWorkdirSession };
30
+ export { createVirtualWorkdirSession, openVirtualWorkdirSession };