trove-cli 0.0.0-stage → 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.
Files changed (210) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +202 -2
  3. package/dist/adapters/file-source.d.ts +31 -0
  4. package/dist/adapters/file-source.js +97 -0
  5. package/dist/adapters/file-source.js.map +1 -0
  6. package/dist/adapters/index.d.ts +34 -0
  7. package/dist/adapters/index.js +146 -0
  8. package/dist/adapters/index.js.map +1 -0
  9. package/dist/adapters/opencode.d.ts +30 -0
  10. package/dist/adapters/opencode.js +135 -0
  11. package/dist/adapters/opencode.js.map +1 -0
  12. package/dist/cli.d.ts +58 -0
  13. package/dist/cli.js +974 -0
  14. package/dist/cli.js.map +1 -0
  15. package/dist/config.d.ts +81 -0
  16. package/dist/config.js +99 -0
  17. package/dist/config.js.map +1 -0
  18. package/dist/core/archive.d.ts +27 -0
  19. package/dist/core/archive.js +101 -0
  20. package/dist/core/archive.js.map +1 -0
  21. package/dist/core/chunker.d.ts +28 -0
  22. package/dist/core/chunker.js +67 -0
  23. package/dist/core/chunker.js.map +1 -0
  24. package/dist/core/clean.d.ts +36 -0
  25. package/dist/core/clean.js +182 -0
  26. package/dist/core/clean.js.map +1 -0
  27. package/dist/core/collect.d.ts +39 -0
  28. package/dist/core/collect.js +194 -0
  29. package/dist/core/collect.js.map +1 -0
  30. package/dist/core/concurrency.d.ts +2 -0
  31. package/dist/core/concurrency.js +16 -0
  32. package/dist/core/concurrency.js.map +1 -0
  33. package/dist/core/cursor.d.ts +31 -0
  34. package/dist/core/cursor.js +31 -0
  35. package/dist/core/cursor.js.map +1 -0
  36. package/dist/core/events/claude.d.ts +7 -0
  37. package/dist/core/events/claude.js +287 -0
  38. package/dist/core/events/claude.js.map +1 -0
  39. package/dist/core/events/codex.d.ts +6 -0
  40. package/dist/core/events/codex.js +149 -0
  41. package/dist/core/events/codex.js.map +1 -0
  42. package/dist/core/events/doubao.d.ts +9 -0
  43. package/dist/core/events/doubao.js +151 -0
  44. package/dist/core/events/doubao.js.map +1 -0
  45. package/dist/core/events/index.d.ts +6 -0
  46. package/dist/core/events/index.js +20 -0
  47. package/dist/core/events/index.js.map +1 -0
  48. package/dist/core/events/opencode.d.ts +7 -0
  49. package/dist/core/events/opencode.js +189 -0
  50. package/dist/core/events/opencode.js.map +1 -0
  51. package/dist/core/events/schema.d.ts +83 -0
  52. package/dist/core/events/schema.js +35 -0
  53. package/dist/core/events/schema.js.map +1 -0
  54. package/dist/core/layout.d.ts +26 -0
  55. package/dist/core/layout.js +122 -0
  56. package/dist/core/layout.js.map +1 -0
  57. package/dist/core/lessons/analyze.d.ts +40 -0
  58. package/dist/core/lessons/analyze.js +107 -0
  59. package/dist/core/lessons/analyze.js.map +1 -0
  60. package/dist/core/lessons/audit.d.ts +51 -0
  61. package/dist/core/lessons/audit.js +57 -0
  62. package/dist/core/lessons/audit.js.map +1 -0
  63. package/dist/core/lessons/compaction.d.ts +5 -0
  64. package/dist/core/lessons/compaction.js +58 -0
  65. package/dist/core/lessons/compaction.js.map +1 -0
  66. package/dist/core/lessons/config.d.ts +15 -0
  67. package/dist/core/lessons/config.js +31 -0
  68. package/dist/core/lessons/config.js.map +1 -0
  69. package/dist/core/lessons/existing.d.ts +4 -0
  70. package/dist/core/lessons/existing.js +22 -0
  71. package/dist/core/lessons/existing.js.map +1 -0
  72. package/dist/core/lessons/extract.d.ts +21 -0
  73. package/dist/core/lessons/extract.js +106 -0
  74. package/dist/core/lessons/extract.js.map +1 -0
  75. package/dist/core/lessons/frontmatter.d.ts +29 -0
  76. package/dist/core/lessons/frontmatter.js +87 -0
  77. package/dist/core/lessons/frontmatter.js.map +1 -0
  78. package/dist/core/lessons/graph.d.ts +256 -0
  79. package/dist/core/lessons/graph.js +86 -0
  80. package/dist/core/lessons/graph.js.map +1 -0
  81. package/dist/core/lessons/jev.d.ts +36 -0
  82. package/dist/core/lessons/jev.js +100 -0
  83. package/dist/core/lessons/jev.js.map +1 -0
  84. package/dist/core/lessons/layout.d.ts +2 -0
  85. package/dist/core/lessons/layout.js +56 -0
  86. package/dist/core/lessons/layout.js.map +1 -0
  87. package/dist/core/lessons/learn.d.ts +38 -0
  88. package/dist/core/lessons/learn.js +190 -0
  89. package/dist/core/lessons/learn.js.map +1 -0
  90. package/dist/core/lessons/llm.d.ts +42 -0
  91. package/dist/core/lessons/llm.js +106 -0
  92. package/dist/core/lessons/llm.js.map +1 -0
  93. package/dist/core/lessons/memo.d.ts +19 -0
  94. package/dist/core/lessons/memo.js +170 -0
  95. package/dist/core/lessons/memo.js.map +1 -0
  96. package/dist/core/lessons/merge.d.ts +39 -0
  97. package/dist/core/lessons/merge.js +79 -0
  98. package/dist/core/lessons/merge.js.map +1 -0
  99. package/dist/core/lessons/queue.d.ts +6 -0
  100. package/dist/core/lessons/queue.js +52 -0
  101. package/dist/core/lessons/queue.js.map +1 -0
  102. package/dist/core/lessons/recent.d.ts +21 -0
  103. package/dist/core/lessons/recent.js +111 -0
  104. package/dist/core/lessons/recent.js.map +1 -0
  105. package/dist/core/lessons/schema.d.ts +26 -0
  106. package/dist/core/lessons/schema.js +64 -0
  107. package/dist/core/lessons/schema.js.map +1 -0
  108. package/dist/core/lessons/select.d.ts +19 -0
  109. package/dist/core/lessons/select.js +55 -0
  110. package/dist/core/lessons/select.js.map +1 -0
  111. package/dist/core/lessons/sidecar.d.ts +23 -0
  112. package/dist/core/lessons/sidecar.js +171 -0
  113. package/dist/core/lessons/sidecar.js.map +1 -0
  114. package/dist/core/lessons/storage.d.ts +21 -0
  115. package/dist/core/lessons/storage.js +65 -0
  116. package/dist/core/lessons/storage.js.map +1 -0
  117. package/dist/core/lessons/usage.d.ts +31 -0
  118. package/dist/core/lessons/usage.js +91 -0
  119. package/dist/core/lessons/usage.js.map +1 -0
  120. package/dist/core/lessons/write.d.ts +18 -0
  121. package/dist/core/lessons/write.js +131 -0
  122. package/dist/core/lessons/write.js.map +1 -0
  123. package/dist/core/library/build.d.ts +22 -0
  124. package/dist/core/library/build.js +163 -0
  125. package/dist/core/library/build.js.map +1 -0
  126. package/dist/core/library/group.d.ts +14 -0
  127. package/dist/core/library/group.js +129 -0
  128. package/dist/core/library/group.js.map +1 -0
  129. package/dist/core/library/index-file.d.ts +7 -0
  130. package/dist/core/library/index-file.js +250 -0
  131. package/dist/core/library/index-file.js.map +1 -0
  132. package/dist/core/library/schema.d.ts +78 -0
  133. package/dist/core/library/schema.js +37 -0
  134. package/dist/core/library/schema.js.map +1 -0
  135. package/dist/core/library/skills.d.ts +26 -0
  136. package/dist/core/library/skills.js +168 -0
  137. package/dist/core/library/skills.js.map +1 -0
  138. package/dist/core/library/sync.d.ts +48 -0
  139. package/dist/core/library/sync.js +271 -0
  140. package/dist/core/library/sync.js.map +1 -0
  141. package/dist/core/lock.d.ts +6 -0
  142. package/dist/core/lock.js +56 -0
  143. package/dist/core/lock.js.map +1 -0
  144. package/dist/core/pull.d.ts +23 -0
  145. package/dist/core/pull.js +147 -0
  146. package/dist/core/pull.js.map +1 -0
  147. package/dist/core/push.d.ts +29 -0
  148. package/dist/core/push.js +170 -0
  149. package/dist/core/push.js.map +1 -0
  150. package/dist/core/remote-layout.d.ts +67 -0
  151. package/dist/core/remote-layout.js +94 -0
  152. package/dist/core/remote-layout.js.map +1 -0
  153. package/dist/core/sessions.d.ts +29 -0
  154. package/dist/core/sessions.js +96 -0
  155. package/dist/core/sessions.js.map +1 -0
  156. package/dist/core/state.d.ts +94 -0
  157. package/dist/core/state.js +62 -0
  158. package/dist/core/state.js.map +1 -0
  159. package/dist/core/sync.d.ts +48 -0
  160. package/dist/core/sync.js +140 -0
  161. package/dist/core/sync.js.map +1 -0
  162. package/dist/core/time-window.d.ts +8 -0
  163. package/dist/core/time-window.js +36 -0
  164. package/dist/core/time-window.js.map +1 -0
  165. package/dist/daemon.d.ts +73 -0
  166. package/dist/daemon.js +272 -0
  167. package/dist/daemon.js.map +1 -0
  168. package/dist/format.d.ts +4 -0
  169. package/dist/format.js +28 -0
  170. package/dist/format.js.map +1 -0
  171. package/dist/main.d.ts +2 -0
  172. package/dist/main.js +9 -0
  173. package/dist/main.js.map +1 -0
  174. package/dist/paths.d.ts +19 -0
  175. package/dist/paths.js +19 -0
  176. package/dist/paths.js.map +1 -0
  177. package/dist/progress-console.d.ts +12 -0
  178. package/dist/progress-console.js +42 -0
  179. package/dist/progress-console.js.map +1 -0
  180. package/dist/progress.d.ts +7 -0
  181. package/dist/progress.js +6 -0
  182. package/dist/progress.js.map +1 -0
  183. package/dist/remote/create-remote.d.ts +20 -0
  184. package/dist/remote/create-remote.js +86 -0
  185. package/dist/remote/create-remote.js.map +1 -0
  186. package/dist/remote/prompt.d.ts +17 -0
  187. package/dist/remote/prompt.js +111 -0
  188. package/dist/remote/prompt.js.map +1 -0
  189. package/dist/remote/s3-sigv4.d.ts +28 -0
  190. package/dist/remote/s3-sigv4.js +100 -0
  191. package/dist/remote/s3-sigv4.js.map +1 -0
  192. package/dist/remote/s3-store.d.ts +75 -0
  193. package/dist/remote/s3-store.js +438 -0
  194. package/dist/remote/s3-store.js.map +1 -0
  195. package/dist/remote/s3-vendors.d.ts +31 -0
  196. package/dist/remote/s3-vendors.js +47 -0
  197. package/dist/remote/s3-vendors.js.map +1 -0
  198. package/dist/remote/store.d.ts +44 -0
  199. package/dist/remote/store.js +2 -0
  200. package/dist/remote/store.js.map +1 -0
  201. package/dist/remote/webdav-store.d.ts +41 -0
  202. package/dist/remote/webdav-store.js +285 -0
  203. package/dist/remote/webdav-store.js.map +1 -0
  204. package/dist/sqlite.d.ts +3 -0
  205. package/dist/sqlite.js +12 -0
  206. package/dist/sqlite.js.map +1 -0
  207. package/dist/status.d.ts +82 -0
  208. package/dist/status.js +162 -0
  209. package/dist/status.js.map +1 -0
  210. package/package.json +43 -4
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync.js","sourceRoot":"","sources":["../../../src/core/library/sync.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ export declare function isProcessAlive(pid: number): boolean;
2
+ /**
3
+ * 跨进程互斥锁(daemon 与手动 sync 不能同时写同一份 vault/state)。
4
+ * 陈旧锁(进程已死)自动接管;释放时校验 token,避免误删别人的锁。
5
+ */
6
+ export declare function acquireLock(path: string): Promise<() => Promise<void>>;
@@ -0,0 +1,56 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { mkdir, open, readFile, unlink } from 'node:fs/promises';
3
+ import { dirname } from 'node:path';
4
+ export function isProcessAlive(pid) {
5
+ if (!Number.isInteger(pid) || pid <= 0)
6
+ return false;
7
+ try {
8
+ process.kill(pid, 0);
9
+ return true;
10
+ }
11
+ catch {
12
+ return false;
13
+ }
14
+ }
15
+ /**
16
+ * 跨进程互斥锁(daemon 与手动 sync 不能同时写同一份 vault/state)。
17
+ * 陈旧锁(进程已死)自动接管;释放时校验 token,避免误删别人的锁。
18
+ */
19
+ export async function acquireLock(path) {
20
+ await mkdir(dirname(path), { recursive: true });
21
+ for (let attempt = 0; attempt < 2; attempt++) {
22
+ const token = randomUUID();
23
+ try {
24
+ const fh = await open(path, 'wx');
25
+ await fh.writeFile(JSON.stringify({ pid: process.pid, startedAt: Date.now(), token }));
26
+ await fh.close();
27
+ return async () => {
28
+ try {
29
+ const cur = JSON.parse(await readFile(path, 'utf8'));
30
+ if (cur.token === token)
31
+ await unlink(path);
32
+ }
33
+ catch {
34
+ // 锁已不存在
35
+ }
36
+ };
37
+ }
38
+ catch (err) {
39
+ if (err.code !== 'EEXIST')
40
+ throw err;
41
+ let existing = null;
42
+ try {
43
+ existing = JSON.parse(await readFile(path, 'utf8'));
44
+ }
45
+ catch {
46
+ existing = null;
47
+ }
48
+ if (existing && typeof existing.pid === 'number' && isProcessAlive(existing.pid)) {
49
+ throw new Error(`已有同步在运行 (pid ${existing.pid}),请等待其结束`);
50
+ }
51
+ await unlink(path).catch(() => { });
52
+ }
53
+ }
54
+ throw new Error('无法获取同步锁');
55
+ }
56
+ //# sourceMappingURL=lock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lock.js","sourceRoot":"","sources":["../../src/core/lock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AACjE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IACrD,IAAI,CAAC;QACH,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IAAY;IAC5C,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,CAAC,EAAE,OAAO,EAAE,EAAE,CAAC;QAC7C,MAAM,KAAK,GAAG,UAAU,EAAE,CAAC;QAC3B,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YAClC,MAAM,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;YACvF,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC;YACjB,OAAO,KAAK,IAAI,EAAE;gBAChB,IAAI,CAAC;oBACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAuB,CAAC;oBAC3E,IAAI,GAAG,CAAC,KAAK,KAAK,KAAK;wBAAE,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC;gBAC9C,CAAC;gBAAC,MAAM,CAAC;oBACP,QAAQ;gBACV,CAAC;YACH,CAAC,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ;gBAAE,MAAM,GAAG,CAAC;YAChE,IAAI,QAAQ,GAA4B,IAAI,CAAC;YAC7C,IAAI,CAAC;gBACH,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAqB,CAAC;YAC1E,CAAC;YAAC,MAAM,CAAC;gBACP,QAAQ,GAAG,IAAI,CAAC;YAClB,CAAC;YACD,IAAI,QAAQ,IAAI,OAAO,QAAQ,CAAC,GAAG,KAAK,QAAQ,IAAI,cAAc,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;gBACjF,MAAM,IAAI,KAAK,CAAC,gBAAgB,QAAQ,CAAC,GAAG,UAAU,CAAC,CAAC;YAC1D,CAAC;YACD,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACrC,CAAC;IACH,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,SAAS,CAAC,CAAC;AAC7B,CAAC"}
@@ -0,0 +1,23 @@
1
+ import { type Progress } from '../progress.js';
2
+ import type { State } from './state.js';
3
+ import type { RemoteStore } from '../remote/store.js';
4
+ export interface PullOptions {
5
+ remote: RemoteStore;
6
+ deviceId: string;
7
+ vaultRoot: string;
8
+ state: State;
9
+ progress?: Progress;
10
+ signal?: AbortSignal;
11
+ }
12
+ export interface PullResult {
13
+ downloadedChunks: number;
14
+ downloadedBytes: number;
15
+ downloadedItems: number;
16
+ skippedItems: number;
17
+ }
18
+ /**
19
+ * 拉取其他设备的增量,重建到本地 vault(只增不删)。
20
+ * 索引里已包含块清单,无需逐会话取清单;单条记录先写临时文件,
21
+ * 全部块校验通过后才 rename,断电不会留下半个会话。
22
+ */
23
+ export declare function pullRemote(opts: PullOptions): Promise<PullResult>;
@@ -0,0 +1,147 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { createReadStream, createWriteStream } from 'node:fs';
3
+ import { mkdir, rename, stat, unlink, writeFile } from 'node:fs/promises';
4
+ import { dirname } from 'node:path';
5
+ import { pipeline } from 'node:stream/promises';
6
+ import { hashFile } from './chunker.js';
7
+ import { throwIfAborted } from './collect.js';
8
+ import { chunkRemotePath, indexPath, itemKey, objectRemotePath, validateIndexItem, } from './remote-layout.js';
9
+ import { silentProgress } from '../progress.js';
10
+ import { EVENTS_PREFIX } from './events/schema.js';
11
+ import { rawFilePath } from './layout.js';
12
+ const SAFE_DEVICE = /^[A-Za-z0-9._-]+$/;
13
+ const SAFE_HARNESS = /^[a-z0-9-]+$/;
14
+ /**
15
+ * 拉取其他设备的增量,重建到本地 vault(只增不删)。
16
+ * 索引里已包含块清单,无需逐会话取清单;单条记录先写临时文件,
17
+ * 全部块校验通过后才 rename,断电不会留下半个会话。
18
+ */
19
+ export async function pullRemote(opts) {
20
+ const { remote, deviceId, vaultRoot, state } = opts;
21
+ const progress = opts.progress ?? silentProgress;
22
+ const devices = (await remote.listDevices()).filter((d) => SAFE_DEVICE.test(d) && d !== '.' && d !== '..');
23
+ progress.phase('pull', `${devices.length} 台设备`);
24
+ let downloadedChunks = 0;
25
+ let downloadedBytes = 0;
26
+ let downloadedItems = 0;
27
+ let skippedItems = 0;
28
+ let itemNo = 0;
29
+ for (const dev of devices) {
30
+ const rawIndex = await remote.readJson(indexPath(dev));
31
+ if (!rawIndex || typeof rawIndex !== 'object')
32
+ continue;
33
+ const index = rawIndex;
34
+ if (!index.items || typeof index.items !== 'object')
35
+ continue;
36
+ const pulled = (state.pulled[dev] ??= {});
37
+ const entries = Object.values(index.items);
38
+ for (const item of entries) {
39
+ throwIfAborted(opts.signal);
40
+ itemNo++;
41
+ if (!SAFE_HARNESS.test(item.harness)) {
42
+ throw new Error(`远端 harness 名非法: ${item.harness}`);
43
+ }
44
+ validateIndexItem(item);
45
+ // 旧版派生数据(对话层)不再放进 raw;trove 端已迁移到 library
46
+ if (item.relPath.startsWith(EVENTS_PREFIX)) {
47
+ skippedItems++;
48
+ continue;
49
+ }
50
+ const stateKey = itemKey(item.harness, item.key);
51
+ if (pulled[stateKey]?.chunksHash === item.chunksHash) {
52
+ skippedItems++;
53
+ progress.item(`${dev}/${item.relPath}`, itemNo, entries.length, 0);
54
+ continue;
55
+ }
56
+ // 本机设备且本地推送记录与索引一致:直接记账,省掉逐文件 stat
57
+ if (dev === deviceId) {
58
+ const pushed = state.pushed[item.harness]?.[item.relPath];
59
+ if (pushed && pushed.generation === item.generation && pushed.size === item.size) {
60
+ pulled[stateKey] = {
61
+ generation: item.generation,
62
+ size: item.size,
63
+ chunksHash: item.chunksHash,
64
+ };
65
+ skippedItems++;
66
+ progress.item(`${dev}/${item.relPath}`, itemNo, entries.length, 0);
67
+ continue;
68
+ }
69
+ }
70
+ const target = rawFilePath(vaultRoot, dev, item.harness, item.relPath);
71
+ let localSize = -1;
72
+ try {
73
+ localSize = (await stat(target)).size;
74
+ }
75
+ catch {
76
+ localSize = -1;
77
+ }
78
+ // 本机设备:只要本地文件存在就视为权威,绝不回拉覆盖自己。
79
+ // (世代不一致 + 本地更小时,远端可能是更旧的快照;覆盖会永久丢失本地新内容。)
80
+ if (dev === deviceId && localSize >= 0) {
81
+ pulled[stateKey] = {
82
+ generation: item.generation,
83
+ size: item.size,
84
+ chunksHash: item.chunksHash,
85
+ };
86
+ skippedItems++;
87
+ continue;
88
+ }
89
+ const sameGeneration = pulled[stateKey]?.generation === item.generation;
90
+ // 续传起点必须是清单里的真实块边界(清单可能来自不同块大小的旧版本)
91
+ let start = 0;
92
+ if (sameGeneration && localSize >= 0 && localSize <= item.size) {
93
+ const incomplete = item.chunks.find((c) => c.offset + c.length > localSize);
94
+ start = incomplete ? incomplete.offset : item.size;
95
+ }
96
+ const tmp = `${target}.tmp-${randomUUID()}`;
97
+ try {
98
+ await mkdir(dirname(target), { recursive: true });
99
+ if (start > 0) {
100
+ await pipeline(createReadStream(target, { start: 0, end: start - 1 }), createWriteStream(tmp));
101
+ }
102
+ else {
103
+ await writeFile(tmp, '');
104
+ }
105
+ for (const chunk of item.chunks) {
106
+ if (chunk.offset < start)
107
+ continue;
108
+ const tmpChunk = `${tmp}.chunk-${chunk.offset}`;
109
+ const remotePath = (item.layout ?? 'chunk') === 'object'
110
+ ? objectRemotePath(dev, item.harness, item.relPath)
111
+ : chunkRemotePath(dev, item.harness, item.key, item.generation, chunk);
112
+ try {
113
+ await remote.readFile(remotePath, tmpChunk);
114
+ const got = await hashFile(tmpChunk);
115
+ if (got !== chunk.sha256) {
116
+ throw new Error(`远端块校验失败: ${item.relPath} @${chunk.offset}`);
117
+ }
118
+ await pipeline(createReadStream(tmpChunk), createWriteStream(tmp, { flags: 'a' }));
119
+ downloadedChunks++;
120
+ downloadedBytes += chunk.length;
121
+ }
122
+ finally {
123
+ await unlink(tmpChunk).catch(() => { });
124
+ }
125
+ }
126
+ const finalSize = (await stat(tmp)).size;
127
+ if (finalSize !== item.size) {
128
+ throw new Error(`拉取后大小不一致: ${item.relPath} 期望 ${item.size} 实得 ${finalSize}`);
129
+ }
130
+ await rename(tmp, target);
131
+ }
132
+ catch (err) {
133
+ await unlink(tmp).catch(() => { });
134
+ throw err;
135
+ }
136
+ pulled[stateKey] = {
137
+ generation: item.generation,
138
+ size: item.size,
139
+ chunksHash: item.chunksHash,
140
+ };
141
+ downloadedItems++;
142
+ progress.item(`${dev}/${item.relPath}`, itemNo, entries.length, item.size);
143
+ }
144
+ }
145
+ return { downloadedChunks, downloadedBytes, downloadedItems, skippedItems };
146
+ }
147
+ //# sourceMappingURL=pull.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pull.js","sourceRoot":"","sources":["../../src/core/pull.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import type { State } from './state.js';
2
+ import { type Progress } from '../progress.js';
3
+ import type { RemoteStore } from '../remote/store.js';
4
+ export interface PushOptions {
5
+ remote: RemoteStore;
6
+ deviceId: string;
7
+ vaultRoot: string;
8
+ state: State;
9
+ /** 当前远端的身份指纹;用于判断本地推送记录是否还作数(换后端时作废)。 */
10
+ remoteId?: string;
11
+ now?: () => number;
12
+ chunkSize?: number;
13
+ concurrency?: number;
14
+ progress?: Progress;
15
+ signal?: AbortSignal;
16
+ }
17
+ export interface PushResult {
18
+ uploadedChunks: number;
19
+ uploadedBytes: number;
20
+ uploadedItems: number;
21
+ skippedItems: number;
22
+ }
23
+ /**
24
+ * 把本地 vault 中本设备的归档增量推到远端(块级)。
25
+ *
26
+ * 远端只有"块 + 单份索引":块先传,索引最后写(提交点)。
27
+ * 重复运行只补缺失的块;世代变化时旧世代块保留(永不删除)。
28
+ */
29
+ export declare function pushLocal(opts: PushOptions): Promise<PushResult>;
@@ -0,0 +1,170 @@
1
+ import { stat } from 'node:fs/promises';
2
+ import { CHUNK_SIZE, chunkRanges, hashFile, hashRange, reusableChunks } from './chunker.js';
3
+ import { mapLimit } from './concurrency.js';
4
+ import { throwIfAborted } from './collect.js';
5
+ import { chunkRemotePath, chunksHash, encodeKey, indexPath, itemKey, objectRemotePath, validateRelPath, } from './remote-layout.js';
6
+ import { silentProgress } from '../progress.js';
7
+ import { rawFilePath } from './layout.js';
8
+ /** 索引检查点间隔(ms):长同步被打断最多损失一分钟的续传记录。 */
9
+ const INDEX_CHECKPOINT_MS = 60_000;
10
+ /**
11
+ * 把本地 vault 中本设备的归档增量推到远端(块级)。
12
+ *
13
+ * 远端只有"块 + 单份索引":块先传,索引最后写(提交点)。
14
+ * 重复运行只补缺失的块;世代变化时旧世代块保留(永不删除)。
15
+ */
16
+ export async function pushLocal(opts) {
17
+ const { remote, deviceId, vaultRoot, state } = opts;
18
+ const now = opts.now ?? Date.now;
19
+ const chunkSize = opts.chunkSize ?? CHUNK_SIZE;
20
+ // 并发数由后端给建议:WebDAV 实测并发 PUT 会 ECONNRESET(给 1),
21
+ // 对象存储允许并发(给更高值),这里显式传参时以传参为准。
22
+ const concurrency = opts.concurrency ?? remote.concurrencyHint?.() ?? 1;
23
+ // 对象存储一个文件一个对象(可读);WebDAV 保持应用层分块(服务端无 multipart)
24
+ const layout = remote.layoutHint?.() ?? 'chunk';
25
+ const progress = opts.progress ?? silentProgress;
26
+ const previous = (await remote.readJson(indexPath(deviceId)));
27
+ const index = {
28
+ v: 1,
29
+ deviceId,
30
+ updatedAt: previous?.updatedAt ?? 0,
31
+ items: { ...(previous?.items ?? {}) },
32
+ };
33
+ const tasks = [];
34
+ for (const [harness, bucket] of Object.entries(state.sources)) {
35
+ for (const [relPath, entry] of Object.entries(bucket)) {
36
+ if (!validateRelPath(relPath))
37
+ throw new Error(`非法路径(源状态): ${relPath}`);
38
+ const absPath = rawFilePath(vaultRoot, deviceId, harness, relPath);
39
+ try {
40
+ if (!(await stat(absPath)).isFile())
41
+ continue;
42
+ }
43
+ catch (err) {
44
+ if (err.code === 'ENOENT')
45
+ continue;
46
+ throw err;
47
+ }
48
+ tasks.push({ harness, relPath, absPath, entry });
49
+ }
50
+ }
51
+ progress.phase('push', `${tasks.length} 个文件`);
52
+ let uploadedChunks = 0;
53
+ let uploadedBytes = 0;
54
+ let uploadedItems = 0;
55
+ let skippedItems = 0;
56
+ let indexChanged = false;
57
+ let lastCheckpoint = Date.now();
58
+ const commitIndex = async () => {
59
+ index.updatedAt = now();
60
+ await remote.writeJson(indexPath(deviceId), index);
61
+ indexChanged = false;
62
+ lastCheckpoint = Date.now();
63
+ };
64
+ for (let i = 0; i < tasks.length; i++) {
65
+ throwIfAborted(opts.signal);
66
+ const { harness, relPath, absPath, entry } = tasks[i];
67
+ const key = encodeKey(relPath);
68
+ const ik = itemKey(harness, key);
69
+ const st = await stat(absPath);
70
+ const pushed = state.pushed[harness]?.[relPath];
71
+ const inIndex = index.items[ik];
72
+ // 索引是远端权威:世代 + 大小 + 布局一致即内容一致(append-only,改写会换世代)。
73
+ // 即使本地 state 丢失,也能零重传地续上。布局变化(如 S3 改单对象)会触发重推。
74
+ if (inIndex &&
75
+ inIndex.generation === entry.generation &&
76
+ inIndex.size === st.size &&
77
+ (inIndex.layout ?? 'chunk') === layout) {
78
+ (state.pushed[harness] ??= {})[relPath] ??= {
79
+ generation: inIndex.generation,
80
+ size: inIndex.size,
81
+ chunks: inIndex.chunks,
82
+ chunksHash: inIndex.chunksHash,
83
+ };
84
+ skippedItems++;
85
+ progress.item(relPath, i + 1, tasks.length, 0);
86
+ continue;
87
+ }
88
+ const chunks = [];
89
+ if (layout === 'object') {
90
+ // 整文件一个对象;分片交给 S3 Multipart,块清单退化为单块
91
+ if (st.size > 0) {
92
+ chunks.push({ offset: 0, length: st.size, sha256: await hashFile(absPath) });
93
+ }
94
+ await remote.writeFile(objectRemotePath(deviceId, harness, relPath), absPath);
95
+ uploadedChunks += chunks.length;
96
+ uploadedBytes += st.size;
97
+ }
98
+ else {
99
+ const ranges = chunkRanges(st.size, chunkSize);
100
+ // 可复用块的来源:
101
+ // - 远端索引:远端自己说有这些块(最权威),且布局必须是 chunk
102
+ // - 本地 state.pushed:只在"同一个远端"时可信(索引丢了但块还在,只补半块)
103
+ // 换后端后本地记录属于旧远端,不能复用 —— 否则新远端只剩索引没有数据。
104
+ const sameRemote = !opts.remoteId || !state.remoteId || opts.remoteId === state.remoteId;
105
+ const reuse = inIndex &&
106
+ inIndex.generation === entry.generation &&
107
+ (inIndex.layout ?? 'chunk') === 'chunk'
108
+ ? reusableChunks(inIndex.chunks, inIndex.size, chunkSize)
109
+ : pushed && sameRemote && pushed.generation === entry.generation
110
+ ? reusableChunks(pushed.chunks, pushed.size, chunkSize)
111
+ : new Map();
112
+ const toUpload = [];
113
+ for (const range of ranges) {
114
+ const old = reuse.get(range.offset);
115
+ if (old) {
116
+ chunks.push(old);
117
+ continue;
118
+ }
119
+ const chunk = {
120
+ ...range,
121
+ sha256: await hashRange(absPath, range.offset, range.length),
122
+ };
123
+ chunks.push(chunk);
124
+ toUpload.push({
125
+ chunk,
126
+ remotePath: chunkRemotePath(deviceId, harness, key, entry.generation, chunk),
127
+ });
128
+ }
129
+ await mapLimit(toUpload, concurrency, async ({ chunk, remotePath }) => {
130
+ await remote.writeFile(remotePath, absPath, {
131
+ offset: chunk.offset,
132
+ length: chunk.length,
133
+ });
134
+ uploadedChunks++;
135
+ uploadedBytes += chunk.length;
136
+ });
137
+ }
138
+ const hash = chunksHash(chunks);
139
+ (state.pushed[harness] ??= {})[relPath] = {
140
+ generation: entry.generation,
141
+ size: st.size,
142
+ chunks,
143
+ chunksHash: hash,
144
+ };
145
+ index.items[ik] = {
146
+ harness,
147
+ key,
148
+ relPath,
149
+ sessionKey: entry.sessionKey,
150
+ generation: entry.generation,
151
+ size: st.size,
152
+ chunks,
153
+ chunksHash: hash,
154
+ layout,
155
+ updatedAt: now(),
156
+ };
157
+ indexChanged = true;
158
+ uploadedItems++;
159
+ progress.item(relPath, i + 1, tasks.length, st.size);
160
+ if (Date.now() - lastCheckpoint >= INDEX_CHECKPOINT_MS)
161
+ await commitIndex();
162
+ }
163
+ if (indexChanged)
164
+ await commitIndex();
165
+ // 记住这次推送属于哪个远端:下次换后端时,本地的复用记录会自动作废
166
+ if (opts.remoteId)
167
+ state.remoteId = opts.remoteId;
168
+ return { uploadedChunks, uploadedBytes, uploadedItems, skippedItems };
169
+ }
170
+ //# sourceMappingURL=push.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"push.js","sourceRoot":"","sources":["../../src/core/push.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,67 @@
1
+ import type { ChunkRef } from './chunker.js';
2
+ /**
3
+ * 远端布局(每设备自描述,对象数尽量少):
4
+ *
5
+ * <device>/index.json 设备索引(清单 + 块哈希都在这里)
6
+ * <device>/meta.json 设备名 / lastSeen
7
+ * <device>/chunks/<harness>/<key>.<gen>.<off>-<len>.bin 不可变块(WebDAV)
8
+ * <device>/files/<harness>/<relPath> 整文件对象(S3,可读)
9
+ *
10
+ * WebDAV 没有原生分片上传,按块存;对象存储一个文件一个对象,路径即原相对路径。
11
+ * 两种布局由索引项的 layout 字段区分,缺省 'chunk'(兼容老索引)。
12
+ *
13
+ * 每个 harness 只有一个目录,避免"每会话多级 MKCOL"的数千次往返;
14
+ * 索引是单次提交点,块先传、索引后写。
15
+ * key = base64url(relPath),避免中文、空格、'..' 进入远端路径。
16
+ */
17
+ export interface IndexItem {
18
+ harness: string;
19
+ key: string;
20
+ relPath: string;
21
+ sessionKey: string;
22
+ generation: string;
23
+ size: number;
24
+ chunks: ChunkRef[];
25
+ /** chunks 数组的 sha256,拉取时判断是否需要取索引里的块列表 */
26
+ chunksHash: string;
27
+ /** 缺省 'chunk':老索引没有这个字段,按分块布局解释。 */
28
+ layout?: 'chunk' | 'object';
29
+ updatedAt: number;
30
+ }
31
+ export interface DeviceIndex {
32
+ v: 1;
33
+ deviceId: string;
34
+ updatedAt: number;
35
+ items: Record<string, IndexItem>;
36
+ }
37
+ export interface DeviceMeta {
38
+ id: string;
39
+ name: string;
40
+ lastSeen: number;
41
+ }
42
+ export declare function chunksHash(chunks: readonly ChunkRef[]): string;
43
+ export declare function encodeKey(relPath: string): string;
44
+ /** 解码并校验远端不可信输入,拒绝绝对路径/穿越/反斜杠/NUL。 */
45
+ export declare function decodeKey(key: string): string;
46
+ export declare function validateRelPath(relPath: string): boolean;
47
+ export declare function itemKey(harness: string, key: string): string;
48
+ /** 设备在远端的根目录:raw/<device>;library 是另一棵独立的树。 */
49
+ export declare function deviceRemoteRoot(deviceId: string): string;
50
+ export declare function indexPath(deviceId: string): string;
51
+ export declare function metaPath(deviceId: string): string;
52
+ /**
53
+ * 扁平块路径:同一 harness 共用一个目录,文件名自带 key/世代/区间。
54
+ * 超长 key(深层项目路径)改用 key+generation 的哈希前缀,保证文件名 <= 240。
55
+ */
56
+ export declare function chunkRemotePath(deviceId: string, harness: string, key: string, generation: string, chunk: {
57
+ offset: number;
58
+ length: number;
59
+ }): string;
60
+ /**
61
+ * 单对象布局:与本地 vault 同构,控制台里能直接浏览/下载会话原文。
62
+ * 路径即 relPath,同路径覆盖写(不加世代后缀)。
63
+ * 超长路径超过 240 字符时整体换成 relPath 的哈希,保证 S3 key 不超限。
64
+ */
65
+ export declare function objectRemotePath(deviceId: string, harness: string, relPath: string): string;
66
+ /** 校验索引项(远端不可信):块必须从 0 连续铺满整个文件。 */
67
+ export declare function validateIndexItem(item: IndexItem): void;
@@ -0,0 +1,94 @@
1
+ import { createHash } from 'node:crypto';
2
+ export function chunksHash(chunks) {
3
+ return createHash('sha256').update(JSON.stringify(chunks)).digest('hex');
4
+ }
5
+ export function encodeKey(relPath) {
6
+ return Buffer.from(relPath, 'utf8').toString('base64url');
7
+ }
8
+ /** 解码并校验远端不可信输入,拒绝绝对路径/穿越/反斜杠/NUL。 */
9
+ export function decodeKey(key) {
10
+ let rel;
11
+ try {
12
+ rel = Buffer.from(key, 'base64url').toString('utf8');
13
+ }
14
+ catch {
15
+ throw new Error(`非法 path key: ${key}`);
16
+ }
17
+ if (!validateRelPath(rel))
18
+ throw new Error(`非法路径(远端不可信): ${rel}`);
19
+ return rel;
20
+ }
21
+ export function validateRelPath(relPath) {
22
+ if (!relPath || relPath.startsWith('/') || relPath.includes('\\') || relPath.includes('\0')) {
23
+ return false;
24
+ }
25
+ const parts = relPath.split('/');
26
+ return parts.every((p) => p !== '' && p !== '.' && p !== '..');
27
+ }
28
+ export function itemKey(harness, key) {
29
+ return `${harness}:${key}`;
30
+ }
31
+ /** 设备在远端的根目录:raw/<device>;library 是另一棵独立的树。 */
32
+ export function deviceRemoteRoot(deviceId) {
33
+ return `raw/${deviceId}`;
34
+ }
35
+ export function indexPath(deviceId) {
36
+ return `${deviceRemoteRoot(deviceId)}/index.json`;
37
+ }
38
+ export function metaPath(deviceId) {
39
+ return `${deviceRemoteRoot(deviceId)}/meta.json`;
40
+ }
41
+ /**
42
+ * 扁平块路径:同一 harness 共用一个目录,文件名自带 key/世代/区间。
43
+ * 超长 key(深层项目路径)改用 key+generation 的哈希前缀,保证文件名 <= 240。
44
+ */
45
+ export function chunkRemotePath(deviceId, harness, key, generation, chunk) {
46
+ const suffix = `${String(chunk.offset).padStart(12, '0')}-${chunk.length}.bin`;
47
+ const plain = `${key}.${generation}.${suffix}`;
48
+ const name = plain.length <= 240
49
+ ? plain
50
+ : `${createHash('sha256').update(`${key}:${generation}`).digest('hex').slice(0, 32)}.${suffix}`;
51
+ return `${deviceRemoteRoot(deviceId)}/chunks/${harness}/${name}`;
52
+ }
53
+ /**
54
+ * 单对象布局:与本地 vault 同构,控制台里能直接浏览/下载会话原文。
55
+ * 路径即 relPath,同路径覆盖写(不加世代后缀)。
56
+ * 超长路径超过 240 字符时整体换成 relPath 的哈希,保证 S3 key 不超限。
57
+ */
58
+ export function objectRemotePath(deviceId, harness, relPath) {
59
+ const plain = `${deviceRemoteRoot(deviceId)}/files/${harness}/${relPath}`;
60
+ const name = plain.length <= 240
61
+ ? relPath
62
+ : createHash('sha256').update(relPath).digest('hex').slice(0, 32);
63
+ return `${deviceRemoteRoot(deviceId)}/files/${harness}/${name}`;
64
+ }
65
+ /** 校验索引项(远端不可信):块必须从 0 连续铺满整个文件。 */
66
+ export function validateIndexItem(item) {
67
+ if (!validateRelPath(item.relPath))
68
+ throw new Error(`非法路径(远端不可信): ${item.relPath}`);
69
+ if (decodeKey(item.key) !== item.relPath)
70
+ throw new Error(`远端索引路径不一致: ${item.relPath}`);
71
+ if (!item.generation || item.generation.includes('/') || item.generation.includes('.')) {
72
+ throw new Error(`远端世代名非法: ${item.generation}`);
73
+ }
74
+ if (item.layout !== undefined && item.layout !== 'chunk' && item.layout !== 'object') {
75
+ throw new Error(`远端布局非法: ${item.relPath}`);
76
+ }
77
+ let offset = 0;
78
+ for (const c of item.chunks) {
79
+ if (c.offset !== offset || c.length <= 0 || !/^[0-9a-f]{64}$/.test(c.sha256)) {
80
+ throw new Error(`远端块布局非法: ${item.relPath}`);
81
+ }
82
+ offset += c.length;
83
+ }
84
+ if (offset !== item.size)
85
+ throw new Error(`远端块未铺满文件: ${item.relPath}`);
86
+ // 单对象布局必须是一整块;空文件沿用零块(远端可能没有对象)
87
+ if (item.layout === 'object' && item.size > 0 && item.chunks.length !== 1) {
88
+ throw new Error(`远端单对象布局非法: ${item.relPath}`);
89
+ }
90
+ if (chunksHash(item.chunks) !== item.chunksHash) {
91
+ throw new Error(`远端块哈希不一致: ${item.relPath}`);
92
+ }
93
+ }
94
+ //# sourceMappingURL=remote-layout.js.map