nano-git 0.1.0 → 0.2.1

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.
Files changed (45) hide show
  1. package/README.md +58 -1
  2. package/dist/backend/sqlite.d.mts +44 -0
  3. package/dist/backend/sqlite.mjs +59 -0
  4. package/dist/core/errors.d.mts +71 -1
  5. package/dist/core/errors.mjs +99 -1
  6. package/dist/errors.d.mts +2 -2
  7. package/dist/errors.mjs +2 -2
  8. package/dist/index.d.mts +2 -1
  9. package/dist/log/index.d.mts +3 -0
  10. package/dist/log/index.mjs +2 -0
  11. package/dist/log/types.d.mts +71 -0
  12. package/dist/log/walk.d.mts +37 -0
  13. package/dist/log/walk.mjs +274 -0
  14. package/dist/odb/sqlite.d.mts +23 -0
  15. package/dist/odb/sqlite.mjs +83 -0
  16. package/dist/refs/shallow/sqlite.d.mts +23 -0
  17. package/dist/refs/shallow/sqlite.mjs +61 -0
  18. package/dist/refs/sqlite.d.mts +23 -0
  19. package/dist/refs/sqlite.mjs +135 -0
  20. package/dist/remote/http.d.mts +51 -0
  21. package/dist/remote/http.mjs +74 -0
  22. package/dist/remote/types.d.mts +20 -0
  23. package/dist/repository/import/import-session-types.d.mts +3 -16
  24. package/dist/repository/ops/object-operations.mjs +0 -5
  25. package/dist/repository/ops/object-types.d.mts +0 -21
  26. package/dist/repository/sqlite.d.mts +28 -0
  27. package/dist/repository/sqlite.mjs +45 -0
  28. package/dist/transport/upload-pack.d.mts +1 -1
  29. package/dist/transport/upload-pack.mjs +1 -1
  30. package/dist/workdir/change-log.mjs +63 -0
  31. package/dist/workdir/core.d.mts +166 -0
  32. package/dist/workdir/core.mjs +2 -0
  33. package/dist/workdir/ids.mjs +20 -0
  34. package/dist/workdir/memory-backend.mjs +21 -0
  35. package/dist/workdir/memory.d.mts +2 -0
  36. package/dist/workdir/memory.mjs +2 -0
  37. package/dist/workdir/nodes.mjs +112 -0
  38. package/dist/workdir/origin.mjs +55 -0
  39. package/dist/workdir/overlay.mjs +95 -0
  40. package/dist/workdir/path.mjs +70 -0
  41. package/dist/workdir/session-internal.mjs +141 -0
  42. package/dist/workdir/session.d.mts +18 -0
  43. package/dist/workdir/session.mjs +380 -0
  44. package/dist/workdir/write-tree.mjs +97 -0
  45. package/package.json +21 -3
@@ -0,0 +1,274 @@
1
+ import { tryReadObject } from "../objects/raw.mjs";
2
+ //#region src/log/walk.ts
3
+ /**
4
+ * 提交日志遍历核心实现
5
+ *
6
+ * 提供 Generator 风格的 `walkLogEntries`,支持按提交时间降序
7
+ * 或严格拓扑序遍历提交历史。
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * // 从 HEAD 开始遍历最近 10 条提交
12
+ * const headHash = resolveRefHash(repo.refs, "HEAD")!;
13
+ * for (const entry of walkLogEntries(repo.objects, { from: [headHash], maxCount: 10 })) {
14
+ * console.log(entry.hash, entry.commit.message);
15
+ * }
16
+ *
17
+ * // 等价于 git log main..feature
18
+ * const featureHash = resolveRefHash(repo.refs, "refs/heads/feature")!;
19
+ * const mainHash = resolveRefHash(repo.refs, "refs/heads/main")!;
20
+ * for (const entry of walkLogEntries(repo.objects, { from: [featureHash], exclude: [mainHash] })) {
21
+ * console.log(entry.hash, entry.commit.message);
22
+ * }
23
+ * ```
24
+ */
25
+ var MaxHeapByTimestamp = class {
26
+ heap = [];
27
+ get size() {
28
+ return this.heap.length;
29
+ }
30
+ push(entry) {
31
+ this.heap.push(entry);
32
+ this.siftUp(this.heap.length - 1);
33
+ }
34
+ pop() {
35
+ if (this.heap.length === 0) return void 0;
36
+ const top = this.heap[0];
37
+ const bottom = this.heap.pop();
38
+ if (this.heap.length > 0) {
39
+ this.heap[0] = bottom;
40
+ this.siftDown(0);
41
+ }
42
+ return top;
43
+ }
44
+ siftUp(idx) {
45
+ const heap = this.heap;
46
+ while (idx > 0) {
47
+ const parent = idx - 1 >> 1;
48
+ if (heap[parent].commit.committer.timestamp >= heap[idx].commit.committer.timestamp) break;
49
+ [heap[parent], heap[idx]] = [heap[idx], heap[parent]];
50
+ idx = parent;
51
+ }
52
+ }
53
+ siftDown(idx) {
54
+ const heap = this.heap;
55
+ const end = heap.length;
56
+ while (true) {
57
+ let largest = idx;
58
+ const left = (idx << 1) + 1;
59
+ const right = left + 1;
60
+ if (left < end && heap[left].commit.committer.timestamp > heap[largest].commit.committer.timestamp) largest = left;
61
+ if (right < end && heap[right].commit.committer.timestamp > heap[largest].commit.committer.timestamp) largest = right;
62
+ if (largest === idx) break;
63
+ [heap[idx], heap[largest]] = [heap[largest], heap[idx]];
64
+ idx = largest;
65
+ }
66
+ }
67
+ };
68
+ /**
69
+ * 递归标记排除提交及其所有祖先
70
+ */
71
+ function markExcluded(source, hash, excluded) {
72
+ const stack = [hash];
73
+ while (stack.length > 0) {
74
+ const current = stack.pop();
75
+ if (excluded.has(current)) continue;
76
+ excluded.add(current);
77
+ const obj = tryReadObject(source, current);
78
+ if (obj?.type === "commit") {
79
+ for (const parent of obj.parents) if (!excluded.has(parent)) stack.push(parent);
80
+ }
81
+ }
82
+ }
83
+ /**
84
+ * 按提交时间降序遍历提交历史
85
+ *
86
+ * 使用最大堆按时间戳排序,每次弹出最新提交后将其 parent 入堆。
87
+ * 可配合 firstParent、since、until、skip、maxCount 等过滤条件。
88
+ */
89
+ function* walkByDate(source, from, excluded, firstParent, skip, maxCount, since, until) {
90
+ const queue = new MaxHeapByTimestamp();
91
+ const seen = /* @__PURE__ */ new Set();
92
+ const visited = /* @__PURE__ */ new Set();
93
+ for (const hash of from) {
94
+ if (excluded.has(hash) || seen.has(hash)) continue;
95
+ const obj = tryReadObject(source, hash);
96
+ if (obj?.type !== "commit") continue;
97
+ seen.add(hash);
98
+ queue.push({
99
+ hash,
100
+ commit: obj
101
+ });
102
+ }
103
+ let skipped = 0;
104
+ let emitted = 0;
105
+ while (queue.size > 0) {
106
+ if (maxCount !== void 0 && emitted >= maxCount) break;
107
+ const entry = queue.pop();
108
+ if (visited.has(entry.hash)) continue;
109
+ if (excluded.has(entry.hash)) continue;
110
+ if (since !== void 0 && entry.commit.committer.timestamp < since) continue;
111
+ if (until !== void 0 && entry.commit.committer.timestamp > until) {
112
+ enqueueParents(source, entry.commit.parents, firstParent, excluded, seen, visited, queue);
113
+ continue;
114
+ }
115
+ if (skipped < skip) {
116
+ skipped++;
117
+ visited.add(entry.hash);
118
+ enqueueParents(source, entry.commit.parents, firstParent, excluded, seen, visited, queue);
119
+ continue;
120
+ }
121
+ visited.add(entry.hash);
122
+ emitted++;
123
+ yield {
124
+ hash: entry.hash,
125
+ commit: entry.commit
126
+ };
127
+ enqueueParents(source, entry.commit.parents, firstParent, excluded, seen, visited, queue);
128
+ }
129
+ }
130
+ /**
131
+ * 将提交的 parent 批量入堆
132
+ */
133
+ function enqueueParents(source, parents, firstParent, excluded, seen, visited, queue) {
134
+ const targetParents = firstParent && parents.length > 0 ? [parents[0]] : parents;
135
+ for (const parentHash of targetParents) {
136
+ if (seen.has(parentHash) || excluded.has(parentHash)) continue;
137
+ seen.add(parentHash);
138
+ const obj = tryReadObject(source, parentHash);
139
+ if (obj?.type !== "commit") continue;
140
+ queue.push({
141
+ hash: parentHash,
142
+ commit: obj
143
+ });
144
+ }
145
+ }
146
+ /**
147
+ * 生成严格拓扑序(子提交先于父提交)的提交迭代器
148
+ *
149
+ * 实现 Kahn 算法:
150
+ * 1. 收集所有起点可达的提交,构建 parent→children 映射
151
+ * 2. 统计每个提交的未输出子提交数(childCount)
152
+ * 3. 将 childCount === 0 的提交入堆(同层按时间戳降序)
153
+ * 4. 每次弹出堆顶输出,递减其 parent 的 childCount
154
+ * 5. parent 的 childCount 归零时入堆
155
+ */
156
+ function* walkTopo(source, from, excluded, firstParent, skip, maxCount, since, until) {
157
+ const commits = /* @__PURE__ */ new Map();
158
+ const children = /* @__PURE__ */ new Map();
159
+ const stack = [...from];
160
+ while (stack.length > 0) {
161
+ const hash = stack.pop();
162
+ if (commits.has(hash) || excluded.has(hash)) continue;
163
+ const obj = tryReadObject(source, hash);
164
+ if (obj?.type !== "commit") continue;
165
+ commits.set(hash, obj);
166
+ if (!children.has(hash)) children.set(hash, []);
167
+ const targetParents = firstParent && obj.parents.length > 0 ? [obj.parents[0]] : obj.parents;
168
+ for (const parentHash of targetParents) {
169
+ if (commits.has(parentHash) || excluded.has(parentHash)) continue;
170
+ if (!children.has(parentHash)) children.set(parentHash, []);
171
+ children.get(parentHash).push(hash);
172
+ stack.push(parentHash);
173
+ }
174
+ }
175
+ if (commits.size === 0) return;
176
+ const childCount = /* @__PURE__ */ new Map();
177
+ const pq = new MaxHeapByTimestamp();
178
+ for (const hash of commits.keys()) {
179
+ const count = children.get(hash)?.length ?? 0;
180
+ childCount.set(hash, count);
181
+ if (count === 0) pq.push({
182
+ hash,
183
+ commit: commits.get(hash)
184
+ });
185
+ }
186
+ let skipped = 0;
187
+ let emitted = 0;
188
+ while (pq.size > 0) {
189
+ if (maxCount !== void 0 && emitted >= maxCount) break;
190
+ const entry = pq.pop();
191
+ if (since !== void 0 && entry.commit.committer.timestamp < since) {
192
+ decrementParentCounts(entry.commit.parents, firstParent, childCount, commits, pq);
193
+ continue;
194
+ }
195
+ if (until !== void 0 && entry.commit.committer.timestamp > until) {
196
+ decrementParentCounts(entry.commit.parents, firstParent, childCount, commits, pq);
197
+ continue;
198
+ }
199
+ if (skipped < skip) {
200
+ skipped++;
201
+ decrementParentCounts(entry.commit.parents, firstParent, childCount, commits, pq);
202
+ continue;
203
+ }
204
+ emitted++;
205
+ yield {
206
+ hash: entry.hash,
207
+ commit: entry.commit
208
+ };
209
+ decrementParentCounts(entry.commit.parents, firstParent, childCount, commits, pq);
210
+ }
211
+ }
212
+ /**
213
+ * 递减 parent 的 childCount,归零时入堆
214
+ */
215
+ function decrementParentCounts(parents, firstParent, childCount, commits, pq) {
216
+ const targetParents = firstParent && parents.length > 0 ? [parents[0]] : parents;
217
+ for (const parentHash of targetParents) {
218
+ const count = childCount.get(parentHash);
219
+ if (count === void 0) continue;
220
+ const newCount = count - 1;
221
+ childCount.set(parentHash, newCount);
222
+ if (newCount === 0) {
223
+ const parentCommit = commits.get(parentHash);
224
+ if (parentCommit) pq.push({
225
+ hash: parentHash,
226
+ commit: parentCommit
227
+ });
228
+ }
229
+ }
230
+ }
231
+ /**
232
+ * 遍历提交历史日志
233
+ *
234
+ * 从指定的起点哈希出发,沿 parent 链回溯,按指定排序策略输出提交。
235
+ * 不涉及 ref 解析——调用方需自行将 ref 名称解析为 SHA1。
236
+ *
237
+ * @param source - 对象源(通常是 `Repository.objects`)
238
+ * @param options - 遍历选项
239
+ * @returns 按序输出的提交日志条目生成器
240
+ *
241
+ * @example
242
+ * ```ts
243
+ * const headHash = resolveRefHash(repo.refs, "HEAD")!;
244
+ * for (const entry of walkLogEntries(repo.objects, { from: [headHash], maxCount: 5 })) {
245
+ * console.log(entry.hash, entry.commit.subject);
246
+ * }
247
+ * ```
248
+ *
249
+ * @example
250
+ * ```ts
251
+ * // 等价于 git log --oneline --since=1600000000 --until=1700000000
252
+ * for (const entry of walkLogEntries(repo.objects, {
253
+ * from: [headHash],
254
+ * since: 1600000000,
255
+ * until: 1700000000,
256
+ * })) {
257
+ * console.log(entry.hash.slice(0, 7), entry.commit.message.split("\n")[0]);
258
+ * }
259
+ * ```
260
+ */
261
+ function walkLogEntries(source, options = {}) {
262
+ const { from = [], exclude = [], skip: skipCount = 0, order = "date", since, until, firstParent = false, maxCount } = options;
263
+ if (from.length === 0) return emptyGenerator();
264
+ const excluded = /* @__PURE__ */ new Set();
265
+ for (const hash of exclude) markExcluded(source, hash, excluded);
266
+ if (order === "topo") return walkTopo(source, from, excluded, firstParent, skipCount, maxCount, since, until);
267
+ return walkByDate(source, from, excluded, firstParent, skipCount, maxCount, since, until);
268
+ }
269
+ /** 返回一个空的 Generator(不产生任何值,直接 done) */
270
+ function emptyGenerator() {
271
+ return [][Symbol.iterator]();
272
+ }
273
+ //#endregion
274
+ export { walkLogEntries };
@@ -0,0 +1,23 @@
1
+ import { ObjectDatabase } from "../core/types/odb.mjs";
2
+ import { Database } from "bun:sqlite";
3
+
4
+ //#region src/odb/sqlite.d.ts
5
+ /**
6
+ * 创建基于 SQLite 的对象数据库
7
+ *
8
+ * @param db - 已打开的 bun:sqlite Database 实例
9
+ * @returns 符合 ObjectDatabase 接口的存储后端
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * import { Database } from "bun:sqlite";
14
+ * const db = new Database("/tmp/repo.sqlite");
15
+ * const store = createSqliteObjectStore(db);
16
+ *
17
+ * store.ingest(raw);
18
+ * const obj = store.read(hash);
19
+ * ```
20
+ */
21
+ declare function createSqliteObjectStore(db: Database): ObjectDatabase;
22
+ //#endregion
23
+ export { createSqliteObjectStore };
@@ -0,0 +1,83 @@
1
+ import { ObjectNotFoundError } from "../core/errors.mjs";
2
+ import { sha1 } from "../core/types.mjs";
3
+ import { hashObject } from "../core/hash-digest.mjs";
4
+ //#region src/odb/sqlite.ts
5
+ /**
6
+ * 基于 SQLite 的对象数据库(raw-first)
7
+ *
8
+ * 所有对象存储在 SQLite 数据库的 objects 表中。
9
+ * 使用 INSERT OR IGNORE 实现幂等写入,使用 db.transaction() 实现批量原子写入。
10
+ *
11
+ * 表创建由上层 createSqliteRepositoryBackend 负责,
12
+ * 本模块只操作已存在的表,不负责 DDL。
13
+ */
14
+ /**
15
+ * 创建基于 SQLite 的对象数据库
16
+ *
17
+ * @param db - 已打开的 bun:sqlite Database 实例
18
+ * @returns 符合 ObjectDatabase 接口的存储后端
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * import { Database } from "bun:sqlite";
23
+ * const db = new Database("/tmp/repo.sqlite");
24
+ * const store = createSqliteObjectStore(db);
25
+ *
26
+ * store.ingest(raw);
27
+ * const obj = store.read(hash);
28
+ * ```
29
+ */
30
+ function createSqliteObjectStore(db) {
31
+ const selectStmt = db.query("SELECT hash, type, content FROM objects WHERE hash = ?");
32
+ const existsStmt = db.query("SELECT 1 FROM objects WHERE hash = ?");
33
+ const insertStmt = db.query("INSERT OR IGNORE INTO objects (hash, type, content) VALUES (?, ?, ?)");
34
+ const deleteStmt = db.query("DELETE FROM objects WHERE hash = ?");
35
+ const listStmt = db.query("SELECT hash FROM objects ORDER BY hash");
36
+ /** 批量插入的事务包装 */
37
+ const ingestManyTx = db.transaction((objects) => {
38
+ for (const raw of objects) {
39
+ const expectedHash = hashObject(raw.type, raw.content);
40
+ if (expectedHash !== raw.hash) throw new Error(`RawGitObject hash mismatch: expected ${expectedHash}, got ${raw.hash}`);
41
+ insertStmt.run(raw.hash, raw.type, raw.content);
42
+ }
43
+ });
44
+ return {
45
+ ingest(raw) {
46
+ const expectedHash = hashObject(raw.type, raw.content);
47
+ if (expectedHash !== raw.hash) throw new Error(`RawGitObject hash mismatch: expected ${expectedHash}, got ${raw.hash}`);
48
+ insertStmt.run(raw.hash, raw.type, raw.content);
49
+ },
50
+ ingestMany(objects) {
51
+ ingestManyTx(objects);
52
+ },
53
+ read(hash) {
54
+ const row = selectStmt.get(hash);
55
+ if (!row) throw new ObjectNotFoundError(hash);
56
+ return {
57
+ hash: sha1(row.hash),
58
+ type: row.type,
59
+ content: Buffer.from(row.content)
60
+ };
61
+ },
62
+ tryRead(hash) {
63
+ const row = selectStmt.get(hash);
64
+ if (!row) return;
65
+ return {
66
+ hash: sha1(row.hash),
67
+ type: row.type,
68
+ content: Buffer.from(row.content)
69
+ };
70
+ },
71
+ exists(hash) {
72
+ return existsStmt.get(hash) !== null;
73
+ },
74
+ list() {
75
+ return listStmt.all().map((row) => sha1(row.hash));
76
+ },
77
+ delete(hash) {
78
+ deleteStmt.run(hash);
79
+ }
80
+ };
81
+ }
82
+ //#endregion
83
+ export { createSqliteObjectStore };
@@ -0,0 +1,23 @@
1
+ import { ShallowStore } from "../../core/types/shallow.mjs";
2
+ import { Database } from "bun:sqlite";
3
+
4
+ //#region src/refs/shallow/sqlite.d.ts
5
+ /**
6
+ * 创建基于 SQLite 的 shallow 边界存储
7
+ *
8
+ * @param db - 已打开的 bun:sqlite Database 实例
9
+ * @returns 符合 ShallowStore 接口的存储后端
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * import { Database } from "bun:sqlite";
14
+ * const db = new Database("/tmp/repo.sqlite");
15
+ * const store = createSqliteShallowStore(db);
16
+ *
17
+ * store.write([hashA, hashB]);
18
+ * console.log(store.isShallow(hashA)); // true
19
+ * ```
20
+ */
21
+ declare function createSqliteShallowStore(db: Database): ShallowStore;
22
+ //#endregion
23
+ export { createSqliteShallowStore };
@@ -0,0 +1,61 @@
1
+ import { sha1 } from "../../core/types.mjs";
2
+ //#region src/refs/shallow/sqlite.ts
3
+ /**
4
+ * 基于 SQLite 的 Shallow 存储
5
+ *
6
+ * 所有 shallow 边界存储在 SQLite 数据库的 shallow 表中。
7
+ * write 使用 DELETE + INSERT 全量替换模式(与 memory/file 后端一致)。
8
+ * applyUpdate 使用 SQL 事务做增量更新。
9
+ *
10
+ * 表创建由上层 createSqliteRepositoryBackend 负责,
11
+ * 本模块只操作已存在的表,不负责 DDL。
12
+ */
13
+ /**
14
+ * 创建基于 SQLite 的 shallow 边界存储
15
+ *
16
+ * @param db - 已打开的 bun:sqlite Database 实例
17
+ * @returns 符合 ShallowStore 接口的存储后端
18
+ *
19
+ * @example
20
+ * ```ts
21
+ * import { Database } from "bun:sqlite";
22
+ * const db = new Database("/tmp/repo.sqlite");
23
+ * const store = createSqliteShallowStore(db);
24
+ *
25
+ * store.write([hashA, hashB]);
26
+ * console.log(store.isShallow(hashA)); // true
27
+ * ```
28
+ */
29
+ function createSqliteShallowStore(db) {
30
+ const selectAllStmt = db.query("SELECT hash FROM shallow ORDER BY hash");
31
+ const selectExistsStmt = db.query("SELECT 1 FROM shallow WHERE hash = ?");
32
+ const deleteAllStmt = db.query("DELETE FROM shallow");
33
+ const insertStmt = db.query("INSERT OR IGNORE INTO shallow (hash) VALUES (?)");
34
+ const deleteOneStmt = db.query("DELETE FROM shallow WHERE hash = ?");
35
+ /** 全量替换事务 */
36
+ const replaceAllTx = db.transaction((boundaries) => {
37
+ deleteAllStmt.run();
38
+ for (const hash of boundaries) insertStmt.run(hash);
39
+ });
40
+ /** 增量更新事务 */
41
+ const applyUpdateTx = db.transaction((update) => {
42
+ for (const hash of update.unshallow) deleteOneStmt.run(hash);
43
+ for (const hash of update.shallow) insertStmt.run(hash);
44
+ });
45
+ return {
46
+ read() {
47
+ return selectAllStmt.all().map((row) => sha1(row.hash));
48
+ },
49
+ write(boundaries) {
50
+ replaceAllTx(boundaries);
51
+ },
52
+ applyUpdate(update) {
53
+ applyUpdateTx(update);
54
+ },
55
+ isShallow(hash) {
56
+ return selectExistsStmt.get(hash) !== null;
57
+ }
58
+ };
59
+ }
60
+ //#endregion
61
+ export { createSqliteShallowStore };
@@ -0,0 +1,23 @@
1
+ import { RefStore } from "../core/types/refs.mjs";
2
+ import { Database } from "bun:sqlite";
3
+
4
+ //#region src/refs/sqlite.d.ts
5
+ /**
6
+ * 创建基于 SQLite 的 RefStore
7
+ *
8
+ * @param db - 已打开的 bun:sqlite Database 实例(生命周期由调用方管理)
9
+ * @returns 符合 RefStore 接口的存储后端(含事务支持)
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * import { Database } from "bun:sqlite";
14
+ * const db = new Database("/tmp/repo.sqlite");
15
+ * const store = createSqliteRefStore(db);
16
+ *
17
+ * store.write("refs/heads/main", "abc123");
18
+ * const content = store.read("refs/heads/main");
19
+ * ```
20
+ */
21
+ declare function createSqliteRefStore(db: Database): RefStore;
22
+ //#endregion
23
+ export { createSqliteRefStore };
@@ -0,0 +1,135 @@
1
+ import { RefNotFoundError, TransactionError } from "../core/errors.mjs";
2
+ import { validateRefName, validateRefPrefix } from "./names.mjs";
3
+ //#region src/refs/sqlite.ts
4
+ /**
5
+ * 基于 SQLite 的 Refs 存储
6
+ *
7
+ * 所有引用存储在 SQLite 数据库的 refs 表中。
8
+ * 使用 INSERT OR REPLACE 实现幂等写入,使用 db.transaction() 实现事务原子性。
9
+ *
10
+ * 表创建由上层 createSqliteRepositoryBackend 负责,
11
+ * 本模块只操作已存在的表,不负责 DDL。
12
+ */
13
+ /**
14
+ * 创建基于 SQLite 的 RefStore
15
+ *
16
+ * @param db - 已打开的 bun:sqlite Database 实例(生命周期由调用方管理)
17
+ * @returns 符合 RefStore 接口的存储后端(含事务支持)
18
+ *
19
+ * @example
20
+ * ```ts
21
+ * import { Database } from "bun:sqlite";
22
+ * const db = new Database("/tmp/repo.sqlite");
23
+ * const store = createSqliteRefStore(db);
24
+ *
25
+ * store.write("refs/heads/main", "abc123");
26
+ * const content = store.read("refs/heads/main");
27
+ * ```
28
+ */
29
+ function createSqliteRefStore(db) {
30
+ const selectStmt = db.query("SELECT target FROM refs WHERE name = ?");
31
+ const selectExistsStmt = db.query("SELECT 1 FROM refs WHERE name = ?");
32
+ const insertStmt = db.query("INSERT OR REPLACE INTO refs (name, target) VALUES (?, ?)");
33
+ const deleteStmt = db.query("DELETE FROM refs WHERE name = ?");
34
+ const listPrefixStmt = db.query("SELECT name FROM refs WHERE name >= ? AND name < ? ORDER BY name");
35
+ const listAllStmt = db.query("SELECT name FROM refs WHERE name LIKE 'refs/%' ORDER BY name");
36
+ /**
37
+ * 开启一个新的事务
38
+ *
39
+ * 所有变更暂存于 JS Map,commit() 时通过 SQLite 事务原子性写入。
40
+ */
41
+ function beginTransaction(hooks) {
42
+ const pending = /* @__PURE__ */ new Map();
43
+ const snapshot = /* @__PURE__ */ new Map();
44
+ for (const row of listAllStmt.all()) {
45
+ const val = selectStmt.get(row.name);
46
+ if (val !== null) snapshot.set(row.name, val.target);
47
+ }
48
+ let committed = false;
49
+ return {
50
+ get pendingCount() {
51
+ return pending.size;
52
+ },
53
+ write(ref, content) {
54
+ if (committed) throw new TransactionError("Transaction already committed");
55
+ validateRefName(ref);
56
+ pending.set(ref, content.trimEnd());
57
+ },
58
+ delete(ref) {
59
+ if (committed) throw new TransactionError("Transaction already committed");
60
+ validateRefName(ref);
61
+ const inDb = selectExistsStmt.get(ref) !== null;
62
+ const inPending = pending.has(ref);
63
+ if (!inDb && !inPending) throw new RefNotFoundError(ref);
64
+ pending.set(ref, null);
65
+ },
66
+ commit() {
67
+ if (committed) throw new TransactionError("Transaction already committed");
68
+ committed = true;
69
+ const txSnapshot = freezePending(pending);
70
+ try {
71
+ for (const hook of hooks ?? []) hook.onPrepare?.(txSnapshot);
72
+ db.transaction(() => {
73
+ for (const [ref, content] of pending) if (content === null) {
74
+ if (!snapshot.has(ref) && !selectExistsStmt.get(ref)) throw new RefNotFoundError(ref);
75
+ deleteStmt.run(ref);
76
+ } else insertStmt.run(ref, content);
77
+ })();
78
+ for (const hook of hooks ?? []) hook.onCommitted?.(txSnapshot);
79
+ } catch (e) {
80
+ for (const hook of hooks ?? []) hook.onAborted?.(txSnapshot);
81
+ throw e;
82
+ }
83
+ },
84
+ rollback() {
85
+ if (committed) return;
86
+ committed = true;
87
+ const txSnapshot = freezePending(pending);
88
+ for (const hook of hooks ?? []) hook.onAborted?.(txSnapshot);
89
+ }
90
+ };
91
+ }
92
+ return {
93
+ read(ref) {
94
+ validateRefName(ref);
95
+ return selectStmt.get(ref)?.target ?? null;
96
+ },
97
+ write(ref, content) {
98
+ validateRefName(ref);
99
+ insertStmt.run(ref, content.trimEnd());
100
+ },
101
+ delete(ref) {
102
+ validateRefName(ref);
103
+ if (!selectExistsStmt.get(ref)) throw new RefNotFoundError(ref);
104
+ deleteStmt.run(ref);
105
+ },
106
+ list(prefix) {
107
+ validateRefPrefix(prefix);
108
+ const end = prefix + "";
109
+ return listPrefixStmt.all(prefix, end).map((row) => row.name);
110
+ },
111
+ listAll() {
112
+ return listAllStmt.all().map((row) => row.name);
113
+ },
114
+ beginTransaction
115
+ };
116
+ }
117
+ /**
118
+ * 将 pending Map 冻结为只读快照
119
+ */
120
+ function freezePending(pending) {
121
+ const writes = [];
122
+ const deletes = [];
123
+ for (const [ref, content] of pending) if (content === null) deletes.push({ ref });
124
+ else writes.push({
125
+ ref,
126
+ content
127
+ });
128
+ return Object.freeze({
129
+ pendingCount: pending.size,
130
+ writes: Object.freeze(writes),
131
+ deletes: Object.freeze(deletes)
132
+ });
133
+ }
134
+ //#endregion
135
+ export { createSqliteRefStore };
@@ -0,0 +1,51 @@
1
+ import { RemoteSource } from "./types.mjs";
2
+ import { RefAdvertisement } from "../transport/protocol/types.mjs";
3
+ import { LsRefsEntry, V2CapabilityAdvertisement } from "../transport/client/upload-pack/types.mjs";
4
+ import { LsRefsOptions } from "../transport/client/upload-pack/ls-refs.mjs";
5
+ import { ObjectInfoQueryResult } from "../transport/client/upload-pack/object-info.mjs";
6
+
7
+ //#region src/remote/http.d.ts
8
+ /**
9
+ * 远端 HTTP 查询接口
10
+ */
11
+ interface HttpRemote {
12
+ /** 远端来源配置 */
13
+ readonly source: Readonly<RemoteSource>;
14
+ /** 读取 v2 能力广告 */
15
+ advertise(): Promise<V2CapabilityAdvertisement>;
16
+ /** 原样执行 ls-refs 查询 */
17
+ listRefs(options?: LsRefsOptions): Promise<LsRefsEntry[]>;
18
+ /** 读取适合高层 API 使用的 ref 快照 */
19
+ readRefAdvertisement(): Promise<RefAdvertisement>;
20
+ /** 查询对象元数据(协议 v2 object-info) */
21
+ fetchObjectInfo(oids: string[]): Promise<ObjectInfoQueryResult>;
22
+ }
23
+ /**
24
+ * 创建基于 Smart HTTP 的远端查询对象
25
+ *
26
+ * 适用于只依赖远端 URL / 认证信息的查询,
27
+ * 例如 refs 快照和 object-info,不需要本地 repo 上下文。
28
+ *
29
+ * @param source - 远端来源
30
+ * @returns 远端查询对象
31
+ *
32
+ * @example
33
+ * ```ts
34
+ * import { createHttpRemote } from "nano-git/remote/http";
35
+ *
36
+ * const remote = createHttpRemote({
37
+ * url: "https://github.com/user/repo.git",
38
+ * });
39
+ *
40
+ * const snapshot = await remote.readRefAdvertisement();
41
+ * const info = await remote.fetchObjectInfo([
42
+ * "95d09f2b10159347eece71399a7e2e907ea3df4f",
43
+ * ]);
44
+ *
45
+ * console.log(snapshot.defaultBranch);
46
+ * console.log(info.objects[0]?.size);
47
+ * ```
48
+ */
49
+ declare function createHttpRemote(source: RemoteSource): HttpRemote;
50
+ //#endregion
51
+ export { HttpRemote, createHttpRemote };