nano-git 0.3.1 → 0.3.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,3 +1,2 @@
1
- import { createVirtualWorkdirSession, openVirtualWorkdirSession } from "./session.mjs";
2
- import { createMemoryVirtualWorkdirBackend } from "./memory-backend.mjs";
3
- export { createMemoryVirtualWorkdirBackend, createVirtualWorkdirSession, openVirtualWorkdirSession };
1
+ import { createVirtualWorkdir, openVirtualWorkdir } from "./workdir.mjs";
2
+ export { createVirtualWorkdir, openVirtualWorkdir };
@@ -6,7 +6,7 @@ import { DirectoryOverlay } from "./overlay.mjs";
6
6
  /** Blob / 符号链接在 origin 与 state 中使用的 mode */
7
7
  type BlobObjectMode = "100644" | "100755" | "120000";
8
8
  /**
9
- * 节点来源(repo 对象或纯 session 新建)
9
+ * 节点来源(repo 对象或纯 workdir 新建)
10
10
  */
11
11
  type NodeOrigin = {
12
12
  readonly kind: "none";
@@ -21,7 +21,7 @@ type NodeOrigin = {
21
21
  /**
22
22
  * 目录节点当前状态
23
23
  *
24
- * `overlay` 表达 session 层增删改;子项 nodeId 通过 overlay 合成与懒加载解析。
24
+ * `overlay` 表达 workdir 层增删改;子项 nodeId 通过 overlay 合成与懒加载解析。
25
25
  */
26
26
  interface DirectoryNodeState {
27
27
  readonly kind: "directory";
@@ -44,14 +44,14 @@ interface SymlinkNodeState {
44
44
  readonly mode: "120000";
45
45
  readonly target?: Buffer;
46
46
  }
47
- type SessionNodeState = DirectoryNodeState | FileNodeState | SymlinkNodeState;
47
+ type WorkdirNodeState = DirectoryNodeState | FileNodeState | SymlinkNodeState;
48
48
  /**
49
- * 完整的会话节点记录
49
+ * 完整的 workdir 节点记录
50
50
  */
51
- interface SessionNode {
51
+ interface WorkdirNode {
52
52
  readonly id: NodeId;
53
53
  readonly origin: NodeOrigin;
54
- readonly state: SessionNodeState;
54
+ readonly state: WorkdirNodeState;
55
55
  }
56
56
  //#endregion
57
- export { SessionNode };
57
+ export { WorkdirNode };
@@ -2,7 +2,7 @@ import { VIRTUAL_ROOT_NODE_ID } from "./ids.mjs";
2
2
  import { cloneDirectoryOverlay, createEmptyDirectoryOverlay } from "./overlay.mjs";
3
3
  //#region src/workdir/nodes.ts
4
4
  /**
5
- * Virtual Workdir 会话节点状态模型
5
+ * Virtual Workdir 节点状态模型
6
6
  *
7
7
  * 节点身份(nodeId)与目录路径绑定分离;origin 描述 repo-backed 来源。
8
8
  */
@@ -63,7 +63,7 @@ function revertNodeState(node) {
63
63
  /**
64
64
  * 为 `copy` 创建新节点:共享 origin,目录为浅复制(子项绑定保留,但 nodeId 为新)
65
65
  */
66
- function cloneSessionNodeForCopy(source, newId) {
66
+ function cloneWorkdirNodeForCopy(source, newId) {
67
67
  const origin = source.origin;
68
68
  if (source.state.kind === "directory") return {
69
69
  id: newId,
@@ -103,4 +103,4 @@ function cloneSessionNodeForCopy(source, newId) {
103
103
  };
104
104
  }
105
105
  //#endregion
106
- export { cloneSessionNodeForCopy, createRootDirectoryNode, revertNodeState };
106
+ export { cloneWorkdirNodeForCopy, createRootDirectoryNode, revertNodeState };
@@ -2,12 +2,12 @@ import { NodeId } from "./ids.mjs";
2
2
 
3
3
  //#region src/workdir/overlay.d.ts
4
4
  /**
5
- * 目录 overlay 状态(挂在目录 SessionNode 上)
5
+ * 目录 overlay 状态(挂在目录 WorkdirNode 上)
6
6
  */
7
7
  interface DirectoryOverlay {
8
- /** session 新增或覆盖:条目名 -> 绑定的 nodeId */
8
+ /** workdir 新增或覆盖:条目名 -> 绑定的 nodeId */
9
9
  readonly addedEntries: Map<string, NodeId>;
10
- /** session 删除的 origin/先前条目名(tombstone) */
10
+ /** workdir 删除的 origin/先前条目名(tombstone) */
11
11
  readonly deletedNames: Set<string>;
12
12
  }
13
13
  //#endregion
@@ -1,30 +1,47 @@
1
- import { VirtualWorkdirBackend } from "./core.mjs";
1
+ import { ObjectDatabase } from "../core/types/odb.mjs";
2
+ import { CreateVirtualWorkdirOptions, VirtualWorkdir } from "./core.mjs";
2
3
  import { Database } from "bun:sqlite";
3
4
 
4
5
  //#region src/workdir/sqlite-backend.d.ts
5
- /** 创建 SQLite Virtual Workdir backend 的可选参数 */
6
- interface CreateSqliteVirtualWorkdirBackendOptions {
6
+ /** SQLite 连接层的可选参数 */
7
+ interface SqliteVirtualWorkdirConnectionOptions {
7
8
  /** 开启 WAL 模式,默认 true */
8
9
  readonly walMode?: boolean;
9
10
  }
11
+ /** 打开 SQLite VirtualWorkdir 的可选参数 */
12
+ interface OpenSqliteVirtualWorkdirOptions extends CreateVirtualWorkdirOptions, SqliteVirtualWorkdirConnectionOptions {
13
+ /** 不存在时按 baseTree 初始化 */
14
+ readonly create?: boolean;
15
+ }
10
16
  /**
11
- * SQLite Virtual Workdir backend(含资源释放能力)
17
+ * 基于 SQLite VirtualWorkdir
18
+ *
19
+ * 返回值附带 `[Symbol.dispose]()`,用于释放内部数据库连接。
12
20
  */
13
- interface SqliteVirtualWorkdirBackend extends VirtualWorkdirBackend {
14
- /** 释放 SQLite 数据库连接 */
21
+ type SqliteVirtualWorkdir = VirtualWorkdir & {
15
22
  [Symbol.dispose](): void;
16
- }
23
+ };
24
+ /**
25
+ * 打开基于 SQLite 的持久化 VirtualWorkdir
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * using workdir = openSqliteVirtualWorkdir(repo.objects, ":memory:", "demo", {
30
+ * baseTree: tree,
31
+ * create: true,
32
+ * });
33
+ * expect(workdir.baseTree).toBe(tree);
34
+ * ```
35
+ */
36
+ declare function openSqliteVirtualWorkdir(source: ObjectDatabase, dbPath: string, workdirKey: string, options: OpenSqliteVirtualWorkdirOptions): SqliteVirtualWorkdir;
17
37
  /**
18
- * 创建基于 SQLite Virtual Workdir backend
38
+ * 删除指定 key 上的 SQLite VirtualWorkdir
19
39
  *
20
40
  * @example
21
41
  * ```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);
42
+ * deleteSqliteVirtualWorkdir("/tmp/workdir.sqlite", "demo");
26
43
  * ```
27
44
  */
28
- declare function createSqliteVirtualWorkdirBackend(dbPath: string, options?: CreateSqliteVirtualWorkdirBackendOptions): SqliteVirtualWorkdirBackend;
45
+ declare function deleteSqliteVirtualWorkdir(dbPath: string, workdirKey: string, options?: SqliteVirtualWorkdirConnectionOptions): void;
29
46
  //#endregion
30
- export { CreateSqliteVirtualWorkdirBackendOptions, SqliteVirtualWorkdirBackend, createSqliteVirtualWorkdirBackend };
47
+ export { OpenSqliteVirtualWorkdirOptions, SqliteVirtualWorkdir, deleteSqliteVirtualWorkdir, openSqliteVirtualWorkdir };
@@ -1,90 +1,85 @@
1
1
  import { sha1 } from "../core/types.mjs";
2
2
  import { createRootDirectoryNode } from "./nodes.mjs";
3
- import { createVirtualWorkdirSessionId } from "./session-id.mjs";
4
- import { openVirtualWorkdirSession } from "./session.mjs";
3
+ import { openVirtualWorkdir } from "./workdir.mjs";
5
4
  import { Database } from "bun:sqlite";
6
5
  //#region src/workdir/sqlite-backend.ts
7
6
  /**
8
7
  * Virtual Workdir SQLite backend
9
8
  */
10
- const WORKDIR_SQLITE_SCHEMA_VERSION = 5;
9
+ const WORKDIR_SQLITE_SCHEMA_VERSION = 6;
11
10
  /**
12
- * 创建基于 SQLite Virtual Workdir backend
11
+ * 打开基于 SQLite 的持久化 VirtualWorkdir
13
12
  *
14
13
  * @example
15
14
  * ```ts
16
- * using backend = createSqliteVirtualWorkdirBackend(":memory:");
17
- * const sessionId = backend.createSession({ baseTree: tree });
18
- * const session = backend.openSession(repo.objects, sessionId);
19
- * expect(session.baseTree).toBe(tree);
15
+ * using workdir = openSqliteVirtualWorkdir(repo.objects, ":memory:", "demo", {
16
+ * baseTree: tree,
17
+ * create: true,
18
+ * });
19
+ * expect(workdir.baseTree).toBe(tree);
20
20
  * ```
21
21
  */
22
- function createSqliteVirtualWorkdirBackend(dbPath, options = {}) {
22
+ function openSqliteVirtualWorkdir(source, dbPath, workdirKey, options) {
23
23
  const db = new Database(dbPath);
24
- let disposed = false;
25
24
  if (options.walMode !== false) db.run("PRAGMA journal_mode = WAL");
26
25
  ensureSchema(db);
27
- return {
28
- kind: "sqlite",
29
- createSession(options) {
30
- assertBackendAvailable(disposed);
31
- const sessionId = createVirtualWorkdirSessionId();
32
- createSqliteVirtualWorkdirStateStore(db, sessionId).reset(options.baseTree);
33
- return sessionId;
34
- },
35
- openSession(source, sessionId) {
36
- assertBackendAvailable(disposed);
37
- if (!hasSession(db, sessionId)) throw new Error(`Virtual workdir session not found: ${sessionId}`);
38
- validateSessionIntegrity(db, sessionId);
39
- return openVirtualWorkdirSession(source, createSqliteVirtualWorkdirStateStore(db, sessionId));
40
- },
41
- deleteSession(sessionId) {
42
- assertBackendAvailable(disposed);
43
- if (!hasSession(db, sessionId)) throw new Error(`Virtual workdir session not found: ${sessionId}`);
44
- deleteSessionRows(db, sessionId);
45
- },
46
- listSessions() {
47
- assertBackendAvailable(disposed);
48
- return db.query("SELECT session_id FROM workdir_sessions ORDER BY session_id").all().map((row) => row.session_id).filter((sessionId) => {
49
- try {
50
- validateSessionIntegrity(db, sessionId);
51
- return true;
52
- } catch {
53
- return false;
54
- }
55
- });
56
- },
57
- [Symbol.dispose]() {
58
- if (disposed) return;
59
- disposed = true;
26
+ const store = createSqliteVirtualWorkdirStateStore(db, workdirKey);
27
+ if (!hasWorkdir(db, workdirKey)) {
28
+ if (options.create !== true) {
60
29
  db.close();
30
+ throw new Error(`Virtual workdir not found: ${workdirKey}`);
61
31
  }
62
- };
32
+ store.reset(options.baseTree);
33
+ }
34
+ validateWorkdirIntegrity(db, workdirKey);
35
+ let disposed = false;
36
+ const workdir = openVirtualWorkdir(source, store);
37
+ return Object.assign(workdir, { [Symbol.dispose]() {
38
+ if (disposed) return;
39
+ disposed = true;
40
+ db.close();
41
+ } });
63
42
  }
64
- function assertBackendAvailable(disposed) {
65
- if (disposed) throw new Error("SQLite virtual workdir backend is disposed");
43
+ /**
44
+ * 删除指定 key 上的 SQLite VirtualWorkdir
45
+ *
46
+ * @example
47
+ * ```ts
48
+ * deleteSqliteVirtualWorkdir("/tmp/workdir.sqlite", "demo");
49
+ * ```
50
+ */
51
+ function deleteSqliteVirtualWorkdir(dbPath, workdirKey, options = {}) {
52
+ const db = new Database(dbPath);
53
+ try {
54
+ if (options.walMode !== false) db.run("PRAGMA journal_mode = WAL");
55
+ ensureSchema(db);
56
+ if (!hasWorkdir(db, workdirKey)) throw new Error(`Virtual workdir not found: ${workdirKey}`);
57
+ deleteWorkdirRows(db, workdirKey);
58
+ } finally {
59
+ db.close();
60
+ }
66
61
  }
67
62
  /**
68
- * 创建单个 session SQLite 状态存储
63
+ * 创建单个 SQLite VirtualWorkdir 的状态存储
69
64
  *
70
65
  * @example
71
66
  * ```ts
72
- * const store = createSqliteVirtualWorkdirStateStore(db, sessionId);
67
+ * const store = createSqliteVirtualWorkdirStateStore(db, "demo");
73
68
  * expect(store.kind).toBe("sqlite");
74
69
  * ```
75
70
  */
76
- function createSqliteVirtualWorkdirStateStore(db, sessionId) {
71
+ function createSqliteVirtualWorkdirStateStore(db, workdirKey) {
77
72
  const transactImpl = db.transaction((fn) => fn());
78
- const readBaseTreeStmt = db.query("SELECT base_tree FROM workdir_sessions WHERE session_id = ?");
79
- const upsertSessionStmt = db.query("INSERT INTO workdir_sessions (session_id, base_tree) VALUES (?, ?) ON CONFLICT(session_id) DO UPDATE SET base_tree = excluded.base_tree");
73
+ const readBaseTreeStmt = db.query("SELECT base_tree FROM workdirs WHERE workdir_key = ?");
74
+ const upsertWorkdirStmt = db.query("INSERT INTO workdirs (workdir_key, base_tree) VALUES (?, ?) ON CONFLICT(workdir_key) DO UPDATE SET base_tree = excluded.base_tree");
80
75
  const getNodeStmt = db.query(`SELECT node_id, origin_kind, origin_hash, origin_mode, state_kind, state_mode, content, target, directory_overlay
81
76
  FROM workdir_nodes
82
- WHERE session_id = ? AND node_id = ?`);
77
+ WHERE workdir_key = ? AND node_id = ?`);
83
78
  const setNodeStmt = db.query(`INSERT INTO workdir_nodes (
84
- session_id, node_id, origin_kind, origin_hash, origin_mode,
79
+ workdir_key, node_id, origin_kind, origin_hash, origin_mode,
85
80
  state_kind, state_mode, content, target, directory_overlay
86
81
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
87
- ON CONFLICT(session_id, node_id) DO UPDATE SET
82
+ ON CONFLICT(workdir_key, node_id) DO UPDATE SET
88
83
  origin_kind = excluded.origin_kind,
89
84
  origin_hash = excluded.origin_hash,
90
85
  origin_mode = excluded.origin_mode,
@@ -93,20 +88,20 @@ function createSqliteVirtualWorkdirStateStore(db, sessionId) {
93
88
  content = excluded.content,
94
89
  target = excluded.target,
95
90
  directory_overlay = excluded.directory_overlay`);
96
- const deleteNodeStmt = db.query("DELETE FROM workdir_nodes WHERE session_id = ? AND node_id = ?");
97
- const clearNodesStmt = db.query("DELETE FROM workdir_nodes WHERE session_id = ?");
91
+ const deleteNodeStmt = db.query("DELETE FROM workdir_nodes WHERE workdir_key = ? AND node_id = ?");
92
+ const clearNodesStmt = db.query("DELETE FROM workdir_nodes WHERE workdir_key = ?");
98
93
  const listChangesStmt = db.query(`SELECT path, previous_kind, previous_mode, previous_hash, current_kind, current_mode, current_hash, source_kind, source_path
99
94
  FROM workdir_changes
100
- WHERE session_id = ?
95
+ WHERE workdir_key = ?
101
96
  ORDER BY path`);
102
97
  const getChangeStmt = db.query(`SELECT path, previous_kind, previous_mode, previous_hash, current_kind, current_mode, current_hash, source_kind, source_path
103
98
  FROM workdir_changes
104
- WHERE session_id = ? AND path = ?`);
99
+ WHERE workdir_key = ? AND path = ?`);
105
100
  const upsertChangeStmt = db.query(`INSERT INTO workdir_changes (
106
- session_id, path, previous_kind, previous_mode, previous_hash,
101
+ workdir_key, path, previous_kind, previous_mode, previous_hash,
107
102
  current_kind, current_mode, current_hash, source_kind, source_path
108
103
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
109
- ON CONFLICT(session_id, path) DO UPDATE SET
104
+ ON CONFLICT(workdir_key, path) DO UPDATE SET
110
105
  previous_kind = excluded.previous_kind,
111
106
  previous_mode = excluded.previous_mode,
112
107
  previous_hash = excluded.previous_hash,
@@ -115,35 +110,35 @@ function createSqliteVirtualWorkdirStateStore(db, sessionId) {
115
110
  current_hash = excluded.current_hash,
116
111
  source_kind = excluded.source_kind,
117
112
  source_path = excluded.source_path`);
118
- const deleteChangeStmt = db.query("DELETE FROM workdir_changes WHERE session_id = ? AND path = ?");
119
- const clearChangesStmt = db.query("DELETE FROM workdir_changes WHERE session_id = ?");
113
+ const deleteChangeStmt = db.query("DELETE FROM workdir_changes WHERE workdir_key = ? AND path = ?");
114
+ const clearChangesStmt = db.query("DELETE FROM workdir_changes WHERE workdir_key = ?");
120
115
  const listDirtyDirsStmt = db.query(`SELECT path, is_dirty, dirty_entry_count, dirty_descendant_count, affected_names, current_tree_hash, hash_state
121
116
  FROM workdir_dirty_dirs
122
- WHERE session_id = ?
117
+ WHERE workdir_key = ?
123
118
  ORDER BY path`);
124
119
  const getDirtyDirStmt = db.query(`SELECT path, is_dirty, dirty_entry_count, dirty_descendant_count, affected_names, current_tree_hash, hash_state
125
120
  FROM workdir_dirty_dirs
126
- WHERE session_id = ? AND path = ?`);
121
+ WHERE workdir_key = ? AND path = ?`);
127
122
  const upsertDirtyDirStmt = db.query(`INSERT INTO workdir_dirty_dirs (
128
- session_id, path, is_dirty, dirty_entry_count, dirty_descendant_count,
123
+ workdir_key, path, is_dirty, dirty_entry_count, dirty_descendant_count,
129
124
  affected_names, current_tree_hash, hash_state
130
125
  )
131
126
  VALUES (?, ?, ?, ?, ?, ?, ?, ?)
132
- ON CONFLICT(session_id, path) DO UPDATE SET
127
+ ON CONFLICT(workdir_key, path) DO UPDATE SET
133
128
  is_dirty = excluded.is_dirty,
134
129
  dirty_entry_count = excluded.dirty_entry_count,
135
130
  dirty_descendant_count = excluded.dirty_descendant_count,
136
131
  affected_names = excluded.affected_names,
137
132
  current_tree_hash = excluded.current_tree_hash,
138
133
  hash_state = excluded.hash_state`);
139
- const deleteDirtyDirStmt = db.query("DELETE FROM workdir_dirty_dirs WHERE session_id = ? AND path = ?");
140
- const clearDirtyDirsStmt = db.query("DELETE FROM workdir_dirty_dirs WHERE session_id = ?");
134
+ const deleteDirtyDirStmt = db.query("DELETE FROM workdir_dirty_dirs WHERE workdir_key = ? AND path = ?");
135
+ const clearDirtyDirsStmt = db.query("DELETE FROM workdir_dirty_dirs WHERE workdir_key = ?");
141
136
  const resetTx = db.transaction((baseTree) => {
142
- upsertSessionStmt.run(sessionId, baseTree);
143
- clearNodesStmt.run(sessionId);
144
- clearChangesStmt.run(sessionId);
145
- clearDirtyDirsStmt.run(sessionId);
146
- writeNode(setNodeStmt, sessionId, createRootDirectoryNode(baseTree));
137
+ upsertWorkdirStmt.run(workdirKey, baseTree);
138
+ clearNodesStmt.run(workdirKey);
139
+ clearChangesStmt.run(workdirKey);
140
+ clearDirtyDirsStmt.run(workdirKey);
141
+ writeNode(setNodeStmt, workdirKey, createRootDirectoryNode(baseTree));
147
142
  });
148
143
  return {
149
144
  kind: "sqlite",
@@ -151,49 +146,49 @@ function createSqliteVirtualWorkdirStateStore(db, sessionId) {
151
146
  return transactImpl(fn);
152
147
  },
153
148
  readBaseTree() {
154
- const row = readBaseTreeStmt.get(sessionId);
155
- if (row === null) throw new Error(`Virtual workdir session not found: ${sessionId}`);
149
+ const row = readBaseTreeStmt.get(workdirKey);
150
+ if (row === null) throw new Error(`Virtual workdir not found: ${workdirKey}`);
156
151
  return readBaseTreeValue(row.base_tree);
157
152
  },
158
153
  writeBaseTree(baseTree) {
159
- upsertSessionStmt.run(sessionId, baseTree);
154
+ upsertWorkdirStmt.run(workdirKey, baseTree);
160
155
  },
161
156
  getNode(id) {
162
- const row = getNodeStmt.get(sessionId, id);
157
+ const row = getNodeStmt.get(workdirKey, id);
163
158
  if (row === null) return null;
164
159
  return readNode(row);
165
160
  },
166
161
  setNode(node) {
167
- writeNode(setNodeStmt, sessionId, node);
162
+ writeNode(setNodeStmt, workdirKey, node);
168
163
  },
169
164
  deleteNode(id) {
170
- deleteNodeStmt.run(sessionId, id);
165
+ deleteNodeStmt.run(workdirKey, id);
171
166
  },
172
167
  listChangeRecords() {
173
- return listChangesStmt.all(sessionId).map(readChangeRecord);
168
+ return listChangesStmt.all(workdirKey).map(readChangeRecord);
174
169
  },
175
170
  getChangeRecord(path) {
176
- const row = getChangeStmt.get(sessionId, path);
171
+ const row = getChangeStmt.get(workdirKey, path);
177
172
  return row === null ? null : readChangeRecord(row);
178
173
  },
179
174
  setChangeRecord(record) {
180
- upsertChangeStmt.run(sessionId, record.path, record.previous?.kind ?? null, record.previous?.mode ?? null, record.previous?.hash ?? null, record.current?.kind ?? null, record.current?.mode ?? null, record.current?.hash ?? null, record.source?.kind ?? null, record.source?.path ?? null);
175
+ upsertChangeStmt.run(workdirKey, record.path, record.previous?.kind ?? null, record.previous?.mode ?? null, record.previous?.hash ?? null, record.current?.kind ?? null, record.current?.mode ?? null, record.current?.hash ?? null, record.source?.kind ?? null, record.source?.path ?? null);
181
176
  },
182
177
  deleteChangeRecord(path) {
183
- deleteChangeStmt.run(sessionId, path);
178
+ deleteChangeStmt.run(workdirKey, path);
184
179
  },
185
180
  listDirtyDirSummaries() {
186
- return listDirtyDirsStmt.all(sessionId).map(readDirtyDirSummary);
181
+ return listDirtyDirsStmt.all(workdirKey).map(readDirtyDirSummary);
187
182
  },
188
183
  getDirtyDirSummary(path) {
189
- const row = getDirtyDirStmt.get(sessionId, path);
184
+ const row = getDirtyDirStmt.get(workdirKey, path);
190
185
  return row === null ? null : readDirtyDirSummary(row);
191
186
  },
192
187
  setDirtyDirSummary(summary) {
193
- upsertDirtyDirStmt.run(sessionId, summary.path, summary.isDirty ? 1 : 0, summary.dirtyEntryCount, summary.dirtyDescendantCount, JSON.stringify(summary.affectedNames), summary.currentTreeHash, summary.hashState);
188
+ upsertDirtyDirStmt.run(workdirKey, summary.path, summary.isDirty ? 1 : 0, summary.dirtyEntryCount, summary.dirtyDescendantCount, JSON.stringify(summary.affectedNames), summary.currentTreeHash, summary.hashState);
194
189
  },
195
190
  deleteDirtyDirSummary(path) {
196
- deleteDirtyDirStmt.run(sessionId, path);
191
+ deleteDirtyDirStmt.run(workdirKey, path);
197
192
  },
198
193
  reset(baseTree) {
199
194
  resetTx(baseTree);
@@ -204,14 +199,14 @@ function ensureSchema(db) {
204
199
  const currentVersion = readSchemaVersion(db);
205
200
  if (currentVersion !== 0 && currentVersion !== WORKDIR_SQLITE_SCHEMA_VERSION) throw new Error(`Unsupported virtual workdir SQLite schema version: expected ${WORKDIR_SQLITE_SCHEMA_VERSION}, got ${currentVersion}`);
206
201
  db.run(`
207
- CREATE TABLE IF NOT EXISTS workdir_sessions (
208
- session_id TEXT PRIMARY KEY,
202
+ CREATE TABLE IF NOT EXISTS workdirs (
203
+ workdir_key TEXT PRIMARY KEY,
209
204
  base_tree TEXT NOT NULL
210
205
  )
211
206
  `);
212
207
  db.run(`
213
208
  CREATE TABLE IF NOT EXISTS workdir_nodes (
214
- session_id TEXT NOT NULL,
209
+ workdir_key TEXT NOT NULL,
215
210
  node_id TEXT NOT NULL,
216
211
  origin_kind TEXT NOT NULL,
217
212
  origin_hash TEXT,
@@ -221,12 +216,12 @@ function ensureSchema(db) {
221
216
  content BLOB,
222
217
  target BLOB,
223
218
  directory_overlay TEXT,
224
- PRIMARY KEY (session_id, node_id)
219
+ PRIMARY KEY (workdir_key, node_id)
225
220
  )
226
221
  `);
227
222
  db.run(`
228
223
  CREATE TABLE IF NOT EXISTS workdir_changes (
229
- session_id TEXT NOT NULL,
224
+ workdir_key TEXT NOT NULL,
230
225
  path TEXT NOT NULL,
231
226
  previous_kind TEXT,
232
227
  previous_mode TEXT,
@@ -236,12 +231,12 @@ function ensureSchema(db) {
236
231
  current_hash TEXT,
237
232
  source_kind TEXT,
238
233
  source_path TEXT,
239
- PRIMARY KEY (session_id, path)
234
+ PRIMARY KEY (workdir_key, path)
240
235
  )
241
236
  `);
242
237
  db.run(`
243
238
  CREATE TABLE IF NOT EXISTS workdir_dirty_dirs (
244
- session_id TEXT NOT NULL,
239
+ workdir_key TEXT NOT NULL,
245
240
  path TEXT NOT NULL,
246
241
  is_dirty INTEGER NOT NULL,
247
242
  dirty_entry_count INTEGER NOT NULL,
@@ -249,20 +244,20 @@ function ensureSchema(db) {
249
244
  affected_names TEXT NOT NULL,
250
245
  current_tree_hash TEXT,
251
246
  hash_state TEXT NOT NULL,
252
- PRIMARY KEY (session_id, path)
247
+ PRIMARY KEY (workdir_key, path)
253
248
  )
254
249
  `);
255
250
  writeSchemaVersion(db, WORKDIR_SQLITE_SCHEMA_VERSION);
256
251
  }
257
- function hasSession(db, sessionId) {
258
- return db.query("SELECT 1 FROM workdir_sessions WHERE session_id = ?").get(sessionId) !== null;
252
+ function hasWorkdir(db, workdirKey) {
253
+ return db.query("SELECT 1 FROM workdirs WHERE workdir_key = ?").get(workdirKey) !== null;
259
254
  }
260
- function deleteSessionRows(db, sessionId) {
255
+ function deleteWorkdirRows(db, workdirKey) {
261
256
  db.transaction(() => {
262
- db.query("DELETE FROM workdir_dirty_dirs WHERE session_id = ?").run(sessionId);
263
- db.query("DELETE FROM workdir_changes WHERE session_id = ?").run(sessionId);
264
- db.query("DELETE FROM workdir_nodes WHERE session_id = ?").run(sessionId);
265
- db.query("DELETE FROM workdir_sessions WHERE session_id = ?").run(sessionId);
257
+ db.query("DELETE FROM workdir_dirty_dirs WHERE workdir_key = ?").run(workdirKey);
258
+ db.query("DELETE FROM workdir_changes WHERE workdir_key = ?").run(workdirKey);
259
+ db.query("DELETE FROM workdir_nodes WHERE workdir_key = ?").run(workdirKey);
260
+ db.query("DELETE FROM workdirs WHERE workdir_key = ?").run(workdirKey);
266
261
  })();
267
262
  }
268
263
  function readSchemaVersion(db) {
@@ -271,33 +266,33 @@ function readSchemaVersion(db) {
271
266
  function writeSchemaVersion(db, version) {
272
267
  db.run(`PRAGMA user_version = ${version}`);
273
268
  }
274
- function validateSessionIntegrity(db, sessionId) {
275
- const sessionRow = db.query("SELECT base_tree FROM workdir_sessions WHERE session_id = ?").get(sessionId);
276
- if (sessionRow === null) throw new Error(`Virtual workdir session not found: ${sessionId}`);
277
- readBaseTreeValue(sessionRow.base_tree);
269
+ function validateWorkdirIntegrity(db, workdirKey) {
270
+ const workdirRow = db.query("SELECT base_tree FROM workdirs WHERE workdir_key = ?").get(workdirKey);
271
+ if (workdirRow === null) throw new Error(`Virtual workdir not found: ${workdirKey}`);
272
+ readBaseTreeValue(workdirRow.base_tree);
278
273
  const rootRow = db.query(`SELECT node_id, origin_kind, origin_hash, origin_mode, state_kind, state_mode, content, target, directory_overlay
279
274
  FROM workdir_nodes
280
- WHERE session_id = ? AND node_id = ?`).get(sessionId, "root");
281
- if (rootRow === null) throw new Error(`Virtual workdir session is corrupted: missing root node for ${sessionId}`);
282
- if (readNode(rootRow).state.kind !== "directory") throw new Error(`Virtual workdir session is corrupted: root node is not a directory for ${sessionId}`);
275
+ WHERE workdir_key = ? AND node_id = ?`).get(workdirKey, "root");
276
+ if (rootRow === null) throw new Error(`Virtual workdir is corrupted: missing root node for ${workdirKey}`);
277
+ if (readNode(rootRow).state.kind !== "directory") throw new Error(`Virtual workdir is corrupted: root node is not a directory for ${workdirKey}`);
283
278
  const allNodesStmt = db.query(`SELECT node_id, origin_kind, origin_hash, origin_mode, state_kind, state_mode, content, target, directory_overlay
284
279
  FROM workdir_nodes
285
- WHERE session_id = ?`);
286
- for (const row of allNodesStmt.all(sessionId)) readNode(row);
280
+ WHERE workdir_key = ?`);
281
+ for (const row of allNodesStmt.all(workdirKey)) readNode(row);
287
282
  }
288
- function writeNode(stmt, sessionId, node) {
283
+ function writeNode(stmt, workdirKey, node) {
289
284
  if (node.state.kind === "directory") {
290
- stmt.run(sessionId, node.id, node.origin.kind, node.origin.kind === "none" ? null : node.origin.hash, node.origin.kind === "repo-blob" ? node.origin.mode : null, "directory", null, null, null, JSON.stringify({
285
+ stmt.run(workdirKey, node.id, node.origin.kind, node.origin.kind === "none" ? null : node.origin.hash, node.origin.kind === "repo-blob" ? node.origin.mode : null, "directory", null, null, null, JSON.stringify({
291
286
  addedEntries: Array.from(node.state.overlay.addedEntries.entries()),
292
287
  deletedNames: Array.from(node.state.overlay.deletedNames.values())
293
288
  }));
294
289
  return;
295
290
  }
296
291
  if (node.state.kind === "file") {
297
- stmt.run(sessionId, node.id, node.origin.kind, node.origin.kind === "none" ? null : node.origin.hash, node.origin.kind === "repo-blob" ? node.origin.mode : null, "file", node.state.mode, node.state.content ?? null, null, null);
292
+ stmt.run(workdirKey, node.id, node.origin.kind, node.origin.kind === "none" ? null : node.origin.hash, node.origin.kind === "repo-blob" ? node.origin.mode : null, "file", node.state.mode, node.state.content ?? null, null, null);
298
293
  return;
299
294
  }
300
- stmt.run(sessionId, node.id, node.origin.kind, node.origin.kind === "none" ? null : node.origin.hash, node.origin.kind === "repo-blob" ? node.origin.mode : null, "symlink", "120000", null, node.state.target ?? null, null);
295
+ stmt.run(workdirKey, node.id, node.origin.kind, node.origin.kind === "none" ? null : node.origin.hash, node.origin.kind === "repo-blob" ? node.origin.mode : null, "symlink", "120000", null, node.state.target ?? null, null);
301
296
  }
302
297
  function readNode(row) {
303
298
  const origin = readNodeOrigin(row);
@@ -445,11 +440,11 @@ function readBlobColumn(raw, column) {
445
440
  return Buffer.from(raw);
446
441
  }
447
442
  function readBaseTreeValue(raw) {
448
- if (typeof raw !== "string") throw new Error("Invalid SQLite workdir session base_tree");
443
+ if (typeof raw !== "string") throw new Error("Invalid SQLite workdir base_tree");
449
444
  try {
450
445
  return sha1(raw);
451
446
  } catch {
452
- throw new Error("Invalid SQLite workdir session base_tree");
447
+ throw new Error("Invalid SQLite workdir base_tree");
453
448
  }
454
449
  }
455
450
  function isDirectoryOverlayPayload(value) {
@@ -461,4 +456,4 @@ function isDirectoryOverlayPayload(value) {
461
456
  return hasValidAddedEntries && hasValidDeletedNames;
462
457
  }
463
458
  //#endregion
464
- export { createSqliteVirtualWorkdirBackend };
459
+ export { deleteSqliteVirtualWorkdir, openSqliteVirtualWorkdir };
@@ -1,2 +1,2 @@
1
- import { CreateSqliteVirtualWorkdirBackendOptions, SqliteVirtualWorkdirBackend, createSqliteVirtualWorkdirBackend } from "./sqlite-backend.mjs";
2
- export { type CreateSqliteVirtualWorkdirBackendOptions, type SqliteVirtualWorkdirBackend, createSqliteVirtualWorkdirBackend };
1
+ import { OpenSqliteVirtualWorkdirOptions, SqliteVirtualWorkdir, deleteSqliteVirtualWorkdir, openSqliteVirtualWorkdir } from "./sqlite-backend.mjs";
2
+ export { type OpenSqliteVirtualWorkdirOptions, type SqliteVirtualWorkdir, deleteSqliteVirtualWorkdir, openSqliteVirtualWorkdir };
@@ -1,2 +1,2 @@
1
- import { createSqliteVirtualWorkdirBackend } from "./sqlite-backend.mjs";
2
- export { createSqliteVirtualWorkdirBackend };
1
+ import { deleteSqliteVirtualWorkdir, openSqliteVirtualWorkdir } from "./sqlite-backend.mjs";
2
+ export { deleteSqliteVirtualWorkdir, openSqliteVirtualWorkdir };
@@ -2,7 +2,7 @@ import { SHA1 } from "../core/types.mjs";
2
2
  import { NodeId } from "./ids.mjs";
3
3
  import { NormalizedChangeRecord } from "./change-index.mjs";
4
4
  import { DirtyDirSummary } from "./dirty-dir.mjs";
5
- import { SessionNode } from "./nodes.mjs";
5
+ import { WorkdirNode } from "./nodes.mjs";
6
6
 
7
7
  //#region src/workdir/state-store.d.ts
8
8
  /**
@@ -14,7 +14,7 @@ interface VirtualWorkdirStateStore {
14
14
  /**
15
15
  * 在单次提交边界内执行状态变更
16
16
  *
17
- * 用于把一次 session 写操作封装为单个内部事务。
17
+ * 用于把一次 workdir 写操作封装为单个内部事务。
18
18
  * 若回调抛错,store 应尽力恢复到调用前状态。
19
19
  */
20
20
  transact<T>(fn: () => T): T;
@@ -23,9 +23,9 @@ interface VirtualWorkdirStateStore {
23
23
  /** 覆盖当前基线 tree */
24
24
  writeBaseTree(baseTree: SHA1): void;
25
25
  /** 读取节点,不存在时返回 null */
26
- getNode(id: NodeId): SessionNode | null;
26
+ getNode(id: NodeId): WorkdirNode | null;
27
27
  /** 写入或覆盖节点 */
28
- setNode(node: SessionNode): void;
28
+ setNode(node: WorkdirNode): void;
29
29
  /** 删除节点 */
30
30
  deleteNode(id: NodeId): void;
31
31
  /** 列出全部规范化变更记录 */