@aigne/afs-did-space 1.12.0-beta.5

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 (510) hide show
  1. package/LICENSE.md +26 -0
  2. package/README.md +129 -0
  3. package/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.cjs +11 -0
  4. package/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.mjs +10 -0
  5. package/dist/_virtual/rolldown_runtime.cjs +29 -0
  6. package/dist/afs-metadata-context.cjs +20 -0
  7. package/dist/afs-metadata-context.d.cts +21 -0
  8. package/dist/afs-metadata-context.d.cts.map +1 -0
  9. package/dist/afs-metadata-context.d.mts +21 -0
  10. package/dist/afs-metadata-context.d.mts.map +1 -0
  11. package/dist/afs-metadata-context.mjs +18 -0
  12. package/dist/afs-metadata-context.mjs.map +1 -0
  13. package/dist/blocklet-hook.cjs +180 -0
  14. package/dist/blocklet-hook.d.cts +29 -0
  15. package/dist/blocklet-hook.d.cts.map +1 -0
  16. package/dist/blocklet-hook.d.mts +29 -0
  17. package/dist/blocklet-hook.d.mts.map +1 -0
  18. package/dist/blocklet-hook.mjs +178 -0
  19. package/dist/blocklet-hook.mjs.map +1 -0
  20. package/dist/changelog-schema.cjs +603 -0
  21. package/dist/changelog-schema.d.cts +395 -0
  22. package/dist/changelog-schema.d.cts.map +1 -0
  23. package/dist/changelog-schema.d.mts +395 -0
  24. package/dist/changelog-schema.d.mts.map +1 -0
  25. package/dist/changelog-schema.mjs +570 -0
  26. package/dist/changelog-schema.mjs.map +1 -0
  27. package/dist/changelog-write.cjs +80 -0
  28. package/dist/changelog-write.d.cts +70 -0
  29. package/dist/changelog-write.d.cts.map +1 -0
  30. package/dist/changelog-write.d.mts +70 -0
  31. package/dist/changelog-write.d.mts.map +1 -0
  32. package/dist/changelog-write.mjs +77 -0
  33. package/dist/changelog-write.mjs.map +1 -0
  34. package/dist/cid-byte-cache.cjs +126 -0
  35. package/dist/cid-byte-cache.d.cts +46 -0
  36. package/dist/cid-byte-cache.d.cts.map +1 -0
  37. package/dist/cid-byte-cache.d.mts +47 -0
  38. package/dist/cid-byte-cache.d.mts.map +1 -0
  39. package/dist/cid-byte-cache.mjs +125 -0
  40. package/dist/cid-byte-cache.mjs.map +1 -0
  41. package/dist/cid.cjs +68 -0
  42. package/dist/cid.d.cts +22 -0
  43. package/dist/cid.d.cts.map +1 -0
  44. package/dist/cid.d.mts +22 -0
  45. package/dist/cid.d.mts.map +1 -0
  46. package/dist/cid.mjs +65 -0
  47. package/dist/cid.mjs.map +1 -0
  48. package/dist/client.cjs +221 -0
  49. package/dist/client.d.cts +72 -0
  50. package/dist/client.d.cts.map +1 -0
  51. package/dist/client.d.mts +72 -0
  52. package/dist/client.d.mts.map +1 -0
  53. package/dist/client.mjs +220 -0
  54. package/dist/client.mjs.map +1 -0
  55. package/dist/cloudflare.cjs +510 -0
  56. package/dist/cloudflare.d.cts +307 -0
  57. package/dist/cloudflare.d.cts.map +1 -0
  58. package/dist/cloudflare.d.mts +308 -0
  59. package/dist/cloudflare.d.mts.map +1 -0
  60. package/dist/cloudflare.mjs +507 -0
  61. package/dist/cloudflare.mjs.map +1 -0
  62. package/dist/ctime-backfill.cjs +272 -0
  63. package/dist/ctime-backfill.d.cts +169 -0
  64. package/dist/ctime-backfill.d.cts.map +1 -0
  65. package/dist/ctime-backfill.d.mts +169 -0
  66. package/dist/ctime-backfill.d.mts.map +1 -0
  67. package/dist/ctime-backfill.mjs +267 -0
  68. package/dist/ctime-backfill.mjs.map +1 -0
  69. package/dist/d1-gc-queue.cjs +80 -0
  70. package/dist/d1-gc-queue.d.cts +59 -0
  71. package/dist/d1-gc-queue.d.cts.map +1 -0
  72. package/dist/d1-gc-queue.d.mts +59 -0
  73. package/dist/d1-gc-queue.d.mts.map +1 -0
  74. package/dist/d1-gc-queue.mjs +81 -0
  75. package/dist/d1-gc-queue.mjs.map +1 -0
  76. package/dist/d1-resolve-store.cjs +189 -0
  77. package/dist/d1-resolve-store.d.cts +93 -0
  78. package/dist/d1-resolve-store.d.cts.map +1 -0
  79. package/dist/d1-resolve-store.d.mts +93 -0
  80. package/dist/d1-resolve-store.d.mts.map +1 -0
  81. package/dist/d1-resolve-store.mjs +190 -0
  82. package/dist/d1-resolve-store.mjs.map +1 -0
  83. package/dist/d1-schema.cjs +361 -0
  84. package/dist/d1-schema.d.cts +237 -0
  85. package/dist/d1-schema.d.cts.map +1 -0
  86. package/dist/d1-schema.d.mts +237 -0
  87. package/dist/d1-schema.d.mts.map +1 -0
  88. package/dist/d1-schema.mjs +341 -0
  89. package/dist/d1-schema.mjs.map +1 -0
  90. package/dist/d1-share-store.cjs +0 -0
  91. package/dist/d1-share-store.d.cts +53 -0
  92. package/dist/d1-share-store.d.cts.map +1 -0
  93. package/dist/d1-share-store.d.mts +53 -0
  94. package/dist/d1-share-store.d.mts.map +1 -0
  95. package/dist/d1-share-store.mjs +0 -0
  96. package/dist/d1-share-store.mjs.map +1 -0
  97. package/dist/d1-tree-index.cjs +1098 -0
  98. package/dist/d1-tree-index.d.cts +203 -0
  99. package/dist/d1-tree-index.d.cts.map +1 -0
  100. package/dist/d1-tree-index.d.mts +203 -0
  101. package/dist/d1-tree-index.d.mts.map +1 -0
  102. package/dist/d1-tree-index.mjs +1094 -0
  103. package/dist/d1-tree-index.mjs.map +1 -0
  104. package/dist/data-backend.cjs +147 -0
  105. package/dist/data-backend.d.cts +30 -0
  106. package/dist/data-backend.d.cts.map +1 -0
  107. package/dist/data-backend.d.mts +30 -0
  108. package/dist/data-backend.d.mts.map +1 -0
  109. package/dist/data-backend.mjs +148 -0
  110. package/dist/data-backend.mjs.map +1 -0
  111. package/dist/dedup-backfill.cjs +278 -0
  112. package/dist/dedup-backfill.d.cts +165 -0
  113. package/dist/dedup-backfill.d.cts.map +1 -0
  114. package/dist/dedup-backfill.d.mts +165 -0
  115. package/dist/dedup-backfill.d.mts.map +1 -0
  116. package/dist/dedup-backfill.mjs +273 -0
  117. package/dist/dedup-backfill.mjs.map +1 -0
  118. package/dist/did-space-like.d.cts +2 -0
  119. package/dist/did-space-like.d.mts +2 -0
  120. package/dist/did-utils.cjs +44 -0
  121. package/dist/did-utils.d.cts +25 -0
  122. package/dist/did-utils.d.cts.map +1 -0
  123. package/dist/did-utils.d.mts +25 -0
  124. package/dist/did-utils.d.mts.map +1 -0
  125. package/dist/did-utils.mjs +43 -0
  126. package/dist/did-utils.mjs.map +1 -0
  127. package/dist/dirty-path-queue.cjs +31 -0
  128. package/dist/dirty-path-queue.d.cts +104 -0
  129. package/dist/dirty-path-queue.d.cts.map +1 -0
  130. package/dist/dirty-path-queue.d.mts +104 -0
  131. package/dist/dirty-path-queue.d.mts.map +1 -0
  132. package/dist/dirty-path-queue.mjs +31 -0
  133. package/dist/dirty-path-queue.mjs.map +1 -0
  134. package/dist/dirty-path-schema.cjs +142 -0
  135. package/dist/dirty-path-schema.d.cts +90 -0
  136. package/dist/dirty-path-schema.d.cts.map +1 -0
  137. package/dist/dirty-path-schema.d.mts +90 -0
  138. package/dist/dirty-path-schema.d.mts.map +1 -0
  139. package/dist/dirty-path-schema.mjs +136 -0
  140. package/dist/dirty-path-schema.mjs.map +1 -0
  141. package/dist/download-actions.cjs +43 -0
  142. package/dist/download-actions.mjs +40 -0
  143. package/dist/download-actions.mjs.map +1 -0
  144. package/dist/fs-object-store.cjs +153 -0
  145. package/dist/fs-object-store.d.cts +21 -0
  146. package/dist/fs-object-store.d.cts.map +1 -0
  147. package/dist/fs-object-store.d.mts +22 -0
  148. package/dist/fs-object-store.d.mts.map +1 -0
  149. package/dist/fs-object-store.mjs +153 -0
  150. package/dist/fs-object-store.mjs.map +1 -0
  151. package/dist/gc-queue.d.cts +43 -0
  152. package/dist/gc-queue.d.cts.map +1 -0
  153. package/dist/gc-queue.d.mts +43 -0
  154. package/dist/gc-queue.d.mts.map +1 -0
  155. package/dist/index.cjs +2712 -0
  156. package/dist/index.d.cts +504 -0
  157. package/dist/index.d.cts.map +1 -0
  158. package/dist/index.d.mts +505 -0
  159. package/dist/index.d.mts.map +1 -0
  160. package/dist/index.mjs +2537 -0
  161. package/dist/index.mjs.map +1 -0
  162. package/dist/inline-content-migration.cjs +212 -0
  163. package/dist/inline-content-migration.d.cts +110 -0
  164. package/dist/inline-content-migration.d.cts.map +1 -0
  165. package/dist/inline-content-migration.d.mts +111 -0
  166. package/dist/inline-content-migration.d.mts.map +1 -0
  167. package/dist/inline-content-migration.mjs +207 -0
  168. package/dist/inline-content-migration.mjs.map +1 -0
  169. package/dist/inline-content.cjs +145 -0
  170. package/dist/inline-content.d.cts +98 -0
  171. package/dist/inline-content.d.cts.map +1 -0
  172. package/dist/inline-content.d.mts +98 -0
  173. package/dist/inline-content.d.mts.map +1 -0
  174. package/dist/inline-content.mjs +137 -0
  175. package/dist/inline-content.mjs.map +1 -0
  176. package/dist/local-changelog.cjs +241 -0
  177. package/dist/local-changelog.d.cts +119 -0
  178. package/dist/local-changelog.d.cts.map +1 -0
  179. package/dist/local-changelog.d.mts +119 -0
  180. package/dist/local-changelog.d.mts.map +1 -0
  181. package/dist/local-changelog.mjs +240 -0
  182. package/dist/local-changelog.mjs.map +1 -0
  183. package/dist/local.cjs +435 -0
  184. package/dist/local.d.cts +254 -0
  185. package/dist/local.d.cts.map +1 -0
  186. package/dist/local.d.mts +254 -0
  187. package/dist/local.d.mts.map +1 -0
  188. package/dist/local.mjs +427 -0
  189. package/dist/local.mjs.map +1 -0
  190. package/dist/memory-gc-queue.cjs +36 -0
  191. package/dist/memory-gc-queue.d.cts +22 -0
  192. package/dist/memory-gc-queue.d.cts.map +1 -0
  193. package/dist/memory-gc-queue.d.mts +22 -0
  194. package/dist/memory-gc-queue.d.mts.map +1 -0
  195. package/dist/memory-gc-queue.mjs +36 -0
  196. package/dist/memory-gc-queue.mjs.map +1 -0
  197. package/dist/memory-tree-index.cjs +734 -0
  198. package/dist/memory-tree-index.d.cts +159 -0
  199. package/dist/memory-tree-index.d.cts.map +1 -0
  200. package/dist/memory-tree-index.d.mts +159 -0
  201. package/dist/memory-tree-index.d.mts.map +1 -0
  202. package/dist/memory-tree-index.mjs +735 -0
  203. package/dist/memory-tree-index.mjs.map +1 -0
  204. package/dist/metadata-db.cjs +562 -0
  205. package/dist/metadata-db.d.cts +102 -0
  206. package/dist/metadata-db.d.cts.map +1 -0
  207. package/dist/metadata-db.d.mts +102 -0
  208. package/dist/metadata-db.d.mts.map +1 -0
  209. package/dist/metadata-db.mjs +562 -0
  210. package/dist/metadata-db.mjs.map +1 -0
  211. package/dist/mime-utils.cjs +2 -0
  212. package/dist/mime-utils.d.cts +2 -0
  213. package/dist/mime-utils.d.mts +2 -0
  214. package/dist/mime-utils.mjs +3 -0
  215. package/dist/object-key.cjs +41 -0
  216. package/dist/object-key.d.cts +25 -0
  217. package/dist/object-key.d.cts.map +1 -0
  218. package/dist/object-key.d.mts +25 -0
  219. package/dist/object-key.d.mts.map +1 -0
  220. package/dist/object-key.mjs +40 -0
  221. package/dist/object-key.mjs.map +1 -0
  222. package/dist/packages/session/dist/index.cjs +788 -0
  223. package/dist/packages/session/dist/index.mjs +782 -0
  224. package/dist/packages/session/dist/index.mjs.map +1 -0
  225. package/dist/platform/r2/dist/types.d.cts +49 -0
  226. package/dist/platform/r2/dist/types.d.cts.map +1 -0
  227. package/dist/prefix-range.cjs +33 -0
  228. package/dist/prefix-range.d.cts +29 -0
  229. package/dist/prefix-range.d.cts.map +1 -0
  230. package/dist/prefix-range.d.mts +29 -0
  231. package/dist/prefix-range.d.mts.map +1 -0
  232. package/dist/prefix-range.mjs +33 -0
  233. package/dist/prefix-range.mjs.map +1 -0
  234. package/dist/prefixed-tree-index.cjs +238 -0
  235. package/dist/prefixed-tree-index.d.cts +85 -0
  236. package/dist/prefixed-tree-index.d.cts.map +1 -0
  237. package/dist/prefixed-tree-index.d.mts +85 -0
  238. package/dist/prefixed-tree-index.d.mts.map +1 -0
  239. package/dist/prefixed-tree-index.mjs +239 -0
  240. package/dist/prefixed-tree-index.mjs.map +1 -0
  241. package/dist/presign-signer.cjs +7 -0
  242. package/dist/presign-signer.d.cts +99 -0
  243. package/dist/presign-signer.d.cts.map +1 -0
  244. package/dist/presign-signer.d.mts +99 -0
  245. package/dist/presign-signer.d.mts.map +1 -0
  246. package/dist/presign-signer.mjs +7 -0
  247. package/dist/presign-signer.mjs.map +1 -0
  248. package/dist/providers/platform/r2/dist/index.d.mts +3 -0
  249. package/dist/providers/platform/r2/dist/types.d.mts +49 -0
  250. package/dist/providers/platform/r2/dist/types.d.mts.map +1 -0
  251. package/dist/reconcile.cjs +184 -0
  252. package/dist/reconcile.d.cts +26 -0
  253. package/dist/reconcile.d.cts.map +1 -0
  254. package/dist/reconcile.d.mts +26 -0
  255. package/dist/reconcile.d.mts.map +1 -0
  256. package/dist/reconcile.mjs +184 -0
  257. package/dist/reconcile.mjs.map +1 -0
  258. package/dist/reference-check.cjs +49 -0
  259. package/dist/reference-check.d.cts +10 -0
  260. package/dist/reference-check.d.cts.map +1 -0
  261. package/dist/reference-check.d.mts +10 -0
  262. package/dist/reference-check.d.mts.map +1 -0
  263. package/dist/reference-check.mjs +48 -0
  264. package/dist/reference-check.mjs.map +1 -0
  265. package/dist/remote-afs.cjs +681 -0
  266. package/dist/remote-afs.d.cts +236 -0
  267. package/dist/remote-afs.d.cts.map +1 -0
  268. package/dist/remote-afs.d.mts +236 -0
  269. package/dist/remote-afs.d.mts.map +1 -0
  270. package/dist/remote-afs.mjs +676 -0
  271. package/dist/remote-afs.mjs.map +1 -0
  272. package/dist/rename-guard.cjs +18 -0
  273. package/dist/rename-guard.mjs +18 -0
  274. package/dist/rename-guard.mjs.map +1 -0
  275. package/dist/resolve.cjs +260 -0
  276. package/dist/resolve.d.cts +113 -0
  277. package/dist/resolve.d.cts.map +1 -0
  278. package/dist/resolve.d.mts +113 -0
  279. package/dist/resolve.d.mts.map +1 -0
  280. package/dist/resolve.mjs +260 -0
  281. package/dist/resolve.mjs.map +1 -0
  282. package/dist/rsync-content.cjs +33 -0
  283. package/dist/rsync-content.mjs +33 -0
  284. package/dist/rsync-content.mjs.map +1 -0
  285. package/dist/rsync.cjs +400 -0
  286. package/dist/rsync.d.cts +157 -0
  287. package/dist/rsync.d.cts.map +1 -0
  288. package/dist/rsync.d.mts +157 -0
  289. package/dist/rsync.d.mts.map +1 -0
  290. package/dist/rsync.mjs +397 -0
  291. package/dist/rsync.mjs.map +1 -0
  292. package/dist/run-gc.cjs +57 -0
  293. package/dist/run-gc.d.cts +40 -0
  294. package/dist/run-gc.d.cts.map +1 -0
  295. package/dist/run-gc.d.mts +41 -0
  296. package/dist/run-gc.d.mts.map +1 -0
  297. package/dist/run-gc.mjs +57 -0
  298. package/dist/run-gc.mjs.map +1 -0
  299. package/dist/scope-id.cjs +70 -0
  300. package/dist/scope-id.d.cts +35 -0
  301. package/dist/scope-id.d.cts.map +1 -0
  302. package/dist/scope-id.d.mts +35 -0
  303. package/dist/scope-id.d.mts.map +1 -0
  304. package/dist/scope-id.mjs +68 -0
  305. package/dist/scope-id.mjs.map +1 -0
  306. package/dist/scope-migration-node.cjs +172 -0
  307. package/dist/scope-migration-node.d.cts +44 -0
  308. package/dist/scope-migration-node.d.cts.map +1 -0
  309. package/dist/scope-migration-node.d.mts +44 -0
  310. package/dist/scope-migration-node.d.mts.map +1 -0
  311. package/dist/scope-migration-node.mjs +171 -0
  312. package/dist/scope-migration-node.mjs.map +1 -0
  313. package/dist/scope-migration.cjs +155 -0
  314. package/dist/scope-migration.d.cts +65 -0
  315. package/dist/scope-migration.d.cts.map +1 -0
  316. package/dist/scope-migration.d.mts +65 -0
  317. package/dist/scope-migration.d.mts.map +1 -0
  318. package/dist/scope-migration.mjs +154 -0
  319. package/dist/scope-migration.mjs.map +1 -0
  320. package/dist/share-declaration.cjs +225 -0
  321. package/dist/share-declaration.d.cts +30 -0
  322. package/dist/share-declaration.d.cts.map +1 -0
  323. package/dist/share-declaration.d.mts +32 -0
  324. package/dist/share-declaration.d.mts.map +1 -0
  325. package/dist/share-declaration.mjs +221 -0
  326. package/dist/share-declaration.mjs.map +1 -0
  327. package/dist/share-hook.cjs +63 -0
  328. package/dist/share-hook.mjs +62 -0
  329. package/dist/share-hook.mjs.map +1 -0
  330. package/dist/share-resolve.cjs +176 -0
  331. package/dist/share-resolve.d.cts +36 -0
  332. package/dist/share-resolve.d.cts.map +1 -0
  333. package/dist/share-resolve.d.mts +36 -0
  334. package/dist/share-resolve.d.mts.map +1 -0
  335. package/dist/share-resolve.mjs +177 -0
  336. package/dist/share-resolve.mjs.map +1 -0
  337. package/dist/share-serve.cjs +145 -0
  338. package/dist/share-serve.d.cts +30 -0
  339. package/dist/share-serve.d.cts.map +1 -0
  340. package/dist/share-serve.d.mts +30 -0
  341. package/dist/share-serve.d.mts.map +1 -0
  342. package/dist/share-serve.mjs +144 -0
  343. package/dist/share-serve.mjs.map +1 -0
  344. package/dist/share-store.cjs +25 -0
  345. package/dist/share-store.d.cts +65 -0
  346. package/dist/share-store.d.cts.map +1 -0
  347. package/dist/share-store.d.mts +65 -0
  348. package/dist/share-store.d.mts.map +1 -0
  349. package/dist/share-store.mjs +25 -0
  350. package/dist/share-store.mjs.map +1 -0
  351. package/dist/share-virtual-path.cjs +102 -0
  352. package/dist/share-virtual-path.mjs +101 -0
  353. package/dist/share-virtual-path.mjs.map +1 -0
  354. package/dist/space-resolver.cjs +54 -0
  355. package/dist/space-resolver.d.cts +34 -0
  356. package/dist/space-resolver.d.cts.map +1 -0
  357. package/dist/space-resolver.d.mts +34 -0
  358. package/dist/space-resolver.d.mts.map +1 -0
  359. package/dist/space-resolver.mjs +55 -0
  360. package/dist/space-resolver.mjs.map +1 -0
  361. package/dist/sqlite-gc-queue.cjs +51 -0
  362. package/dist/sqlite-gc-queue.d.cts +22 -0
  363. package/dist/sqlite-gc-queue.d.cts.map +1 -0
  364. package/dist/sqlite-gc-queue.d.mts +22 -0
  365. package/dist/sqlite-gc-queue.d.mts.map +1 -0
  366. package/dist/sqlite-gc-queue.mjs +52 -0
  367. package/dist/sqlite-gc-queue.mjs.map +1 -0
  368. package/dist/sqlite-resolve-store.cjs +163 -0
  369. package/dist/sqlite-resolve-store.d.cts +50 -0
  370. package/dist/sqlite-resolve-store.d.cts.map +1 -0
  371. package/dist/sqlite-resolve-store.d.mts +50 -0
  372. package/dist/sqlite-resolve-store.d.mts.map +1 -0
  373. package/dist/sqlite-resolve-store.mjs +164 -0
  374. package/dist/sqlite-resolve-store.mjs.map +1 -0
  375. package/dist/sqlite-share-store.cjs +157 -0
  376. package/dist/sqlite-share-store.d.cts +31 -0
  377. package/dist/sqlite-share-store.d.cts.map +1 -0
  378. package/dist/sqlite-share-store.d.mts +31 -0
  379. package/dist/sqlite-share-store.d.mts.map +1 -0
  380. package/dist/sqlite-share-store.mjs +157 -0
  381. package/dist/sqlite-share-store.mjs.map +1 -0
  382. package/dist/sqlite-tree-index.cjs +901 -0
  383. package/dist/sqlite-tree-index.d.cts +107 -0
  384. package/dist/sqlite-tree-index.d.cts.map +1 -0
  385. package/dist/sqlite-tree-index.d.mts +107 -0
  386. package/dist/sqlite-tree-index.d.mts.map +1 -0
  387. package/dist/sqlite-tree-index.mjs +902 -0
  388. package/dist/sqlite-tree-index.mjs.map +1 -0
  389. package/dist/sync-engine.cjs +1324 -0
  390. package/dist/sync-engine.d.cts +473 -0
  391. package/dist/sync-engine.d.cts.map +1 -0
  392. package/dist/sync-engine.d.mts +473 -0
  393. package/dist/sync-engine.d.mts.map +1 -0
  394. package/dist/sync-engine.mjs +1315 -0
  395. package/dist/sync-engine.mjs.map +1 -0
  396. package/dist/sync-state.cjs +303 -0
  397. package/dist/sync-state.d.cts +224 -0
  398. package/dist/sync-state.d.cts.map +1 -0
  399. package/dist/sync-state.d.mts +224 -0
  400. package/dist/sync-state.d.mts.map +1 -0
  401. package/dist/sync-state.mjs +297 -0
  402. package/dist/sync-state.mjs.map +1 -0
  403. package/dist/timestamp.cjs +92 -0
  404. package/dist/timestamp.mjs +91 -0
  405. package/dist/timestamp.mjs.map +1 -0
  406. package/dist/transports/http.cjs +54 -0
  407. package/dist/transports/http.d.cts +26 -0
  408. package/dist/transports/http.d.cts.map +1 -0
  409. package/dist/transports/http.d.mts +26 -0
  410. package/dist/transports/http.d.mts.map +1 -0
  411. package/dist/transports/http.mjs +54 -0
  412. package/dist/transports/http.mjs.map +1 -0
  413. package/dist/transports/static.cjs +55 -0
  414. package/dist/transports/static.d.cts +28 -0
  415. package/dist/transports/static.d.cts.map +1 -0
  416. package/dist/transports/static.d.mts +28 -0
  417. package/dist/transports/static.d.mts.map +1 -0
  418. package/dist/transports/static.mjs +55 -0
  419. package/dist/transports/static.mjs.map +1 -0
  420. package/dist/transports/types.cjs +13 -0
  421. package/dist/transports/types.d.cts +72 -0
  422. package/dist/transports/types.d.cts.map +1 -0
  423. package/dist/transports/types.d.mts +72 -0
  424. package/dist/transports/types.d.mts.map +1 -0
  425. package/dist/transports/types.mjs +13 -0
  426. package/dist/transports/types.mjs.map +1 -0
  427. package/dist/transports/ws.cjs +207 -0
  428. package/dist/transports/ws.d.cts +47 -0
  429. package/dist/transports/ws.d.cts.map +1 -0
  430. package/dist/transports/ws.d.mts +47 -0
  431. package/dist/transports/ws.d.mts.map +1 -0
  432. package/dist/transports/ws.mjs +206 -0
  433. package/dist/transports/ws.mjs.map +1 -0
  434. package/dist/tree-index.cjs +333 -0
  435. package/dist/tree-index.d.cts +524 -0
  436. package/dist/tree-index.d.cts.map +1 -0
  437. package/dist/tree-index.d.mts +524 -0
  438. package/dist/tree-index.d.mts.map +1 -0
  439. package/dist/tree-index.mjs +322 -0
  440. package/dist/tree-index.mjs.map +1 -0
  441. package/dist/tree-query.cjs +655 -0
  442. package/dist/tree-query.d.cts +70 -0
  443. package/dist/tree-query.d.cts.map +1 -0
  444. package/dist/tree-query.d.mts +70 -0
  445. package/dist/tree-query.d.mts.map +1 -0
  446. package/dist/tree-query.mjs +648 -0
  447. package/dist/tree-query.mjs.map +1 -0
  448. package/dist/upload-actions.cjs +97 -0
  449. package/dist/upload-actions.d.cts +19 -0
  450. package/dist/upload-actions.d.cts.map +1 -0
  451. package/dist/upload-actions.d.mts +21 -0
  452. package/dist/upload-actions.d.mts.map +1 -0
  453. package/dist/upload-actions.mjs +90 -0
  454. package/dist/upload-actions.mjs.map +1 -0
  455. package/dist/upload-session-store.cjs +185 -0
  456. package/dist/upload-session-store.d.cts +99 -0
  457. package/dist/upload-session-store.d.cts.map +1 -0
  458. package/dist/upload-session-store.d.mts +99 -0
  459. package/dist/upload-session-store.d.mts.map +1 -0
  460. package/dist/upload-session-store.mjs +184 -0
  461. package/dist/upload-session-store.mjs.map +1 -0
  462. package/dist/url-normalize.cjs +61 -0
  463. package/dist/url-normalize.d.cts +33 -0
  464. package/dist/url-normalize.d.cts.map +1 -0
  465. package/dist/url-normalize.d.mts +33 -0
  466. package/dist/url-normalize.d.mts.map +1 -0
  467. package/dist/url-normalize.mjs +61 -0
  468. package/dist/url-normalize.mjs.map +1 -0
  469. package/dist/versioned-content-backends.cjs +155 -0
  470. package/dist/versioned-content-backends.d.cts +49 -0
  471. package/dist/versioned-content-backends.d.cts.map +1 -0
  472. package/dist/versioned-content-backends.d.mts +49 -0
  473. package/dist/versioned-content-backends.d.mts.map +1 -0
  474. package/dist/versioned-content-backends.mjs +154 -0
  475. package/dist/versioned-content-backends.mjs.map +1 -0
  476. package/dist/versioned-content-index.cjs +102 -0
  477. package/dist/versioned-content-index.d.cts +46 -0
  478. package/dist/versioned-content-index.d.cts.map +1 -0
  479. package/dist/versioned-content-index.d.mts +46 -0
  480. package/dist/versioned-content-index.d.mts.map +1 -0
  481. package/dist/versioned-content-index.mjs +103 -0
  482. package/dist/versioned-content-index.mjs.map +1 -0
  483. package/dist/versioned-content-store.cjs +121 -0
  484. package/dist/versioned-content-store.d.cts +90 -0
  485. package/dist/versioned-content-store.d.cts.map +1 -0
  486. package/dist/versioned-content-store.d.mts +90 -0
  487. package/dist/versioned-content-store.d.mts.map +1 -0
  488. package/dist/versioned-content-store.mjs +122 -0
  489. package/dist/versioned-content-store.mjs.map +1 -0
  490. package/dist/versioned-scope.cjs +39 -0
  491. package/dist/versioned-scope.d.cts +57 -0
  492. package/dist/versioned-scope.d.cts.map +1 -0
  493. package/dist/versioned-scope.d.mts +57 -0
  494. package/dist/versioned-scope.d.mts.map +1 -0
  495. package/dist/versioned-scope.mjs +37 -0
  496. package/dist/versioned-scope.mjs.map +1 -0
  497. package/migrations/0001_tree_entries.sql +17 -0
  498. package/migrations/0002_resolve_entries.sql +7 -0
  499. package/migrations/0003_entries_v2.sql +38 -0
  500. package/migrations/0004_gc_queue.sql +5 -0
  501. package/migrations/0006_resolve_enhance.sql +14 -0
  502. package/migrations/0007_resolve_created_at.sql +10 -0
  503. package/migrations/0008_changelog.sql +34 -0
  504. package/migrations/0009_device_sync_state.sql +29 -0
  505. package/migrations/0010_scope_meta.sql +33 -0
  506. package/migrations/0011_shares.sql +25 -0
  507. package/migrations/0012_scope_meta_file_count.sql +41 -0
  508. package/migrations/0013_scope_meta_total_bytes.sql +47 -0
  509. package/migrations/0014_rename_tables.sql +62 -0
  510. package/package.json +102 -0
@@ -0,0 +1,1324 @@
1
+ const require_rolldown_runtime = require('./_virtual/rolldown_runtime.cjs');
2
+ const require_cid = require('./cid.cjs');
3
+ const require_download_actions = require('./download-actions.cjs');
4
+ require('./mime-utils.cjs');
5
+ const require_inline_content = require('./inline-content.cjs');
6
+ const require_remote_afs = require('./remote-afs.cjs');
7
+ let _aigne_afs = require("@aigne/afs");
8
+ let _aigne_afs_utils_env = require("@aigne/afs/utils/env");
9
+ let _aigne_afs_provider_utils = require("@aigne/afs-provider-utils");
10
+
11
+ //#region src/sync-engine.ts
12
+ const log = (0, _aigne_afs.makeNsLog)("provider:did-space");
13
+ /** Client-side batch packing constants (wire-contract §9 CF tier — packing
14
+ * to the CF table is slightly conservative against Node but always valid;
15
+ * the client never needs to know the server runtime, design §7.3). */
16
+ const BATCH_PUT_MAX_ENTRIES = 20;
17
+ const BATCH_DELETE_MAX_ENTRIES = 40;
18
+ const BATCH_PUT_MAX_BYTES = 1024 * 1024;
19
+ const BATCH_READ_MAX_ENTRIES = 20;
20
+ const BATCH_READ_MAX_BYTES = 2 * 1024 * 1024;
21
+ /**
22
+ * Push batch size. Each round, push() fetches up to this many unpushed
23
+ * changelog rows in a single SQLite query and uploads them one by one.
24
+ * Lower than PULL_BATCH_LIMIT because each row is a separate PUT/DELETE
25
+ * round-trip (vs. PULL which fetches many change rows in a single call).
26
+ */
27
+ const PUSH_BATCH_LIMIT = 100;
28
+ /**
29
+ * Client-side upload-plane ACTIVATION threshold (design §6.1 / §15 decision 4).
30
+ * The upload plane (prepare → out-of-band PUT → commit) is taken ONLY for large
31
+ * backlogs — the Aside pain point of a ~2000-row small-record first full sync.
32
+ * Small/common writes stay on the proven `batchWrite` path. A push uses the
33
+ * upload plane only when its put backlog is ≥ MIN_ENTRIES (peeked) AND the
34
+ * server advertises grant capability.
35
+ *
36
+ * Value 200 is deliberate, not a placeholder: lowering it does nothing for the
37
+ * 2000 case (2000 ≥ 200 already activates) and would only pull the 100–200 band
38
+ * onto the upload plane — MORE round-trips than batchWrite's ceil(N/20) POSTs.
39
+ * Only lower with a measured 100–200 wall-clock crossover.
40
+ */
41
+ const AFS_DID_SPACE_UPLOAD_MIN_ENTRIES = 200;
42
+ const AFS_DID_SPACE_UPLOAD_MIN_BYTES = 8 * 1024 * 1024;
43
+ /**
44
+ * Browser upload-plane SINGLE-FILE size threshold (upload-browser §4.5 / T4.2).
45
+ * Distinct from the native-sync count/bytes batch gates above: the browser
46
+ * orchestrator triages by individual file size — a file LARGER than this goes
47
+ * the grant plane (bytes direct to R2, out of the worker); smaller files fall to
48
+ * the legacy WS batchWrite. The browser orchestrator mirrors this value in its
49
+ * inlined page JS (it cannot import this const). Conservative default — only
50
+ * clearly-large files grant; measurement-gated, calibrated in P6.
51
+ */
52
+ const AFS_DID_SPACE_UPLOAD_MIN_SIZE = 4 * 1024 * 1024;
53
+ /**
54
+ * Upload-plane put-CHUNK caps (the load-bearing fix for Aside 2000 small writes).
55
+ * Once the upload plane is active, each prepare/commit round packs up to this
56
+ * many entries — DECOUPLED from `BATCH_PUT_MAX_ENTRIES` (20). Reusing the batch
57
+ * cap would turn a 2000-entry backlog into ~100 prepare/commit rounds; at 100 it
58
+ * is ~20. The effective chunk is `min(CHUNK_MAX_ENTRIES, PUSH_BATCH_LIMIT)`, and
59
+ * `PUSH_BATCH_LIMIT` is also 100, so a full window is one chunk → 20 rounds for
60
+ * 2000. The Cloudflare commit-sim probe (100 entries ≈ 425ms wall) justifies 100
61
+ * as a safe client default; raising it past PUSH_BATCH_LIMIT is inert.
62
+ */
63
+ const AFS_DID_SPACE_UPLOAD_CHUNK_MAX_ENTRIES = 100;
64
+ const AFS_DID_SPACE_UPLOAD_CHUNK_MAX_BYTES = 8 * 1024 * 1024;
65
+ /**
66
+ * Out-of-band PUT fan-out for the upload plane (independent tuning, design §3).
67
+ * Each presigned PUT leaves the worker, so the client can run several in flight;
68
+ * 8 is the deterministic default. Adaptive 8→4 backoff under R2 5xx/429 is a
69
+ * follow-up, intentionally NOT in the first patch.
70
+ */
71
+ const AFS_DID_SPACE_UPLOAD_PUT_CONCURRENCY = 8;
72
+ /**
73
+ * Flush local_seq to SQLite every N successful pushes. Trade-off: lower
74
+ * = less progress lost on crash, higher = fewer DB writes. 10 is the
75
+ * round-trip frequency Task 3.6 spec mandates.
76
+ */
77
+ const PUSH_PROGRESS_FLUSH_INTERVAL = 10;
78
+ /**
79
+ * Pull batch size. Each `.changelog` cursor list call fetches up to this
80
+ * many rows. Matches the server's MAX_LIMIT hard cap
81
+ * (`services/arc-computer/src/handlers/space-api.ts` — .changelog list
82
+ * branch inside `_createSpaceHandler`).
83
+ */
84
+ const PULL_BATCH_LIMIT = 1e3;
85
+ /**
86
+ * Concurrency cap for `.objects/{cid}` reads inside pull(). Higher = faster
87
+ * under large pulls, but risks server-side rate limits and memory
88
+ * pressure on the client. 4 is the spec default.
89
+ */
90
+ const PULL_FETCH_CONCURRENCY = 4;
91
+ /**
92
+ * Download-plane size gate (design §6.3 / T3.5) — CALIBRATED by P4/T4.4 staging
93
+ * measurement (2026-06-21, aside.afsd.io, logs/staging-download-plane-*.log).
94
+ * An object whose size is ≥ this routes to the download plane (prepare-download
95
+ * → direct R2 GET → computeCID → internalWrite), keeping its bytes out of the
96
+ * worker; smaller objects / text / inline-cache hits stay on `batchRead` (D1
97
+ * inline serves small text with ZERO R2 GETs — faster there). A null/missing
98
+ * size is treated as small (never route into the plane on missing size).
99
+ *
100
+ * MEASURED breaking point: a single in-worker `.objects/{cid}` read (arrayBuffer
101
+ * + base64-over-JSON) is fine at 5 MiB (~930ms) but HARD-FAILS with HTTP 503
102
+ * (Worker exceeded resource limits) at 8 / 12 / 18 MiB — the download plane
103
+ * served all of those correctly (8 MiB ~0.8s warm). So the worker simply CANNOT
104
+ * read a single object ≳8 MiB; 4 MiB sits safely below that failure zone (and
105
+ * covers the reported 5.26 MiB pain case) while keeping the common small/medium
106
+ * objects on the locally-fast worker path.
107
+ */
108
+ const DOWNLOAD_PLANE_MIN_BYTES = 4 * 1024 * 1024;
109
+ /**
110
+ * Out-of-band GET fan-out for the download plane (design §6.3). The client reads
111
+ * directly from R2 and is NOT bound by the Workers 6-simultaneous-connection cap,
112
+ * so the default is higher than the in-worker batchRead. Default 8; TS/Node may
113
+ * override via `AFS_DID_SPACE_DOWNLOAD_GET_CONCURRENCY` (a single-point int parse
114
+ * — NOT parseBool; the `AFS_DID_SPACE_DOWNLOAD_*` family, mirror of the shipped
115
+ * `AFS_DID_SPACE_UPLOAD_*`). Swift/Kotlin clients use their own platform constant.
116
+ */
117
+ const DOWNLOAD_GET_CONCURRENCY_DEFAULT = 8;
118
+ function resolveDownloadGetConcurrency() {
119
+ const raw = typeof process !== "undefined" && process.env ? (0, _aigne_afs_utils_env.firstNonEmpty)(process.env.AFS_DID_SPACE_DOWNLOAD_GET_CONCURRENCY) : void 0;
120
+ const n = raw !== void 0 ? Number.parseInt(raw, 10) : NaN;
121
+ return Number.isInteger(n) && n > 0 ? n : DOWNLOAD_GET_CONCURRENCY_DEFAULT;
122
+ }
123
+ /**
124
+ * Wall-clock budget for a single sync() call. Protects against runaway
125
+ * pulls on wildly out-of-date clients. pull() checks Date.now() against
126
+ * the deadline at the top of every batch loop iteration.
127
+ */
128
+ const SYNC_WALL_CLOCK_BUDGET_MS = 25e3;
129
+ /**
130
+ * Thrown by {@link SyncEngine.resolveConflict} when the server's 409
131
+ * conflict response is missing required fields (notably `currentMtime`).
132
+ * The LWW decision needs both timestamps, so a malformed response is
133
+ * treated as fatal to the in-flight push leg.
134
+ *
135
+ * Phase 5 Task 5.1 (G11): replaced the Task 3.6 `ConflictTODOError`
136
+ * stub with the real LWW resolver. The resolver handles every well-formed
137
+ * conflict shape inline; this error class only fires on protocol breakage.
138
+ */
139
+ var ConflictResolutionError = class extends Error {
140
+ change;
141
+ reason;
142
+ constructor(change, reason) {
143
+ super(`resolveConflict on ${change.path}: ${reason}`);
144
+ this.name = "ConflictResolutionError";
145
+ this.change = change;
146
+ this.reason = reason;
147
+ }
148
+ };
149
+ /**
150
+ * Thrown by {@link SyncEngine.fullResync} when the cloud-side manifest
151
+ * snapshot exceeds the size budget the server is willing to materialise
152
+ * in one shot. The server signals this via `result.meta.truncated === true`
153
+ * on the `cloudAfs.list("", { maxDepth: -1 })` response.
154
+ *
155
+ * Recovery: the caller (typically a higher-level scheduler) is expected
156
+ * to either narrow the scope (sub-tree manifest, future feature) or
157
+ * back off and retry once the cloud has compacted the manifest. There
158
+ * is no automatic recovery inside SyncEngine — applying a partial
159
+ * manifest would silently lose data.
160
+ */
161
+ var ManifestTooLargeError = class extends Error {
162
+ constructor(message = "fullResync: cloud manifest is too large to materialise in one shot") {
163
+ super(message);
164
+ this.name = "ManifestTooLargeError";
165
+ }
166
+ };
167
+ /**
168
+ * Thrown when an object read returns 404 for a cid that is the **current**
169
+ * state of a path — design §4.7.2, the GC-go-live safety contract. Enabling
170
+ * object GC makes a current-cid 404 possible (a GC bug or the residual
171
+ * re-reference race); it MUST be a loud, hard error — NEVER silently treated as
172
+ * convergence. The cursor must stay pinned (do not advance past the failing
173
+ * change) so the next sync revisits it once the object is restored/re-pushed.
174
+ *
175
+ * Distinct from a **superseded**-cid 404 (an old changelog row a later same-path
176
+ * row supersedes), which is benign — skip + advance (the Path-Y offline
177
+ * tradeoff, §0.2). Classification: see {@link classifyMissingObject}.
178
+ */
179
+ var CurrentObjectMissingError = class extends Error {
180
+ path;
181
+ cid;
182
+ source;
183
+ constructor(path, cid, source) {
184
+ super(`SyncEngine: current object missing (${source}) for path ${path} cid ${cid} — object GC may have reclaimed a live cid; cursor left pinned for retry`);
185
+ this.name = "CurrentObjectMissingError";
186
+ this.path = path;
187
+ this.cid = cid;
188
+ this.source = source;
189
+ }
190
+ };
191
+ /**
192
+ * Classify an object-read 404 during incremental apply (design §4.7.2).
193
+ *
194
+ * `lastCidForPath` maps each path in the batch to the cid of its LAST row
195
+ * (max seq) — `null` if that last row is a delete. Fail-closed:
196
+ * - cid === the path's last-row cid ⇒ "current" (this cid is the path's
197
+ * final state in the batch) ⇒ hard error.
198
+ * - path absent from the map (unknown cid, not in this batch) ⇒ "current"
199
+ * (fail closed — never benign-skip something we can't prove superseded).
200
+ * - otherwise a later same-path row supersedes this cid ⇒ "superseded" ⇒ skip.
201
+ */
202
+ function classifyMissingObject(path, cid, lastCidForPath) {
203
+ if (!lastCidForPath.has(path)) return "current";
204
+ return lastCidForPath.get(path) === cid ? "current" : "superseded";
205
+ }
206
+ /**
207
+ * Simple promise-based mutex used to serialise concurrent sync() calls.
208
+ * Callers `await acquire()` to claim the lock and MUST `release()` in a
209
+ * finally block. FIFO ordering is not guaranteed — the spec only
210
+ * requires mutual exclusion.
211
+ */
212
+ var AsyncMutex = class {
213
+ chain = Promise.resolve();
214
+ async acquire() {
215
+ let release;
216
+ const next = new Promise((resolve) => {
217
+ release = resolve;
218
+ });
219
+ const prev = this.chain;
220
+ this.chain = this.chain.then(() => next);
221
+ await prev;
222
+ return release;
223
+ }
224
+ };
225
+ /**
226
+ * Local push + pull + GC orchestrator. One instance per DIDSpaceProvider /
227
+ * per userDid. Instances are NOT thread-safe at the data level — the
228
+ * internal mutex only serialises `sync()` calls; callers that mutate
229
+ * local state while a sync is in flight are expected to do so via
230
+ * `provider.internalWrite/Delete` so the changelog row is visible to the
231
+ * next push batch.
232
+ */
233
+ var SyncEngine = class {
234
+ localChangelog;
235
+ syncStateStore;
236
+ cloudAfs;
237
+ provider;
238
+ selfDeviceId;
239
+ wallClockBudgetMs;
240
+ mutex = new AsyncMutex();
241
+ /**
242
+ * Per-INSTANCE capability-fallback memory (design §7.1): set the first
243
+ * time the server answers a batch envelope with the exact
244
+ * "Unsupported op 'batch…'" 400. Never set by any other 400 (e.g.
245
+ * BATCH_OVER_BUDGET halves and retries instead).
246
+ */
247
+ batchFallback = false;
248
+ /**
249
+ * Per-INSTANCE upload-plane capability memory (design §7.1, mirrors
250
+ * batchFallback). null = not yet probed; set on the first `push()` that
251
+ * reaches the threshold. `false` (Node / old server / signer-less) keeps the
252
+ * engine on batchWrite forever for this instance.
253
+ */
254
+ uploadPlaneCapable = null;
255
+ /** Set per-push() when this push routes large put-chunks through the upload plane. */
256
+ uploadPlaneActive = false;
257
+ /**
258
+ * Per-INSTANCE download-plane capability memory (design §7.3, mirrors
259
+ * uploadPlaneCapable). null = not yet probed. `false` (Node / old server /
260
+ * signer-less) keeps the engine on batchRead forever for this instance — it is
261
+ * NOT reset per session (unlike {@link downloadFallback}): a server that never
262
+ * advertised the capability won't start mid-process, so re-probing every sync
263
+ * would just waste a `/.actions` list. A server UPGRADE needs a client restart.
264
+ */
265
+ downloadPlaneCapable = null;
266
+ /**
267
+ * Runtime fallback-on-403 latch (design §7.3): set when a presigned GET is
268
+ * rejected 401/403 (token lacks GetObject / bad signature). Once set, the whole
269
+ * sync SESSION reverts to batchRead and never re-tries the plane — so a misconfig
270
+ * costs at most ONE 403 per object before the first hit flips it, then nothing.
271
+ * Reset at the start of each sync() so a fixed token recovers without restart.
272
+ */
273
+ downloadFallback = false;
274
+ /** Resolved once per instance — the out-of-band GET fan-out (env-overridable). */
275
+ downloadGetConcurrency = resolveDownloadGetConcurrency();
276
+ constructor(opts) {
277
+ if (!opts.deviceId) throw new Error("SyncEngine: deviceId is required");
278
+ this.localChangelog = opts.localChangelog;
279
+ this.syncStateStore = opts.syncStateStore;
280
+ this.cloudAfs = opts.cloudAfs;
281
+ this.provider = opts.provider;
282
+ this.selfDeviceId = opts.deviceId;
283
+ this.wallClockBudgetMs = opts.wallClockBudgetMs ?? SYNC_WALL_CLOCK_BUDGET_MS;
284
+ }
285
+ /**
286
+ * Run a single sync round — push → pull → gc. Concurrent sync() calls
287
+ * on the same instance are serialised by the internal mutex. Always
288
+ * returns a {@link SyncResult}; throws only on unhandled errors from
289
+ * the underlying store/network layers.
290
+ */
291
+ async sync(opts = {}) {
292
+ const release = await this.mutex.acquire();
293
+ const deadline = Date.now() + this.wallClockBudgetMs;
294
+ this.downloadFallback = false;
295
+ const result = {
296
+ ok: false,
297
+ aborted: false,
298
+ pushed: 0,
299
+ pulled: 0,
300
+ gcDeleted: 0,
301
+ fullResynced: false,
302
+ endedAt: 0
303
+ };
304
+ try {
305
+ if (opts.signal?.aborted) {
306
+ result.aborted = true;
307
+ result.endedAt = Date.now();
308
+ await this.syncStateStore.save({ syncStatus: { kind: "idle" } });
309
+ return result;
310
+ }
311
+ await this.syncStateStore.save({ syncStatus: { kind: "syncing" } });
312
+ try {
313
+ if ((await this.syncStateStore.load()).lastSyncAt === 0) {
314
+ result.pulled = (await this.fullResync({
315
+ signal: opts.signal,
316
+ deadline
317
+ })).pulled;
318
+ result.fullResynced = true;
319
+ } else {
320
+ result.pushed = (await this.push({
321
+ signal: opts.signal,
322
+ deadline
323
+ })).pushed;
324
+ const pullResult = await this.pull({
325
+ signal: opts.signal,
326
+ deadline
327
+ });
328
+ result.pulled = pullResult.pulled;
329
+ if (pullResult.fullResynced) result.fullResynced = true;
330
+ }
331
+ result.gcDeleted = (await this.localChangelog.gc()).deleted;
332
+ await this.syncStateStore.save({
333
+ syncStatus: { kind: "active" },
334
+ lastSyncAt: Date.now()
335
+ });
336
+ result.ok = true;
337
+ } catch (e) {
338
+ if (opts.signal?.aborted) {
339
+ result.aborted = true;
340
+ await this.syncStateStore.save({ syncStatus: { kind: "idle" } });
341
+ return result;
342
+ }
343
+ const message = e instanceof Error ? e.message : String(e);
344
+ await this.syncStateStore.save({ syncStatus: {
345
+ kind: "error",
346
+ message
347
+ } });
348
+ throw e;
349
+ }
350
+ } finally {
351
+ result.endedAt = Date.now();
352
+ release();
353
+ }
354
+ return result;
355
+ }
356
+ /**
357
+ * Upload unpushed local changes to the cloud, advancing local_seq in
358
+ * SQLite every `PUSH_PROGRESS_FLUSH_INTERVAL` rows. On any error
359
+ * mid-batch, flush the already-advanced cursor before re-throwing so
360
+ * the next sync() resumes where this one left off.
361
+ *
362
+ * Per-row semantics:
363
+ * - `put` row → `cloudAfs.write(path, content, {...})`
364
+ * - `delete` row → `cloudAfs.delete(path, {...})`
365
+ * - `put` row whose file has been deleted locally since the row was
366
+ * written → skip (cursor advances; the following delete row will
367
+ * tell the cloud what happened).
368
+ * - 409 conflict → delegate to {@link SyncEngine.resolveConflict}
369
+ * (LWW + tiebreak — see G11 in native-sync-design.md).
370
+ */
371
+ async push(opts) {
372
+ let cursor = (await this.syncStateStore.load()).localSeq;
373
+ let pushed = 0;
374
+ let processedSinceFlush = 0;
375
+ this.uploadPlaneActive = await this.shouldUseUploadPlane(cursor);
376
+ const batchCapable = () => !this.batchFallback && typeof this.cloudAfs.batchWrite === "function" && typeof this.cloudAfs.batchDelete === "function";
377
+ try {
378
+ while (true) {
379
+ if (opts.signal?.aborted) return { pushed };
380
+ if (Date.now() >= opts.deadline) return { pushed };
381
+ const batch = await this.localChangelog.getUnpushed(cursor, PUSH_BATCH_LIMIT);
382
+ if (batch.length === 0) break;
383
+ if (batchCapable()) {
384
+ const r = await this.pushBatchLeg(batch, cursor, opts);
385
+ cursor = r.cursor;
386
+ pushed += r.pushed;
387
+ await this.syncStateStore.save({ localSeq: cursor });
388
+ if (r.stop) return { pushed };
389
+ continue;
390
+ }
391
+ for (const change of batch) {
392
+ if (opts.signal?.aborted) return { pushed };
393
+ if (Date.now() >= opts.deadline) return { pushed };
394
+ await this.pushOne(change);
395
+ cursor = change.seq;
396
+ pushed += 1;
397
+ processedSinceFlush += 1;
398
+ if (processedSinceFlush >= PUSH_PROGRESS_FLUSH_INTERVAL) {
399
+ await this.syncStateStore.save({ localSeq: cursor });
400
+ processedSinceFlush = 0;
401
+ }
402
+ }
403
+ }
404
+ } finally {
405
+ if (cursor > 0) await this.syncStateStore.save({ localSeq: cursor });
406
+ }
407
+ return { pushed };
408
+ }
409
+ /**
410
+ * Batch push leg (P4, design §7.2): split the changelog window into
411
+ * MAXIMAL same-op runs in seq order (op type never mixes inside a run —
412
+ * put(a)→delete(a) is two runs, so same-path cross-op order rides the
413
+ * sequential awaits); pack each run by maxEntries + Σ-decoded-bytes +
414
+ * UNIQUE-path-per-chunk (a duplicate path cuts the chunk so the server's
415
+ * one-shot preread stays serial-equivalent — and the SECOND chunk's
416
+ * ifMatch sees the first chunk's result); consume results strictly in
417
+ * order. Returns the advanced cursor; `stop` = abort/deadline/tail-skip.
418
+ */
419
+ async pushBatchLeg(batch, cursor, opts) {
420
+ let pushed = 0;
421
+ const runs = [];
422
+ for (const change of batch) {
423
+ const last = runs[runs.length - 1];
424
+ if (last && last[0].op === change.op) last.push(change);
425
+ else runs.push([change]);
426
+ }
427
+ for (const run of runs) {
428
+ let index = 0;
429
+ while (index < run.length) {
430
+ if (opts.signal?.aborted || Date.now() >= opts.deadline) return {
431
+ cursor,
432
+ pushed,
433
+ stop: true
434
+ };
435
+ const putUploadPlane = run[0].op === "put" && this.uploadPlaneActive;
436
+ const maxEntries = run[0].op !== "put" ? BATCH_DELETE_MAX_ENTRIES : putUploadPlane ? AFS_DID_SPACE_UPLOAD_CHUNK_MAX_ENTRIES : BATCH_PUT_MAX_ENTRIES;
437
+ const maxBytes = putUploadPlane ? AFS_DID_SPACE_UPLOAD_CHUNK_MAX_BYTES : BATCH_PUT_MAX_BYTES;
438
+ const chunk = [];
439
+ const seen = /* @__PURE__ */ new Set();
440
+ let chunkBytes = 0;
441
+ while (index < run.length && chunk.length < maxEntries) {
442
+ const change = run[index];
443
+ if (seen.has(change.path)) break;
444
+ if (change.op === "put") {
445
+ const bytes = await this.provider.readBytes(change.path);
446
+ if (bytes === null) {
447
+ cursor = change.seq;
448
+ index++;
449
+ continue;
450
+ }
451
+ if (chunk.length > 0 && chunkBytes + bytes.length > maxBytes) break;
452
+ chunkBytes += bytes.length;
453
+ chunk.push({
454
+ change,
455
+ bytes
456
+ });
457
+ } else chunk.push({
458
+ change,
459
+ bytes: null
460
+ });
461
+ seen.add(change.path);
462
+ index++;
463
+ }
464
+ if (chunk.length === 0) continue;
465
+ const outcome = await this.sendPushChunk(chunk);
466
+ if (outcome.fallback) {
467
+ this.batchFallback = true;
468
+ return {
469
+ cursor,
470
+ pushed,
471
+ stop: false
472
+ };
473
+ }
474
+ for (const consumed of outcome.consumed) {
475
+ cursor = consumed.seq;
476
+ if (consumed.counted) pushed += 1;
477
+ }
478
+ if (outcome.consumed.length > 0 || outcome.error) await this.syncStateStore.save({ localSeq: cursor });
479
+ if (outcome.error) throw outcome.error;
480
+ if (outcome.tailSkipped) return {
481
+ cursor,
482
+ pushed,
483
+ stop: false
484
+ };
485
+ }
486
+ }
487
+ return {
488
+ cursor,
489
+ pushed,
490
+ stop: false
491
+ };
492
+ }
493
+ /**
494
+ * Send one packed chunk, halving on envelope over-budget (down to 1 entry
495
+ * = pushOne, the literal old behavior — bounded recursion by construction:
496
+ * each level halves a strictly smaller chunk).
497
+ */
498
+ async sendPushChunk(chunk) {
499
+ const op = chunk[0].change.op;
500
+ let result;
501
+ try {
502
+ if (op === "put") result = this.uploadPlaneActive ? await this.uploadPlaneWriteChunk(chunk) : await this.cloudAfs.batchWrite(await Promise.all(chunk.map(async ({ change, bytes }) => {
503
+ const appMeta = await this.readAppMeta(change.path, bytes);
504
+ return {
505
+ path: normaliseWirePath(change.path),
506
+ content: appMeta ? {
507
+ content: bytes,
508
+ meta: appMeta
509
+ } : { content: bytes },
510
+ options: {
511
+ ...change.old_cid ? { ifMatch: change.old_cid } : {},
512
+ meta: {
513
+ deviceId: change.device_id,
514
+ mtime: change.mtime
515
+ }
516
+ }
517
+ };
518
+ })));
519
+ else result = await this.cloudAfs.batchDelete(chunk.map(({ change }) => ({
520
+ path: normaliseWirePath(change.path),
521
+ options: { meta: {
522
+ deviceId: change.device_id,
523
+ mtime: change.mtime,
524
+ ...change.old_cid ? { ifMatch: change.old_cid } : {}
525
+ } }
526
+ })));
527
+ } catch (e) {
528
+ if (e instanceof require_remote_afs.BatchUnsupportedError) return {
529
+ consumed: [],
530
+ tailSkipped: false,
531
+ fallback: true
532
+ };
533
+ if (e instanceof require_remote_afs.BatchOverBudgetError) {
534
+ if (chunk.length === 1) {
535
+ await this.pushOne(chunk[0].change);
536
+ return {
537
+ consumed: [{
538
+ seq: chunk[0].change.seq,
539
+ counted: true
540
+ }],
541
+ tailSkipped: false,
542
+ fallback: false
543
+ };
544
+ }
545
+ const mid = Math.ceil(chunk.length / 2);
546
+ const left = await this.sendPushChunk(chunk.slice(0, mid));
547
+ if (left.fallback || left.tailSkipped || left.error) return left;
548
+ const right = await this.sendPushChunk(chunk.slice(mid));
549
+ return {
550
+ consumed: [...left.consumed, ...right.consumed],
551
+ tailSkipped: right.tailSkipped,
552
+ fallback: right.fallback,
553
+ error: right.error
554
+ };
555
+ }
556
+ throw e;
557
+ }
558
+ if (!Array.isArray(result.results) || result.results.length !== chunk.length) throw new Error(`SyncEngine.push: batch results length ${result.results?.length ?? "?"} != chunk length ${chunk.length} (protocol violation)`);
559
+ const consumed = [];
560
+ for (let i = 0; i < result.results.length; i++) {
561
+ const r = result.results[i];
562
+ const change = chunk[i].change;
563
+ if (r.success) {
564
+ consumed.push({
565
+ seq: change.seq,
566
+ counted: true
567
+ });
568
+ continue;
569
+ }
570
+ if (r.code === "BATCH_TAIL_SKIPPED") return {
571
+ consumed,
572
+ tailSkipped: true,
573
+ fallback: false
574
+ };
575
+ if (r.conflict) {
576
+ await this.resolveConflict(change, r.conflict);
577
+ consumed.push({
578
+ seq: change.seq,
579
+ counted: true
580
+ });
581
+ continue;
582
+ }
583
+ return {
584
+ consumed,
585
+ tailSkipped: false,
586
+ fallback: false,
587
+ error: /* @__PURE__ */ new Error(`SyncEngine.push: batch entry failed at seq=${change.seq} path=${change.path}: ${r.error ?? r.code ?? r.status}`)
588
+ };
589
+ }
590
+ return {
591
+ consumed,
592
+ tailSkipped: false,
593
+ fallback: false
594
+ };
595
+ }
596
+ /**
597
+ * Decide whether THIS push routes large put-chunks through the upload plane
598
+ * (design §6.1 / decision 4). True only when (a) the adapter has the
599
+ * prepare/commit/uploadObject surface, (b) the server advertises grant
600
+ * capability (memoized per instance — Node/old-server/signer-less → false →
601
+ * never), and (c) the put backlog meets the conservative threshold
602
+ * (≥ MIN_ENTRIES puts, or ≥ MIN_BYTES) via a cheap bounded peek.
603
+ */
604
+ async shouldUseUploadPlane(cursor) {
605
+ if (typeof this.cloudAfs.prepareUpload !== "function" || typeof this.cloudAfs.commitUpload !== "function" || typeof this.cloudAfs.uploadObject !== "function") return false;
606
+ if (this.uploadPlaneCapable === null) try {
607
+ this.uploadPlaneCapable = typeof this.cloudAfs.uploadGrantCapability === "function" ? await this.cloudAfs.uploadGrantCapability() : false;
608
+ } catch {
609
+ this.uploadPlaneCapable = false;
610
+ }
611
+ if (!this.uploadPlaneCapable) return false;
612
+ const puts = (await this.localChangelog.getUnpushed(cursor, AFS_DID_SPACE_UPLOAD_MIN_ENTRIES + 1)).filter((c) => c.op === "put");
613
+ if (puts.length >= AFS_DID_SPACE_UPLOAD_MIN_ENTRIES) return true;
614
+ return puts.reduce((sum, c) => sum + (c.size ?? 0), 0) >= AFS_DID_SPACE_UPLOAD_MIN_BYTES;
615
+ }
616
+ /**
617
+ * Upload-plane transport for one put chunk (design §6.1 / §8). Mirrors a
618
+ * `batchWrite` call but moves content OUT of the worker:
619
+ * 1. prepare-upload (reuses each row's local `cid` — never re-hashed);
620
+ * 2. out-of-band PUT each grant (bounded concurrency);
621
+ * 3. commit-upload ONLY the entries whose PUT returned 200 (commit-after-
622
+ * 200, decision 2 — the sole dangling defense). PUT-failed entries get a
623
+ * per-entry failure so the cursor pins there for retry next round.
624
+ * Returns the SAME `RpcBatchResult` shape `sendPushChunk` consumes. If a grant
625
+ * is unexpectedly absent (capability was advertised but a row got none), bail
626
+ * via `BatchUnsupportedError` → the engine reverts to the legacy path.
627
+ */
628
+ /**
629
+ * Application metadata for a push entry, derived from its own bytes via the
630
+ * optional provider hook (did-space-metadata G3). Returns undefined when the
631
+ * provider exposes none — so the engine emits no metadata and the wire stays
632
+ * pre-G3 byte-identical.
633
+ */
634
+ async readAppMeta(path, bytes) {
635
+ const read = this.provider.readApplicationMetadata;
636
+ if (!read) return void 0;
637
+ try {
638
+ const meta = await read.call(this.provider, path, bytes);
639
+ return meta && Object.keys(meta).length > 0 ? meta : void 0;
640
+ } catch {
641
+ return;
642
+ }
643
+ }
644
+ async uploadPlaneWriteChunk(chunk) {
645
+ const cids = await Promise.all(chunk.map(({ bytes }) => require_cid.computeCID(bytes)));
646
+ const toEntry = (change, size, cid) => {
647
+ const wirePath = normaliseWirePath(change.path);
648
+ const mimeType = (0, _aigne_afs_provider_utils.detectMimeType)(wirePath);
649
+ return {
650
+ path: wirePath,
651
+ cid,
652
+ size,
653
+ ...mimeType ? { mimeType } : {},
654
+ ...change.old_cid ? { ifMatch: change.old_cid } : {},
655
+ deviceId: change.device_id,
656
+ mtime: change.mtime
657
+ };
658
+ };
659
+ const inlineCandidate = (bytes, mimeType) => {
660
+ if (bytes.length > require_inline_content.DEFAULT_INLINE_THRESHOLD) return void 0;
661
+ return require_inline_content.decodeInlineText(bytes, mimeType) ?? void 0;
662
+ };
663
+ const prep = await this.cloudAfs.prepareUpload(chunk.map(({ change, bytes }, i) => toEntry(change, bytes.length, cids[i])));
664
+ if (prep.entries.length !== chunk.length || prep.entries.some((e) => !e.grant)) throw new require_remote_afs.BatchUnsupportedError();
665
+ const putOk = new Array(chunk.length).fill(false);
666
+ await this.boundedAll(chunk.length, AFS_DID_SPACE_UPLOAD_PUT_CONCURRENCY, async (i) => {
667
+ putOk[i] = await this.cloudAfs.uploadObject(prep.entries[i].grant, chunk[i].bytes) === 200;
668
+ });
669
+ const commitEntries = [];
670
+ const commitIndex = /* @__PURE__ */ new Map();
671
+ for (const [i, { change, bytes }] of chunk.entries()) {
672
+ if (!putOk[i]) continue;
673
+ commitIndex.set(i, commitEntries.length);
674
+ const base = toEntry(change, bytes.length, cids[i]);
675
+ const content = inlineCandidate(bytes, base.mimeType);
676
+ const metadata = await this.readAppMeta(change.path, bytes);
677
+ commitEntries.push({
678
+ ...base,
679
+ ...content !== void 0 ? { content } : {},
680
+ ...metadata ? { metadata } : {}
681
+ });
682
+ }
683
+ const commit = commitEntries.length > 0 ? await this.cloudAfs.commitUpload(prep.sessionId, commitEntries) : {
684
+ results: [],
685
+ succeeded: 0,
686
+ failed: 0
687
+ };
688
+ const results = chunk.map(({ change }, i) => {
689
+ const ci = commitIndex.get(i);
690
+ if (ci !== void 0) return commit.results[ci] ?? {
691
+ path: normaliseWirePath(change.path),
692
+ success: false,
693
+ status: 500,
694
+ error: "missing commit result"
695
+ };
696
+ return {
697
+ path: normaliseWirePath(change.path),
698
+ success: false,
699
+ status: 503,
700
+ code: "UPLOAD_PUT_FAILED",
701
+ error: "presigned PUT did not return 200"
702
+ };
703
+ });
704
+ let succeeded = 0;
705
+ for (const r of results) if (r.success) succeeded++;
706
+ return {
707
+ results,
708
+ succeeded,
709
+ failed: results.length - succeeded
710
+ };
711
+ }
712
+ /** Run `fn(0..n-1)` with at most `limit` in flight (push-side PUT fan-out). */
713
+ async boundedAll(n, limit, fn) {
714
+ let next = 0;
715
+ const workers = Array.from({ length: Math.min(limit, n) }, async () => {
716
+ while (next < n) await fn(next++);
717
+ });
718
+ await Promise.all(workers);
719
+ }
720
+ /**
721
+ * Upload a single change row. Split from push() so both the normal
722
+ * loop and future retry paths can call it.
723
+ *
724
+ * On 409 conflict, delegates to {@link resolveConflict}, which decides
725
+ * by LWW + tiebreak whether to force-overwrite the cloud or adopt the
726
+ * cloud's version locally. Either way, after resolveConflict returns,
727
+ * the row is considered "handled" and the push cursor advances past it.
728
+ */
729
+ async pushOne(change) {
730
+ const wirePath = normaliseWirePath(change.path);
731
+ if (change.op === "put") {
732
+ if (change.cid === null) throw new Error(`SyncEngine.push: put row with null cid at seq=${change.seq} path=${change.path}`);
733
+ const bytes = await this.provider.readBytes(change.path);
734
+ if (bytes === null) return;
735
+ const appMeta = await this.readAppMeta(change.path, bytes);
736
+ try {
737
+ await this.cloudAfs.write(wirePath, {
738
+ content: bytes,
739
+ meta: {
740
+ ...appMeta,
741
+ extra: {
742
+ contentCid: change.cid,
743
+ deviceId: change.device_id,
744
+ mtime: change.mtime,
745
+ ifMatch: change.old_cid ?? void 0
746
+ }
747
+ }
748
+ });
749
+ } catch (e) {
750
+ if (e instanceof _aigne_afs.AFSConflictError) {
751
+ await this.resolveConflict(change, {
752
+ currentCid: e.currentCid,
753
+ currentMtime: e.currentMtime,
754
+ currentDeviceId: e.currentDeviceId
755
+ });
756
+ return;
757
+ }
758
+ throw e;
759
+ }
760
+ } else if (change.op === "delete") try {
761
+ await this.cloudAfs.delete(wirePath, { meta: {
762
+ deviceId: change.device_id,
763
+ mtime: change.mtime,
764
+ ifMatch: change.old_cid ?? void 0
765
+ } });
766
+ } catch (e) {
767
+ if (e instanceof _aigne_afs.AFSConflictError) {
768
+ await this.resolveConflict(change, {
769
+ currentCid: e.currentCid,
770
+ currentMtime: e.currentMtime,
771
+ currentDeviceId: e.currentDeviceId
772
+ });
773
+ return;
774
+ }
775
+ throw e;
776
+ }
777
+ else throw new Error(`SyncEngine.push: unexpected op=${change.op} at seq=${change.seq}`);
778
+ }
779
+ /**
780
+ * Resolve a push-side 409 conflict via Last-Write-Wins (LWW).
781
+ *
782
+ * Decision matrix (see `native-sync-design.md` G11 for the full 7-row
783
+ * matrix and rationale):
784
+ *
785
+ * 1. `localMtime > cloudMtime` → **local wins**: force-overwrite the
786
+ * cloud (resend without `If-Match`). The server still validates
787
+ * `X-Content-CID`, so a corrupted upload still fails.
788
+ *
789
+ * 2. `localMtime < cloudMtime` → **cloud wins**: adopt the cloud's
790
+ * version locally via `provider.internalWrite/Delete` with the
791
+ * ORIGIN deviceId from the conflict response. Echo-loop Layer II.
792
+ *
793
+ * 3. `localMtime === cloudMtime` → **tiebreak by CID lexicographic
794
+ * order** (round 7 decision). The larger cid wins. delete ops
795
+ * treat their cid as the empty string, so a `put` with non-empty
796
+ * cid always beats a `delete` at the same mtime.
797
+ *
798
+ * Special cases handled here:
799
+ * - delete vs delete (both null cid) → idempotent no-op (defensive;
800
+ * in practice the server's delete handler turns DELETE 404 into
801
+ * success and the conflict never reaches here).
802
+ * - same mtime + same cid → idempotent natural convergence (the
803
+ * local-wins path is taken but the resend produces the same cid
804
+ * the cloud already has).
805
+ *
806
+ * mtime skew defense: G5.c (Task 3.3) rejects writes with
807
+ * `mtime > serverNow + 5min` at the header layer, so a single device
808
+ * with a wildly bad clock cannot poison the LWW decision globally.
809
+ */
810
+ async resolveConflict(change, resp) {
811
+ if (resp.currentMtime === null) throw new ConflictResolutionError(change, "missing currentMtime in conflict response");
812
+ const localMtime = change.mtime;
813
+ const cloudMtime = resp.currentMtime;
814
+ if (change.op === "delete" && resp.currentCid === null) return;
815
+ const localCid = change.cid ?? "";
816
+ const cloudCid = resp.currentCid ?? "";
817
+ if (localMtime > cloudMtime || localMtime === cloudMtime && localCid > cloudCid) {
818
+ const wirePath = normaliseWirePath(change.path);
819
+ if (change.op === "put") {
820
+ const bytes = await this.provider.readBytes(change.path);
821
+ if (bytes === null) return;
822
+ const appMeta = await this.readAppMeta(change.path, bytes);
823
+ await this.cloudAfs.write(wirePath, {
824
+ content: bytes,
825
+ meta: {
826
+ ...appMeta,
827
+ extra: {
828
+ contentCid: change.cid,
829
+ deviceId: change.device_id,
830
+ mtime: change.mtime
831
+ }
832
+ }
833
+ });
834
+ } else await this.cloudAfs.delete(wirePath, { meta: {
835
+ deviceId: change.device_id,
836
+ mtime: change.mtime
837
+ } });
838
+ } else {
839
+ const wirePath = normaliseWirePath(change.path);
840
+ const originDeviceId = resp.currentDeviceId ?? "unknown";
841
+ if (resp.currentCid !== null) {
842
+ let bytes;
843
+ try {
844
+ bytes = (await this.cloudAfs.read(`/.objects/${resp.currentCid}`)).data?.content;
845
+ } catch (e) {
846
+ if (e instanceof Error && e.name === "AbortError") throw e;
847
+ throw new CurrentObjectMissingError(wirePath, resp.currentCid, "conflict");
848
+ }
849
+ if (bytes === void 0) throw new CurrentObjectMissingError(wirePath, resp.currentCid, "conflict");
850
+ await this.provider.internalWrite(wirePath, {
851
+ content: bytes,
852
+ deviceId: originDeviceId,
853
+ mtime: cloudMtime,
854
+ metadata: await this.readAppMeta(wirePath, bytes)
855
+ });
856
+ } else await this.provider.internalDelete(wirePath, {
857
+ deviceId: originDeviceId,
858
+ mtime: cloudMtime
859
+ });
860
+ }
861
+ }
862
+ /**
863
+ * Fetch and apply incoming changes from the cloud. Loops until either
864
+ * the server reports `hasMore === false`, the deadline passes, or the
865
+ * abort signal fires. On 409 Compacted, delegates to fullResync().
866
+ *
867
+ * Apply order: sequential in seq order within a batch (SQLite
868
+ * integrity + single origin-deviceId per row). Content fetching is
869
+ * parallel up to `PULL_FETCH_CONCURRENCY`.
870
+ */
871
+ async pull(opts) {
872
+ const initialCloudSeq = (await this.syncStateStore.load()).cloudSeq;
873
+ let cloudCursor = initialCloudSeq > 0 ? String(initialCloudSeq) : void 0;
874
+ let pulled = 0;
875
+ try {
876
+ while (true) {
877
+ if (opts.signal?.aborted) return {
878
+ pulled,
879
+ fullResynced: false
880
+ };
881
+ if (Date.now() >= opts.deadline) return {
882
+ pulled,
883
+ fullResynced: false
884
+ };
885
+ let listResult;
886
+ try {
887
+ listResult = await this.cloudAfs.list(".changelog", {
888
+ cursor: cloudCursor ?? "0",
889
+ limit: PULL_BATCH_LIMIT
890
+ });
891
+ } catch (e) {
892
+ if (e instanceof _aigne_afs.AFSCompactedError) {
893
+ const r = await this.fullResync({
894
+ signal: opts.signal,
895
+ deadline: opts.deadline
896
+ });
897
+ return {
898
+ pulled: pulled + r.pulled,
899
+ fullResynced: true
900
+ };
901
+ }
902
+ throw e;
903
+ }
904
+ const allChanges = listResult.data.map((entry) => {
905
+ const extra = entry.meta?.extra ?? {};
906
+ return {
907
+ seq: Number(extra.seq ?? 0),
908
+ originalPath: String(extra.originalPath ?? ""),
909
+ op: String(extra.op ?? ""),
910
+ cid: typeof extra.cid === "string" ? extra.cid : null,
911
+ oldCid: typeof extra.oldCid === "string" ? extra.oldCid : null,
912
+ size: typeof extra.size === "number" ? extra.size : null,
913
+ mtime: Number(extra.mtime ?? 0),
914
+ deviceId: String(extra.deviceId ?? "")
915
+ };
916
+ });
917
+ const hasMore = listResult.meta?.hasMore ?? false;
918
+ for (let i = 1; i < allChanges.length; i++) if (allChanges[i].seq <= allChanges[i - 1].seq) throw new Error("SyncEngine.pull: cloud returned out-of-order changes");
919
+ const foreignChanges = allChanges.filter((c) => c.deviceId !== this.selfDeviceId);
920
+ const putRows = foreignChanges.filter((c) => c.op === "put" && c.cid !== null);
921
+ const cidMap = /* @__PURE__ */ new Map();
922
+ await this.fetchContent(putRows, cidMap, opts);
923
+ const lastCidForPath = /* @__PURE__ */ new Map();
924
+ for (const c of foreignChanges) lastCidForPath.set(normaliseAfsPath(c.originalPath), c.op === "put" ? c.cid : null);
925
+ for (const change of foreignChanges) {
926
+ if (opts.signal?.aborted) return {
927
+ pulled,
928
+ fullResynced: false
929
+ };
930
+ const wirePath = normaliseAfsPath(change.originalPath);
931
+ if (change.op === "put") {
932
+ if (change.cid === null) continue;
933
+ const bytes = cidMap.get(change.cid);
934
+ if (bytes === void 0) {
935
+ if (classifyMissingObject(wirePath, change.cid, lastCidForPath) === "current") throw new CurrentObjectMissingError(wirePath, change.cid, "latest-put");
936
+ log.warn(`SyncEngine.pull: skipping superseded seq=${change.seq} path=${wirePath} (cid ${change.cid} reclaimed)`);
937
+ cloudCursor = String(change.seq);
938
+ continue;
939
+ }
940
+ await this.provider.internalWrite(wirePath, {
941
+ content: bytes,
942
+ deviceId: change.deviceId,
943
+ mtime: change.mtime,
944
+ metadata: await this.readAppMeta(wirePath, bytes)
945
+ });
946
+ } else if (change.op === "delete") await this.provider.internalDelete(wirePath, {
947
+ deviceId: change.deviceId,
948
+ mtime: change.mtime
949
+ });
950
+ cloudCursor = String(change.seq);
951
+ pulled += 1;
952
+ }
953
+ if (foreignChanges.length === 0 && allChanges.length > 0) cloudCursor = listResult.meta?.cursor ?? cloudCursor;
954
+ if (!hasMore) break;
955
+ }
956
+ } finally {
957
+ if (cloudCursor !== void 0) {
958
+ const numericCursor = Number(cloudCursor);
959
+ if (Number.isFinite(numericCursor) && numericCursor > 0) await this.syncStateStore.save({ cloudSeq: numericCursor });
960
+ }
961
+ }
962
+ return {
963
+ pulled,
964
+ fullResynced: false
965
+ };
966
+ }
967
+ /**
968
+ * Fetch content bytes for a batch of `put` rows in parallel, bounded
969
+ * by {@link PULL_FETCH_CONCURRENCY}. Results land in `cidMap`;
970
+ * individual fetch failures (404, cid mismatch) are logged and the
971
+ * offending cid is omitted so the caller's serial apply pass can
972
+ * detect the gap and skip.
973
+ */
974
+ /**
975
+ * Content fetch dispatcher (P4): chunked `batchRead(optional:true)` when
976
+ * the adapter has it (≤20 cids and ≤2MB ESTIMATED bytes per chunk — size
977
+ * from the changelog rows), single concurrent reads otherwise. Tail-
978
+ * skipped cids are simply re-queued for the next chunk (they were never
979
+ * read); `missing:true` cids stay out of the map so the apply loop's
980
+ * §4.7.2 current/superseded classification is untouched.
981
+ */
982
+ async fetchContent(rows, cidMap, opts) {
983
+ const planeSatisfied = await this.fillBytesViaDownloadPlane(rows.filter((r) => r.cid !== null), cidMap, opts);
984
+ const remaining = planeSatisfied.size === 0 ? rows : rows.filter((r) => r.cid === null || !planeSatisfied.has(r.cid));
985
+ if (this.batchFallback || typeof this.cloudAfs.batchRead !== "function") return this.fetchContentConcurrent(remaining, cidMap, opts);
986
+ const sizeByCid = /* @__PURE__ */ new Map();
987
+ for (const r of remaining) if (r.cid !== null && !sizeByCid.has(r.cid)) sizeByCid.set(r.cid, r.size ?? 0);
988
+ const queue = Array.from(sizeByCid.keys());
989
+ while (queue.length > 0) {
990
+ if (opts.signal?.aborted) return;
991
+ const chunk = [];
992
+ let estBytes = 0;
993
+ while (queue.length > 0 && chunk.length < BATCH_READ_MAX_ENTRIES) {
994
+ const cid = queue[0];
995
+ const est = sizeByCid.get(cid) ?? 0;
996
+ if (chunk.length > 0 && estBytes + est > BATCH_READ_MAX_BYTES) break;
997
+ chunk.push(cid);
998
+ estBytes += est;
999
+ queue.shift();
1000
+ }
1001
+ let result;
1002
+ try {
1003
+ result = await this.cloudAfs.batchRead(chunk.map((cid) => ({
1004
+ path: `/.objects/${cid}`,
1005
+ optional: true
1006
+ })));
1007
+ } catch (e) {
1008
+ if (e instanceof require_remote_afs.BatchUnsupportedError) {
1009
+ this.batchFallback = true;
1010
+ const remaining$1 = [...chunk, ...queue].map((cid) => ({
1011
+ cid,
1012
+ size: sizeByCid.get(cid) ?? null
1013
+ }));
1014
+ return this.fetchContentConcurrent(remaining$1, cidMap, opts);
1015
+ }
1016
+ if (e instanceof require_remote_afs.BatchOverBudgetError && chunk.length > 1) {
1017
+ const half = Math.ceil(chunk.length / 2);
1018
+ for (const cid of chunk.slice(0, half)) await this.fetchOneObject(cid, cidMap, opts);
1019
+ queue.unshift(...chunk.slice(half));
1020
+ continue;
1021
+ }
1022
+ throw e;
1023
+ }
1024
+ let skippedTail = false;
1025
+ for (let i = 0; i < result.results.length; i++) {
1026
+ const r = result.results[i];
1027
+ const cid = chunk[i];
1028
+ if (r.success) {
1029
+ const content = r.data?.content;
1030
+ const bytes = content instanceof Uint8Array ? content : typeof content === "string" ? new TextEncoder().encode(content) : void 0;
1031
+ if (bytes) {
1032
+ const actualCid = await require_cid.computeCID(bytes);
1033
+ if (actualCid !== cid) log.warn(`SyncEngine.pull: cid mismatch for ${cid}: got ${actualCid}`);
1034
+ else cidMap.set(cid, bytes);
1035
+ }
1036
+ continue;
1037
+ }
1038
+ if (r.code === "BATCH_TAIL_SKIPPED") {
1039
+ if (i === 0) for (const c of chunk.slice(i)) await this.fetchOneObject(c, cidMap, opts);
1040
+ else queue.unshift(...chunk.slice(i));
1041
+ skippedTail = true;
1042
+ break;
1043
+ }
1044
+ }
1045
+ if (skippedTail) continue;
1046
+ }
1047
+ }
1048
+ /** Single `.objects/{cid}` read with the same soft-failure semantics. */
1049
+ async fetchOneObject(cid, cidMap, opts) {
1050
+ if (opts.signal?.aborted) return;
1051
+ try {
1052
+ const bytes = (await this.cloudAfs.read(`/.objects/${cid}`)).data?.content;
1053
+ if (bytes) {
1054
+ const actualCid = await require_cid.computeCID(bytes);
1055
+ if (actualCid === cid) cidMap.set(cid, bytes);
1056
+ else log.warn(`SyncEngine.pull: cid mismatch for ${cid}: got ${actualCid}`);
1057
+ }
1058
+ } catch (e) {
1059
+ if (e instanceof Error && e.name === "AbortError") throw e;
1060
+ log.warn(`SyncEngine.pull: read .objects/${cid} failed: ${e instanceof Error ? e.message : String(e)}`);
1061
+ }
1062
+ }
1063
+ async fetchContentConcurrent(rows, cidMap, opts) {
1064
+ const uniqueCids = /* @__PURE__ */ new Set();
1065
+ for (const r of rows) if (r.cid !== null) uniqueCids.add(r.cid);
1066
+ const cids = Array.from(uniqueCids);
1067
+ let index = 0;
1068
+ const worker = async () => {
1069
+ while (index < cids.length) {
1070
+ if (opts.signal?.aborted) return;
1071
+ const cid = cids[index++];
1072
+ try {
1073
+ const bytes = (await this.cloudAfs.read(`/.objects/${cid}`)).data?.content;
1074
+ if (bytes) {
1075
+ const actualCid = await require_cid.computeCID(bytes);
1076
+ if (actualCid !== cid) log.warn(`SyncEngine.pull: cid mismatch for ${cid}: got ${actualCid}`);
1077
+ else cidMap.set(cid, bytes);
1078
+ }
1079
+ } catch (e) {
1080
+ if (e instanceof Error && e.name === "AbortError") throw e;
1081
+ log.warn(`SyncEngine.pull: read .objects/${cid} failed: ${e instanceof Error ? e.message : String(e)}`);
1082
+ }
1083
+ }
1084
+ };
1085
+ const concurrency = Math.max(1, Math.min(PULL_FETCH_CONCURRENCY, cids.length));
1086
+ const workers = [];
1087
+ for (let i = 0; i < concurrency; i++) workers.push(worker());
1088
+ await Promise.all(workers);
1089
+ }
1090
+ /**
1091
+ * Whether the download plane can be used RIGHT NOW: the adapter speaks the
1092
+ * methods, the server advertised the INDEPENDENT capability (memoized per
1093
+ * instance, §7.3), and this session hasn't latched a 401/403 fallback.
1094
+ */
1095
+ async downloadPlaneAvailable() {
1096
+ if (this.downloadFallback) return false;
1097
+ if (typeof this.cloudAfs.prepareDownload !== "function" || typeof this.cloudAfs.downloadObject !== "function") return false;
1098
+ if (this.downloadPlaneCapable === null) try {
1099
+ this.downloadPlaneCapable = typeof this.cloudAfs.downloadGrantCapability === "function" ? await this.cloudAfs.downloadGrantCapability() : false;
1100
+ } catch {
1101
+ this.downloadPlaneCapable = false;
1102
+ }
1103
+ return this.downloadPlaneCapable === true;
1104
+ }
1105
+ /**
1106
+ * Fetch LARGE objects (size ≥ {@link DOWNLOAD_PLANE_MIN_BYTES}) via the
1107
+ * download plane — prepare-download → bounded-concurrency direct R2 GET →
1108
+ * computeCID → `bytesMap`. Bytes leave R2 directly (never through the worker /
1109
+ * base64). Returns the SET of cids it satisfied; the caller routes the rest
1110
+ * (small objects, Node `grant:null`, forbidden, 404, post-fallback) through
1111
+ * batchRead — so a true miss still raises the existing fail-closed path.
1112
+ *
1113
+ * fail-closed: a byte string whose `computeCID` ≠ the requested cid (R2 served
1114
+ * tampered/other bytes) is DROPPED, never written (§8.1). A 401/403 latches
1115
+ * `downloadFallback` for the whole session — at most one wall-bang (§7.3).
1116
+ */
1117
+ async fillBytesViaDownloadPlane(entries, bytesMap, opts) {
1118
+ const satisfied = /* @__PURE__ */ new Set();
1119
+ if (!await this.downloadPlaneAvailable()) return satisfied;
1120
+ const bigCids = [];
1121
+ const seen = /* @__PURE__ */ new Set();
1122
+ for (const e of entries) {
1123
+ if ((e.size ?? 0) < DOWNLOAD_PLANE_MIN_BYTES) continue;
1124
+ if (seen.has(e.cid)) continue;
1125
+ seen.add(e.cid);
1126
+ bigCids.push(e.cid);
1127
+ }
1128
+ if (bigCids.length === 0) return satisfied;
1129
+ for (let i = 0; i < bigCids.length; i += require_download_actions.DOWNLOAD_MAX_ENTRIES) {
1130
+ if (opts.signal?.aborted || this.downloadFallback) break;
1131
+ const batch = bigCids.slice(i, i + require_download_actions.DOWNLOAD_MAX_ENTRIES);
1132
+ let prep;
1133
+ try {
1134
+ prep = await this.cloudAfs.prepareDownload(batch.map((cid) => ({ cid })));
1135
+ } catch (e) {
1136
+ if (e instanceof require_remote_afs.BatchUnsupportedError) {
1137
+ this.downloadPlaneCapable = false;
1138
+ return satisfied;
1139
+ }
1140
+ throw e;
1141
+ }
1142
+ const grantByCid = /* @__PURE__ */ new Map();
1143
+ for (const pe of prep.entries) if (pe.status === "ok" && pe.grant) grantByCid.set(pe.cid, pe.grant);
1144
+ const grantedCids = batch.filter((c) => grantByCid.has(c));
1145
+ await this.boundedAll(grantedCids.length, this.downloadGetConcurrency, async (j) => {
1146
+ if (opts.signal?.aborted || this.downloadFallback) return;
1147
+ const cid = grantedCids[j];
1148
+ try {
1149
+ const bytes = await this.cloudAfs.downloadObject(grantByCid.get(cid));
1150
+ const actualCid = await require_cid.computeCID(bytes);
1151
+ if (actualCid === cid) {
1152
+ bytesMap.set(cid, bytes);
1153
+ satisfied.add(cid);
1154
+ } else log.warn(`SyncEngine.pull: download-plane cid mismatch for ${cid}: got ${actualCid}`);
1155
+ } catch (e) {
1156
+ if (e instanceof require_remote_afs.DownloadForbiddenError) {
1157
+ this.downloadFallback = true;
1158
+ return;
1159
+ }
1160
+ if (e instanceof Error && e.name === "AbortError") throw e;
1161
+ }
1162
+ });
1163
+ }
1164
+ return satisfied;
1165
+ }
1166
+ /**
1167
+ * First-sync / post-compaction recovery path. Fetches the full manifest
1168
+ * and reconciles the local store against it, skipping paths the local
1169
+ * changelog still has unpushed changes for (invariant I1).
1170
+ *
1171
+ * ORDERING IS CRITICAL — `pendingPaths` MUST be collected BEFORE the
1172
+ * manifest fetch so a delete written between pendingPaths collection
1173
+ * and apply does not get reverted by the manifest restoration.
1174
+ */
1175
+ async fullResync(opts) {
1176
+ const unpushed = await this.localChangelog.getUnpushed(0, Number.MAX_SAFE_INTEGER);
1177
+ const pendingPaths = new Set(unpushed.map((c) => c.path));
1178
+ if (opts.signal?.aborted) return { pulled: 0 };
1179
+ const manifestResult = await this.cloudAfs.list("", { maxDepth: -1 });
1180
+ if (manifestResult.meta?.truncated === true) throw new ManifestTooLargeError();
1181
+ const manifestEntries = manifestResult.data.map((entry) => {
1182
+ const extra = entry.meta?.extra ?? {};
1183
+ const cid = typeof extra.cid === "string" ? extra.cid : typeof entry.meta?.cid === "string" ? entry.meta.cid : "";
1184
+ return {
1185
+ path: entry.path,
1186
+ cid,
1187
+ mtime: typeof extra.mtime === "number" ? extra.mtime : 0,
1188
+ size: typeof entry.meta?.size === "number" ? entry.meta.size : 0
1189
+ };
1190
+ });
1191
+ const manifestSeq = Number(manifestResult.meta?.cursor ?? 0);
1192
+ const manifestPathSet = /* @__PURE__ */ new Set();
1193
+ for (const e of manifestEntries) manifestPathSet.add(normaliseToLocalPath(e.path));
1194
+ let pulled = 0;
1195
+ const applicable = manifestEntries.filter((entry) => !pendingPaths.has(normaliseToLocalPath(entry.path)));
1196
+ const planeBytes = /* @__PURE__ */ new Map();
1197
+ const planeSatisfied = await this.fillBytesViaDownloadPlane(applicable.map((e) => ({
1198
+ cid: e.cid,
1199
+ size: e.size
1200
+ })), planeBytes, opts);
1201
+ for (const entry of applicable) {
1202
+ if (opts.signal?.aborted) return { pulled };
1203
+ if (Date.now() >= opts.deadline) return { pulled };
1204
+ const bytes = planeBytes.get(entry.cid);
1205
+ if (!bytes) continue;
1206
+ await this.provider.internalWrite(normaliseAfsPath(entry.path), {
1207
+ content: bytes,
1208
+ deviceId: this.selfDeviceId,
1209
+ mtime: entry.mtime,
1210
+ metadata: await this.readAppMeta(normaliseAfsPath(entry.path), bytes)
1211
+ });
1212
+ pulled += 1;
1213
+ }
1214
+ const viaBatch = planeSatisfied.size === 0 ? applicable : applicable.filter((e) => !planeSatisfied.has(e.cid));
1215
+ const useBatchRead = () => !this.batchFallback && typeof this.cloudAfs.batchRead === "function";
1216
+ for (let offset = 0; offset < viaBatch.length;) {
1217
+ if (opts.signal?.aborted) return { pulled };
1218
+ if (Date.now() >= opts.deadline) return { pulled };
1219
+ const chunk = [];
1220
+ while (offset < viaBatch.length && chunk.length < (useBatchRead() ? BATCH_READ_MAX_ENTRIES : 1)) {
1221
+ chunk.push(viaBatch[offset]);
1222
+ offset++;
1223
+ }
1224
+ const bytesByCid = /* @__PURE__ */ new Map();
1225
+ if (useBatchRead() && chunk.length > 0) {
1226
+ let result;
1227
+ try {
1228
+ result = await this.cloudAfs.batchRead(chunk.map((entry) => ({
1229
+ path: `/.objects/${entry.cid}`,
1230
+ optional: true
1231
+ })));
1232
+ } catch (e) {
1233
+ if (e instanceof require_remote_afs.BatchUnsupportedError) {
1234
+ this.batchFallback = true;
1235
+ offset -= chunk.length;
1236
+ continue;
1237
+ }
1238
+ throw e;
1239
+ }
1240
+ for (let i = 0; i < result.results.length; i++) {
1241
+ const r = result.results[i];
1242
+ const entry = chunk[i];
1243
+ if (r.code === "BATCH_TAIL_SKIPPED") {
1244
+ offset -= chunk.length - i;
1245
+ break;
1246
+ }
1247
+ if (!r.success) throw new CurrentObjectMissingError(entry.path, entry.cid, "manifest");
1248
+ const content = r.data?.content;
1249
+ const bytes = content instanceof Uint8Array ? content : typeof content === "string" ? new TextEncoder().encode(content) : void 0;
1250
+ if (!bytes) throw new CurrentObjectMissingError(entry.path, entry.cid, "manifest");
1251
+ bytesByCid.set(entry.cid, bytes);
1252
+ }
1253
+ } else for (const entry of chunk) try {
1254
+ const content = (await this.cloudAfs.read(`/.objects/${entry.cid}`)).data?.content;
1255
+ if (!content) throw new CurrentObjectMissingError(entry.path, entry.cid, "manifest");
1256
+ bytesByCid.set(entry.cid, content);
1257
+ } catch (e) {
1258
+ if (e instanceof CurrentObjectMissingError) throw e;
1259
+ if (e instanceof Error && e.name === "AbortError") throw e;
1260
+ throw new CurrentObjectMissingError(entry.path, entry.cid, "manifest");
1261
+ }
1262
+ for (const entry of chunk) {
1263
+ if (opts.signal?.aborted) return { pulled };
1264
+ const bytes = bytesByCid.get(entry.cid);
1265
+ if (!bytes) continue;
1266
+ const wirePath = normaliseAfsPath(entry.path);
1267
+ await this.provider.internalWrite(wirePath, {
1268
+ content: bytes,
1269
+ deviceId: this.selfDeviceId,
1270
+ mtime: entry.mtime,
1271
+ metadata: await this.readAppMeta(wirePath, bytes)
1272
+ });
1273
+ pulled += 1;
1274
+ }
1275
+ }
1276
+ if (!opts.signal?.aborted) {
1277
+ const localPaths = await this.provider.listAllPaths();
1278
+ for (const p of localPaths) {
1279
+ if (opts.signal?.aborted) break;
1280
+ if (manifestPathSet.has(p)) continue;
1281
+ if (pendingPaths.has(p)) continue;
1282
+ await this.provider.internalDelete(normaliseAfsPath(p), { deviceId: this.selfDeviceId });
1283
+ }
1284
+ }
1285
+ if (manifestSeq > 0) await this.syncStateStore.save({ cloudSeq: manifestSeq });
1286
+ return { pulled };
1287
+ }
1288
+ };
1289
+ /**
1290
+ * Normalise an internal changelog path (no leading slash) to the AFS
1291
+ * wire path used by PUT/DELETE endpoints (always starts with `/`).
1292
+ */
1293
+ function normaliseWirePath(path) {
1294
+ return path.startsWith("/") ? path : `/${path}`;
1295
+ }
1296
+ /**
1297
+ * Same as {@link normaliseWirePath} but applied to paths that may come
1298
+ * from either the manifest (no leading slash) or a `.changelog` row's
1299
+ * `meta.extra.originalPath` (no leading slash either — the server stores
1300
+ * without leading slash). Always returns a `/`-prefixed path suitable
1301
+ * for `provider.internalWrite/Delete`.
1302
+ */
1303
+ function normaliseAfsPath(path) {
1304
+ return path.startsWith("/") ? path : `/${path}`;
1305
+ }
1306
+ /**
1307
+ * Inverse of {@link normaliseAfsPath}: strip the leading slash so the
1308
+ * path matches the `entries.path` / `changelog.path` column shape
1309
+ * (used by pendingPaths lookups).
1310
+ */
1311
+ function normaliseToLocalPath(path) {
1312
+ return path.startsWith("/") ? path.slice(1) : path;
1313
+ }
1314
+
1315
+ //#endregion
1316
+ exports.ConflictResolutionError = ConflictResolutionError;
1317
+ exports.CurrentObjectMissingError = CurrentObjectMissingError;
1318
+ exports.PULL_BATCH_LIMIT = PULL_BATCH_LIMIT;
1319
+ exports.PULL_FETCH_CONCURRENCY = PULL_FETCH_CONCURRENCY;
1320
+ exports.PUSH_BATCH_LIMIT = PUSH_BATCH_LIMIT;
1321
+ exports.PUSH_PROGRESS_FLUSH_INTERVAL = PUSH_PROGRESS_FLUSH_INTERVAL;
1322
+ exports.SYNC_WALL_CLOCK_BUDGET_MS = SYNC_WALL_CLOCK_BUDGET_MS;
1323
+ exports.SyncEngine = SyncEngine;
1324
+ exports.classifyMissingObject = classifyMissingObject;