trove-cli 0.0.0-stage → 0.2.0

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 +200 -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 +972 -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,438 @@
1
+ import { open, readFile, stat, writeFile } from 'node:fs/promises';
2
+ import { request as httpRequest, Agent as HttpAgent } from 'node:http';
3
+ import { request as httpsRequest, Agent as HttpsAgent } from 'node:https';
4
+ import { mapLimit } from '../core/concurrency.js';
5
+ import { canonicalQuery, canonicalUri, signRequest } from './s3-sigv4.js';
6
+ const REQUEST_TIMEOUT_MS = 120_000;
7
+ const MAX_ATTEMPTS = 3;
8
+ /**
9
+ * 并发上传块数。OSS 官方性能最佳实践建议高吞吐时用多连接并发
10
+ * (大批量小文件场景建议 -j 100~500),这里取一个保守起点。
11
+ */
12
+ const DEFAULT_CONCURRENCY = 8;
13
+ /** 超过这个大小走 Multipart Upload;以下单 PUT 更省请求。 */
14
+ const MULTIPART_THRESHOLD = 16 * 1024 * 1024;
15
+ /** Multipart 分片大小:满足 S3 对非末尾分片的 5 MiB 下限。 */
16
+ const PART_SIZE = 8 * 1024 * 1024;
17
+ /** 下载分段大小:避免整个对象进内存。 */
18
+ const READ_PART_SIZE = 8 * 1024 * 1024;
19
+ function strip(p) {
20
+ return p.replace(/^\/+|\/+$/g, '');
21
+ }
22
+ /** S3 key 不能以 / 开头,也不能含空段。 */
23
+ function toKey(path) {
24
+ return strip(path);
25
+ }
26
+ function sleep(ms) {
27
+ return new Promise((r) => setTimeout(r, ms));
28
+ }
29
+ function statusOf(err) {
30
+ const e = err;
31
+ return e?.status ?? e?.statusCode ?? e?.response?.status;
32
+ }
33
+ /** 5xx 与 429 视为可重试;4xx 是配置或权限问题,立即失败。 */
34
+ function retryable(err) {
35
+ const status = statusOf(err);
36
+ if (status !== undefined)
37
+ return status >= 500 || status === 429;
38
+ const code = err?.code;
39
+ return ['ECONNRESET', 'ECONNREFUSED', 'ETIMEDOUT', 'EPIPE', 'EAI_AGAIN'].includes(code ?? '');
40
+ }
41
+ export class S3Error extends Error {
42
+ status;
43
+ code;
44
+ constructor(message, status, code) {
45
+ super(message);
46
+ this.status = status;
47
+ this.code = code;
48
+ this.name = 'S3Error';
49
+ }
50
+ }
51
+ /** 从 S3 的 XML 错误体里取出 <Code> 与 <Message>。 */
52
+ function parseErrorBody(body) {
53
+ const code = /<Code>([^<]*)<\/Code>/.exec(body)?.[1];
54
+ const message = /<Message>([^<]*)<\/Message>/.exec(body)?.[1];
55
+ return { code, message };
56
+ }
57
+ /**
58
+ * S3 存储实现:对象存储天然是扁平的 key,目录语义靠 ListObjectsV2 的 delimiter 模拟。
59
+ *
60
+ * 与 WebDAV 实现的差异:
61
+ * - 目录不用创建(PutObject 自动建"目录")
62
+ * - 索引写入直接覆盖同 key,天然原子(S3 的 PutObject 对同 key 是原子的)
63
+ * - 允许并发:WebDAV 实测并发 PUT 会 ECONNRESET,对象存储不会
64
+ */
65
+ export class S3Store {
66
+ endpoint;
67
+ region;
68
+ bucket;
69
+ accessKeyId;
70
+ secretAccessKey;
71
+ forcePathStyle;
72
+ timeoutMs;
73
+ maxSockets;
74
+ concurrency;
75
+ /** keep-alive 连接池:对象存储允许并发,默认 8 条。 */
76
+ agents;
77
+ constructor(opts) {
78
+ this.endpoint = opts.endpoint.replace(/\/+$/, '');
79
+ this.region = opts.region;
80
+ this.bucket = opts.bucket;
81
+ this.accessKeyId = opts.accessKeyId;
82
+ this.secretAccessKey = opts.secretAccessKey;
83
+ this.forcePathStyle = opts.forcePathStyle ?? false;
84
+ this.timeoutMs = opts.timeoutMs ?? REQUEST_TIMEOUT_MS;
85
+ this.maxSockets = opts.maxSockets ?? DEFAULT_CONCURRENCY;
86
+ this.concurrency = opts.concurrency ?? DEFAULT_CONCURRENCY;
87
+ this.agents = {
88
+ http: new HttpAgent({ keepAlive: true, maxSockets: this.maxSockets }),
89
+ https: new HttpsAgent({ keepAlive: true, maxSockets: this.maxSockets }),
90
+ };
91
+ }
92
+ /** virtual-hosted:bucket 放主机名;path-style:bucket 放路径。 */
93
+ hostAndPath(key) {
94
+ const u = new URL(this.endpoint);
95
+ if (this.forcePathStyle) {
96
+ return { host: u.host, path: `/${this.bucket}/${key}` };
97
+ }
98
+ return { host: `${this.bucket}.${u.host}`, path: `/${key}` };
99
+ }
100
+ async raw(method, key, opts = {}) {
101
+ const { host, path } = this.hostAndPath(key);
102
+ const url = new URL(this.endpoint);
103
+ url.host = host;
104
+ url.pathname = path;
105
+ for (const [k, v] of Object.entries(opts.query ?? {}))
106
+ url.searchParams.set(k, v);
107
+ const body = opts.body ?? Buffer.alloc(0);
108
+ const signed = signRequest({
109
+ method,
110
+ url: url.toString(),
111
+ pathname: path,
112
+ headers: { host, ...opts.headers },
113
+ body,
114
+ region: this.region,
115
+ service: 's3',
116
+ accessKeyId: this.accessKeyId,
117
+ secretAccessKey: this.secretAccessKey,
118
+ date: new Date(),
119
+ });
120
+ const isHttps = url.protocol === 'https:';
121
+ const reqFn = isHttps ? httpsRequest : httpRequest;
122
+ // 发送路径必须与签名用的 canonical URI 完全一致。
123
+ // WHATWG URL 会保留 ( ) ! * + 等字符,而 SigV4 要求按 RFC3986 编码,
124
+ // 混用会导致 SignatureDoesNotMatch(对象路径含这些字符时必现)。
125
+ const query = canonicalQuery(opts.query ?? {});
126
+ const sendPath = `${canonicalUri(path)}${query ? `?${query}` : ''}`;
127
+ return new Promise((resolve, reject) => {
128
+ const req = reqFn({
129
+ protocol: url.protocol,
130
+ hostname: url.hostname,
131
+ port: url.port || (isHttps ? 443 : 80),
132
+ method,
133
+ path: sendPath,
134
+ headers: { ...signed.headers, 'content-length': String(body.length) },
135
+ agent: isHttps ? this.agents.https : this.agents.http,
136
+ }, (res) => {
137
+ const chunks = [];
138
+ res.on('data', (c) => chunks.push(c));
139
+ res.on('end', () => {
140
+ resolve({
141
+ status: res.statusCode ?? 0,
142
+ headers: res.headers,
143
+ body: Buffer.concat(chunks),
144
+ });
145
+ });
146
+ res.on('error', reject);
147
+ });
148
+ req.setTimeout(this.timeoutMs, () => {
149
+ req.destroy(new Error(`S3 请求超时(${this.timeoutMs}ms): ${method} ${key}`));
150
+ });
151
+ req.on('error', reject);
152
+ if (body.length)
153
+ req.write(body);
154
+ req.end();
155
+ });
156
+ }
157
+ /** 带重试的请求;5xx/429 退避重试,4xx 立即抛。 */
158
+ async call(method, key, opts = {}) {
159
+ let lastErr;
160
+ for (let i = 0; i < MAX_ATTEMPTS; i++) {
161
+ try {
162
+ const res = await this.raw(method, key, opts);
163
+ if (res.status === 404) {
164
+ if (opts.allow404)
165
+ return null;
166
+ const { code, message } = parseErrorBody(res.body.toString('utf8'));
167
+ throw new S3Error(`对象不存在: ${key}${message ? ` (${message})` : ''}`, 404, code);
168
+ }
169
+ if (res.status >= 400) {
170
+ const { code, message } = parseErrorBody(res.body.toString('utf8'));
171
+ const err = new S3Error(`S3 ${method} 失败 (${res.status})${code ? ` ${code}` : ''}${message ? `: ${message}` : ''}`, res.status, code);
172
+ if (!retryable(err) || i === MAX_ATTEMPTS - 1)
173
+ throw err;
174
+ lastErr = err;
175
+ }
176
+ else {
177
+ return res;
178
+ }
179
+ }
180
+ catch (err) {
181
+ lastErr = err;
182
+ if (!retryable(err) || i === MAX_ATTEMPTS - 1)
183
+ throw err;
184
+ }
185
+ await sleep(500 * 2 ** i * (0.7 + Math.random() * 0.6));
186
+ }
187
+ throw lastErr;
188
+ }
189
+ /** 列一层:prefix 下的直接子项;delimiter='/' 时把子目录折叠成目录项,空串表示递归。 */
190
+ async listPage(prefix, delimiter = '/') {
191
+ const out = [];
192
+ let token;
193
+ do {
194
+ const query = {
195
+ 'list-type': '2',
196
+ prefix,
197
+ };
198
+ if (delimiter)
199
+ query['delimiter'] = delimiter;
200
+ if (token)
201
+ query['continuation-token'] = token;
202
+ const res = await this.call('GET', '', { query });
203
+ const xml = res.body.toString('utf8');
204
+ const contentRe = /<Contents>([\s\S]*?)<\/Contents>/g;
205
+ let m;
206
+ while ((m = contentRe.exec(xml))) {
207
+ const key = /<Key>([\s\S]*?)<\/Key>/.exec(m[1])?.[1];
208
+ if (!key)
209
+ continue;
210
+ const size = Number(/<Size>(\d+)<\/Size>/.exec(m[1])?.[1] ?? '0');
211
+ const modified = /<LastModified>([\s\S]*?)<\/LastModified>/.exec(m[1])?.[1];
212
+ const mtime = modified ? Date.parse(modified) : NaN;
213
+ out.push({ key, size, isDir: false, ...(Number.isFinite(mtime) ? { mtime } : {}) });
214
+ }
215
+ const prefixRe = /<CommonPrefixes>([\s\S]*?)<\/CommonPrefixes>/g;
216
+ while ((m = prefixRe.exec(xml))) {
217
+ const p = /<Prefix>([\s\S]*?)<\/Prefix>/.exec(m[1])?.[1];
218
+ if (!p)
219
+ continue;
220
+ out.push({ key: p, size: 0, isDir: true });
221
+ }
222
+ const truncated = /<IsTruncated>true<\/IsTruncated>/.test(xml);
223
+ token = truncated
224
+ ? /<NextContinuationToken>([\s\S]*?)<\/NextContinuationToken>/.exec(xml)?.[1]
225
+ : undefined;
226
+ } while (token);
227
+ return out;
228
+ }
229
+ async list(path, signal) {
230
+ if (signal?.aborted)
231
+ return [];
232
+ const prefix = strip(path);
233
+ const fullPrefix = prefix ? `${prefix}/` : '';
234
+ const items = await this.listPage(fullPrefix);
235
+ const out = [];
236
+ for (const it of items) {
237
+ const name = it.isDir
238
+ ? strip(it.key).split('/').pop() ?? ''
239
+ : it.key.slice(fullPrefix.length);
240
+ if (!name)
241
+ continue;
242
+ out.push({
243
+ name,
244
+ type: it.isDir ? 'directory' : 'file',
245
+ size: it.size,
246
+ ...(it.mtime !== undefined ? { mtime: it.mtime } : {}),
247
+ });
248
+ }
249
+ return out;
250
+ }
251
+ /** 递归列前缀下所有文件(不带 delimiter,一次分页拉完)。 */
252
+ async listAll(prefix, signal) {
253
+ if (signal?.aborted)
254
+ return [];
255
+ const items = await this.listPage(strip(prefix), '');
256
+ return items
257
+ .filter((it) => !it.isDir)
258
+ .map((it) => ({
259
+ name: it.key,
260
+ type: 'file',
261
+ size: it.size,
262
+ ...(it.mtime !== undefined ? { mtime: it.mtime } : {}),
263
+ }));
264
+ }
265
+ async listDevices() {
266
+ const items = await this.listPage('raw/');
267
+ const out = new Set();
268
+ for (const it of items) {
269
+ if (!it.isDir)
270
+ continue;
271
+ const name = strip(it.key).split('/').pop();
272
+ if (name && name !== '.')
273
+ out.add(name);
274
+ }
275
+ return [...out].sort();
276
+ }
277
+ async readJson(path, signal) {
278
+ if (signal?.aborted)
279
+ return null;
280
+ const res = await this.call('GET', toKey(path), { allow404: true });
281
+ if (!res)
282
+ return null;
283
+ const text = res.body.toString('utf8');
284
+ if (!text.trim())
285
+ return null;
286
+ try {
287
+ return JSON.parse(text);
288
+ }
289
+ catch {
290
+ // 远端索引写一半/被破坏:忽略它,让 push 从本地状态重建索引
291
+ console.warn(`[trove] 远端 JSON 损坏,已忽略: ${path}`);
292
+ return null;
293
+ }
294
+ }
295
+ async writeJson(path, value) {
296
+ const body = Buffer.from(JSON.stringify(value), 'utf8');
297
+ // S3 的 PutObject 对同 key 是原子覆盖,天然满足"索引是唯一提交点"
298
+ await this.call('PUT', toKey(path), { body });
299
+ }
300
+ async writeFile(path, localPath, range) {
301
+ const key = toKey(path);
302
+ // 区间上传是应用层分块(旧 chunk 布局),按区间读后单独 PUT
303
+ if (range) {
304
+ const body = await this.readRange(localPath, range);
305
+ await this.call('PUT', key, { body });
306
+ return;
307
+ }
308
+ const size = (await stat(localPath)).size;
309
+ if (size > MULTIPART_THRESHOLD) {
310
+ await this.multipart(key, localPath, size);
311
+ return;
312
+ }
313
+ // 读整个文件到内存再发:控制在 16MiB 以内
314
+ await this.call('PUT', key, { body: await readFile(localPath) });
315
+ }
316
+ /** 逐段落读本地文件,避免整个大文件进内存。 */
317
+ async readPart(fh, offset, length) {
318
+ const buf = Buffer.allocUnsafe(length);
319
+ let read = 0;
320
+ while (read < length) {
321
+ const { bytesRead } = await fh.read(buf, read, length - read, offset + read);
322
+ if (bytesRead === 0)
323
+ break;
324
+ read += bytesRead;
325
+ }
326
+ return buf.subarray(0, read);
327
+ }
328
+ /**
329
+ * S3 Multipart Upload:分片只在协议层,最终仍是一个对象。
330
+ * 失败时尽力 Abort,避免桶里留下未完成的分片。
331
+ */
332
+ async multipart(key, localPath, size) {
333
+ const created = await this.call('POST', key, { query: { uploads: '' } });
334
+ const uploadId = /<UploadId>([\s\S]*?)<\/UploadId>/.exec(created.body.toString('utf8'))?.[1];
335
+ if (!uploadId)
336
+ throw new S3Error(`创建分片上传失败(缺少 UploadId): ${key}`);
337
+ const fh = await open(localPath, 'r');
338
+ try {
339
+ // shortcut: 8 MiB × 10000 片 = 78 GiB 上限,会话文件远小于此;真超了再动态放大分片
340
+ const count = Math.ceil(size / PART_SIZE);
341
+ const parts = await mapLimit(Array.from({ length: count }, (_, i) => i + 1), this.concurrency, async (partNumber) => {
342
+ const offset = (partNumber - 1) * PART_SIZE;
343
+ const length = Math.min(PART_SIZE, size - offset);
344
+ const body = await this.readPart(fh, offset, length);
345
+ const res = await this.call('PUT', key, {
346
+ query: { partNumber: String(partNumber), uploadId },
347
+ body,
348
+ });
349
+ const raw = res.headers.etag;
350
+ const etag = Array.isArray(raw) ? raw[0] : raw;
351
+ if (!etag)
352
+ throw new S3Error(`分片 ${partNumber} 缺少 ETag: ${key}`);
353
+ return { partNumber, etag };
354
+ });
355
+ parts.sort((a, b) => a.partNumber - b.partNumber);
356
+ const xml = [
357
+ '<CompleteMultipartUpload>',
358
+ ...parts.map((p) => `<Part><PartNumber>${p.partNumber}</PartNumber><ETag>${p.etag}</ETag></Part>`),
359
+ '</CompleteMultipartUpload>',
360
+ ].join('');
361
+ const res = await this.call('POST', key, {
362
+ query: { uploadId },
363
+ body: Buffer.from(xml, 'utf8'),
364
+ headers: { 'content-type': 'application/xml' },
365
+ });
366
+ // COS 的 Complete 可能返回 200 但响应体内嵌 <Error>
367
+ const text = res.body.toString('utf8');
368
+ if (/<Error>/.test(text))
369
+ throw new S3Error(`分片上传合并失败: ${text.slice(0, 300)}`);
370
+ }
371
+ catch (err) {
372
+ await this.call('DELETE', key, { query: { uploadId }, allow404: true }).catch(() => { });
373
+ throw err;
374
+ }
375
+ finally {
376
+ await fh.close();
377
+ }
378
+ }
379
+ async readRange(localPath, range) {
380
+ if (!range) {
381
+ return readFile(localPath);
382
+ }
383
+ const fh = await open(localPath, 'r');
384
+ try {
385
+ return await this.readPart(fh, range.offset, range.length);
386
+ }
387
+ finally {
388
+ await fh.close();
389
+ }
390
+ }
391
+ async readFile(path, localPath) {
392
+ const key = toKey(path);
393
+ // HEAD 拿长度后分段 GET:单对象布局下文件可能很大,不能整段进内存
394
+ const size = await this.call('HEAD', key, { allow404: true })
395
+ .then((res) => {
396
+ const raw = res?.headers['content-length'];
397
+ const n = Number(Array.isArray(raw) ? raw[0] : raw);
398
+ return Number.isFinite(n) ? n : null;
399
+ })
400
+ .catch(() => null);
401
+ if (size === null) {
402
+ const res = await this.call('GET', key);
403
+ await writeFile(localPath, res.body);
404
+ return;
405
+ }
406
+ const fh = await open(localPath, 'w');
407
+ try {
408
+ for (let offset = 0; offset < size; offset += READ_PART_SIZE) {
409
+ const end = Math.min(offset + READ_PART_SIZE, size) - 1;
410
+ const res = await this.call('GET', key, {
411
+ headers: { range: `bytes=${offset}-${end}` },
412
+ });
413
+ // 服务端忽略 Range 会回 200 全量:直接整份落盘,不能按偏移写
414
+ if (res.status !== 206) {
415
+ await fh.write(res.body, 0, res.body.length, 0);
416
+ return;
417
+ }
418
+ await fh.write(res.body, 0, res.body.length, offset);
419
+ }
420
+ }
421
+ finally {
422
+ await fh.close();
423
+ }
424
+ }
425
+ async delete(path) {
426
+ // S3 删除不存在的对象也返回 204,不需要处理 404
427
+ await this.call('DELETE', toKey(path), { allow404: true });
428
+ }
429
+ /** 对象存储允许并发上传;OSS 官方建议大批量小文件用较高并发。 */
430
+ concurrencyHint() {
431
+ return this.concurrency;
432
+ }
433
+ /** 对象存储:一个文件一个对象,控制台可直接浏览。 */
434
+ layoutHint() {
435
+ return 'object';
436
+ }
437
+ }
438
+ //# sourceMappingURL=s3-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"s3-store.js","sourceRoot":"","sources":["../../src/remote/s3-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * S3 厂商预设。
3
+ *
4
+ * 两家都提供 AWS S3 兼容 API,差别只在 endpoint 形态与寻址方式:
5
+ * - 阿里云 OSS:官方文档要求 addressing_style=virtual、pathStyleAccessEnabled(false)、
6
+ * chunkedEncodingEnabled(false);走 path-style 会 403。
7
+ * - 腾讯云 COS:兼容 S3,桶名形如 <name>-<appid>。
8
+ */
9
+ export type S3VendorId = 'aliyun' | 'tencent';
10
+ export interface S3Vendor {
11
+ id: S3VendorId;
12
+ label: string;
13
+ /** 交互引导里的提示文案。 */
14
+ hint: string;
15
+ defaultRegion: string;
16
+ /** 该厂商的 S3 兼容 endpoint 形态。 */
17
+ endpoint: (region: string) => string;
18
+ /** 阿里云 OSS 默认 virtual-hosted;path-style 会被拒。 */
19
+ forcePathStyle: boolean;
20
+ }
21
+ export declare const S3_VENDORS: Record<S3VendorId, S3Vendor>;
22
+ export declare function vendorById(id: string): S3Vendor;
23
+ export interface EndpointSpec {
24
+ provider?: S3VendorId;
25
+ region?: string;
26
+ endpoint?: string;
27
+ }
28
+ /**
29
+ * 解析实际使用的 endpoint:手填优先,否则按厂商 + 地域推导。
30
+ */
31
+ export declare function resolveEndpoint(spec: EndpointSpec): string;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * S3 厂商预设。
3
+ *
4
+ * 两家都提供 AWS S3 兼容 API,差别只在 endpoint 形态与寻址方式:
5
+ * - 阿里云 OSS:官方文档要求 addressing_style=virtual、pathStyleAccessEnabled(false)、
6
+ * chunkedEncodingEnabled(false);走 path-style 会 403。
7
+ * - 腾讯云 COS:兼容 S3,桶名形如 <name>-<appid>。
8
+ */
9
+ export const S3_VENDORS = {
10
+ aliyun: {
11
+ id: 'aliyun',
12
+ label: '阿里云 OSS',
13
+ hint: '需要 AccessKey ID / Secret,以及 Bucket 名与地域(如 cn-hangzhou)。',
14
+ defaultRegion: 'cn-hangzhou',
15
+ endpoint: (region) => `https://s3.oss-${region}.aliyuncs.com`,
16
+ forcePathStyle: false,
17
+ },
18
+ tencent: {
19
+ id: 'tencent',
20
+ label: '腾讯云 COS',
21
+ hint: '需要 SecretId / SecretKey,以及完整 Bucket 名(含 -<appid> 后缀)与地域(如 ap-guangzhou)。',
22
+ defaultRegion: 'ap-guangzhou',
23
+ endpoint: (region) => `https://cos.${region}.myqcloud.com`,
24
+ forcePathStyle: false,
25
+ },
26
+ };
27
+ export function vendorById(id) {
28
+ const v = S3_VENDORS[id];
29
+ if (!v) {
30
+ throw new Error(`不支持的 S3 厂商: ${id}(目前支持 ${Object.keys(S3_VENDORS).join(' / ')})`);
31
+ }
32
+ return v;
33
+ }
34
+ /**
35
+ * 解析实际使用的 endpoint:手填优先,否则按厂商 + 地域推导。
36
+ */
37
+ export function resolveEndpoint(spec) {
38
+ if (spec.endpoint && spec.endpoint.trim()) {
39
+ return spec.endpoint.trim().replace(/\/+$/, '');
40
+ }
41
+ if (spec.provider) {
42
+ const v = vendorById(spec.provider);
43
+ return v.endpoint(spec.region || v.defaultRegion);
44
+ }
45
+ throw new Error('缺少 S3 endpoint:请指定厂商(provider)或直接填 endpoint。');
46
+ }
47
+ //# sourceMappingURL=s3-vendors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"s3-vendors.js","sourceRoot":"","sources":["../../src/remote/s3-vendors.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAgBH,MAAM,CAAC,MAAM,UAAU,GAAiC;IACtD,MAAM,EAAE;QACN,EAAE,EAAE,QAAQ;QACZ,KAAK,EAAE,SAAS;QAChB,IAAI,EAAE,yDAAyD;QAC/D,aAAa,EAAE,aAAa;QAC5B,QAAQ,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,kBAAkB,MAAM,eAAe;QAC7D,cAAc,EAAE,KAAK;KACtB;IACD,OAAO,EAAE;QACP,EAAE,EAAE,SAAS;QACb,KAAK,EAAE,SAAS;QAChB,IAAI,EAAE,0EAA0E;QAChF,aAAa,EAAE,cAAc;QAC7B,QAAQ,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,eAAe,MAAM,eAAe;QAC1D,cAAc,EAAE,KAAK;KACtB;CACF,CAAC;AAEF,MAAM,UAAU,UAAU,CAAC,EAAU;IACnC,MAAM,CAAC,GAAG,UAAU,CAAC,EAAgB,CAAC,CAAC;IACvC,IAAI,CAAC,CAAC,EAAE,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,eAAe,EAAE,SAAS,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACpF,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAQD;;GAEG;AACH,MAAM,UAAU,eAAe,CAAC,IAAkB;IAChD,IAAI,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC;QAC1C,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAClD,CAAC;IACD,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAClB,MAAM,CAAC,GAAG,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACpC,OAAO,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC,aAAa,CAAC,CAAC;IACpD,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;AAClE,CAAC"}
@@ -0,0 +1,44 @@
1
+ export interface RemoteEntry {
2
+ name: string;
3
+ type: 'file' | 'directory';
4
+ size: number;
5
+ /** 最后修改时间(ms);后端不提供时缺省,清理逻辑对缺失值一律不删。 */
6
+ mtime?: number;
7
+ }
8
+ /** 远端只要求 7 个原语;WebDAV/内存实现同一接口,编排逻辑可单测。 */
9
+ export interface RemoteStore {
10
+ /** 列出某个目录的直接子项(不递归);目录不存在返回空数组。signal 用于短超时探测。 */
11
+ list(path: string, signal?: AbortSignal): Promise<RemoteEntry[]>;
12
+ /**
13
+ * 递归列出前缀下所有文件,name 为完整路径。对象存储一次 ListObjectsV2 即可,
14
+ * 避免逐目录几千次往返。不实现时调用方回退为逐层 list。
15
+ */
16
+ listAll?(prefix: string, signal?: AbortSignal): Promise<RemoteEntry[]>;
17
+ /** 远端根下的设备目录名(无嵌套)。 */
18
+ listDevices(): Promise<string[]>;
19
+ /** 读取 JSON;不存在返回 null。 */
20
+ readJson(path: string, signal?: AbortSignal): Promise<unknown | null>;
21
+ /** 原子写入 JSON(实现负责覆盖)。 */
22
+ writeJson(path: string, value: unknown): Promise<void>;
23
+ /** 上传本地文件(实现自动建父目录);range 用于只传一个块。 */
24
+ writeFile(path: string, localPath: string, range?: {
25
+ offset: number;
26
+ length: number;
27
+ }): Promise<void>;
28
+ /** 下载远端文件到本地路径(字节一致)。 */
29
+ readFile(path: string, localPath: string): Promise<void>;
30
+ /** 删除单个文件;不存在时忽略。 */
31
+ delete(path: string): Promise<void>;
32
+ /**
33
+ * 该后端建议的并发上传数。
34
+ * WebDAV 实测并发 PUT 会 ECONNRESET,返回 1;对象存储允许并发,返回更高值。
35
+ * 不实现则按 1 处理。
36
+ */
37
+ concurrencyHint?(): number;
38
+ /**
39
+ * 该后端偏好的文件布局。
40
+ * object:一个文件一个对象(对象存储,控制台可读);chunk:应用层分块(WebDAV)。
41
+ * 不实现则按 chunk 处理。
42
+ */
43
+ layoutHint?(): 'chunk' | 'object';
44
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/remote/store.ts"],"names":[],"mappings":""}
@@ -0,0 +1,41 @@
1
+ import type { RemoteEntry, RemoteStore } from './store.js';
2
+ export interface WebDavOptions {
3
+ url: string;
4
+ username?: string;
5
+ password?: string;
6
+ }
7
+ /**
8
+ * WebDAV 存储实现。块是 8MiB 级小对象,目录按需创建并缓存,
9
+ * 避免老实现"每个文件逐级 MKCOL"的数千次多余往返。
10
+ */
11
+ export declare class WebDavStore implements RemoteStore {
12
+ private client;
13
+ private ensured;
14
+ private lastRequestAt;
15
+ constructor(opts: WebDavOptions);
16
+ /** 限速:相邻请求至少间隔 REQUEST_MIN_INTERVAL_MS,给 frpc 隧道留缓冲。 */
17
+ private throttle;
18
+ /** 调用方信号(如 SIGTERM)与统一超时取最早;半开连接超时后自动放弃,重试才有意义。 */
19
+ private scope;
20
+ listDevices(): Promise<string[]>;
21
+ list(path: string, signal?: AbortSignal): Promise<Array<{
22
+ name: string;
23
+ type: 'file' | 'directory';
24
+ size: number;
25
+ mtime?: number;
26
+ }>>;
27
+ /** 递归列前缀下所有文件(WebDAV 的 deep 列举一次返回)。 */
28
+ listAll(prefix: string, signal?: AbortSignal): Promise<RemoteEntry[]>;
29
+ readJson(path: string, signal?: AbortSignal): Promise<unknown | null>;
30
+ writeJson(path: string, value: unknown): Promise<void>;
31
+ writeFile(path: string, localPath: string, range?: {
32
+ offset: number;
33
+ length: number;
34
+ }): Promise<void>;
35
+ readFile(path: string, localPath: string): Promise<void>;
36
+ delete(path: string): Promise<void>;
37
+ /** WebDAV 实测并发 PUT 会 ECONNRESET,保持单连接顺序上传。 */
38
+ concurrencyHint(): number;
39
+ /** 逐段建父目录;已建过的段落直接跳过(缓存跨文件生效)。 */
40
+ private ensureDir;
41
+ }