@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,1094 @@
1
+ import { CHANGELOG_INDEX_SQL, CHANGELOG_TABLE_SQL, PREFIX_WATERMARK_BUMP_SQL, PREFIX_WATERMARK_READ_SQL, PREFIX_WATERMARK_TABLE_SQL, SCOPE_META_FILE_COUNT_DELETE_ONE_SQL, SCOPE_META_FILE_COUNT_DELETE_RANGE_SQL, SCOPE_META_FILE_COUNT_TRIGGER_SQL, SCOPE_META_TABLE_SQL, SCOPE_META_TOTAL_BYTES_COLUMN_SQL, SCOPE_META_TOTAL_BYTES_DELETE_ONE_SQL, SCOPE_META_TOTAL_BYTES_DELETE_RANGE_SQL, SCOPE_META_TOTAL_BYTES_TRIGGER_SQL, watermarkPrefix, watermarkWritePrefix } from "./changelog-schema.mjs";
2
+ import { resolveInlineConfig, resolveInlineContent } from "./inline-content.mjs";
3
+ import { resolveParentDirStamp } from "./timestamp.mjs";
4
+ import { STORAGE_QUOTA_EXCEEDED_CODE, buildKeysetCursor, buildOrderedQueryParts, parseTreeCursor, parseTreeEntryMetadata, serializeTreeEntryMetadata, storageQuotaError, treeEntryMetadataEqual } from "./tree-index.mjs";
5
+ import { buildChildrenFrom, buildTreeQueryResultFromSql, buildTreeQueryStatements, mapAggregateRows } from "./tree-query.mjs";
6
+ import { ENTRIES_CONTENT_TYPE_COLUMN_SQL, ENTRIES_CONTENT_TYPE_INDEX_DROP_SQL, ENTRIES_DEDUP_INDEX_SQL, ENTRIES_DEDUP_KEY_COLUMN_SQL, ENTRIES_DEDUP_SCOPE_COLUMN_SQL, ENTRIES_INDEX_SQL, ENTRIES_INLINE_CONTENT_COLUMN_SQL, ENTRIES_METADATA_COLUMN_SQL, ENTRIES_TABLE_SQL } from "./d1-schema.mjs";
7
+ import { validateDeviceId } from "./changelog-write.mjs";
8
+ import { prefixRange } from "./prefix-range.mjs";
9
+ import { DIRTY_PATH_ACK_SQL, DIRTY_PATH_INDEX_SQL, DIRTY_PATH_LIST_SQL, DIRTY_PATH_MARK_ERROR_SQL, DIRTY_PATH_TABLE_SQL, DIRTY_PATH_TRIGGER_SQL } from "./dirty-path-schema.mjs";
10
+
11
+ //#region src/d1-tree-index.ts
12
+ /**
13
+ * D1TreeIndex — D1 (SQLite) implementation of TreeIndex.
14
+ *
15
+ * Uses the `entries` table (v2 schema) with parent/name/kind columns
16
+ * for efficient directory operations. WITHOUT ROWID for direct PK lookup.
17
+ *
18
+ * Each instance is scoped to a (scope) partition.
19
+ */
20
+ /** Extract parent directory path from a file/dir path */
21
+ function parentOf(path) {
22
+ if (!path) return "";
23
+ const clean = path.endsWith("/") ? path.slice(0, -1) : path;
24
+ const lastSlash = clean.lastIndexOf("/");
25
+ if (lastSlash <= 0) return "";
26
+ return clean.slice(0, lastSlash + 1);
27
+ }
28
+ /** Extract basename from a file/dir path */
29
+ function nameOf(path) {
30
+ if (!path) return "";
31
+ const clean = path.endsWith("/") ? path.slice(0, -1) : path;
32
+ const lastSlash = clean.lastIndexOf("/");
33
+ return lastSlash < 0 ? clean : clean.slice(lastSlash + 1);
34
+ }
35
+ /** Collect all ancestor directory paths from a file path (deepest first) */
36
+ function ancestorDirs(path) {
37
+ const dirs = [];
38
+ let current = path;
39
+ while (current.includes("/")) {
40
+ const slashIdx = current.lastIndexOf("/");
41
+ const dirPath = current.slice(0, slashIdx + 1);
42
+ if (!dirPath || dirPath === "/") break;
43
+ dirs.push(dirPath);
44
+ current = dirPath.slice(0, -1);
45
+ }
46
+ return dirs;
47
+ }
48
+ /**
49
+ * Parent-dir-chain statements (Rule R2, feed-sort-createdat design §5.2).
50
+ *
51
+ * `DIR_INSERT_IGNORE_SQL` is the legacy "scaffold a dir if absent" — used for
52
+ * the whole ancestor chain and for a direct parent whose write carries no
53
+ * application `createdAt`. `DIR_STAMP_UPSERT_SQL` additionally overwrites the
54
+ * existing dir row's ctime/mtime — used ONLY for the DIRECT parent when the
55
+ * write carries a valid createdAt, so the feed's `orderBy ctime` (which sorts
56
+ * the DIR row `e.ctime`) reflects the application createdAt. Both take the
57
+ * SAME 6 binds: (scope, path, parent, name, ctime, mtime).
58
+ */
59
+ const DIR_COLS = "(scope, path, parent, name, kind, size, ctime, mtime)";
60
+ const DIR_INSERT_IGNORE_SQL = `INSERT OR IGNORE INTO ds_entries ${DIR_COLS}
61
+ VALUES (?, ?, ?, ?, 'dir', 0, ?, ?)`;
62
+ const DIR_STAMP_UPSERT_SQL = `INSERT INTO ds_entries ${DIR_COLS}
63
+ VALUES (?, ?, ?, ?, 'dir', 0, ?, ?)
64
+ ON CONFLICT(scope, path) DO UPDATE SET ctime = excluded.ctime, mtime = excluded.mtime`;
65
+ /**
66
+ * Build the dir-chain statement for one `dirPath`: a Rule-R2 stamping UPSERT
67
+ * when `stamp` is non-null (the direct parent of a createdAt-bearing write),
68
+ * else the scaffold-only `INSERT OR IGNORE` at insert time `now`. Backend-
69
+ * agnostic `{ sql, args }`; the D1 caller wraps with `prepare().bind()`, the
70
+ * SQLite caller pushes it as-is.
71
+ */
72
+ function buildDirChainStmt(scope, dirPath, stamp, now) {
73
+ const sql = stamp ? DIR_STAMP_UPSERT_SQL : DIR_INSERT_IGNORE_SQL;
74
+ const { ctime, mtime } = stamp ?? {
75
+ ctime: now,
76
+ mtime: now
77
+ };
78
+ return {
79
+ sql,
80
+ args: [
81
+ scope,
82
+ dirPath,
83
+ parentOf(dirPath),
84
+ nameOf(dirPath),
85
+ ctime,
86
+ mtime
87
+ ]
88
+ };
89
+ }
90
+ /** Maximum file entries per scope (files only, excludes dirs) */
91
+ const MAX_ENTRIES_PER_SCOPE = 1e6;
92
+ /**
93
+ * `isReferencedMany` IN-shard size for D1 (design §7.2, Finding 5). D1 caps a
94
+ * query at 100 bound parameters. The three-way UNION binds each cid 3× plus
95
+ * scope per branch (3 total), and 2 more per branch (lo/hi) when prefix-local:
96
+ * - whole-scope: 3N + 3 ≤ 100 → N ≤ 32
97
+ * - prefix-local: 3N + 3 + 6 ≤ 100 → N ≤ 30
98
+ * 28 is the conservative single value covering both branches (never 30+).
99
+ */
100
+ const D1_MANY_SHARD_SIZE = 28;
101
+ var D1TreeIndex = class D1TreeIndex {
102
+ conn;
103
+ maxEntriesPerScope;
104
+ /** upload-limits Phase 5 byte quota (0 = disabled, the opt-in default). */
105
+ maxBytesPerScope;
106
+ inline;
107
+ constructor(db, scope, opts) {
108
+ this.db = db;
109
+ this.scope = scope;
110
+ this.conn = opts?.useReplica && typeof db.withSession === "function" ? db.withSession("first-unconstrained") : db;
111
+ this.maxEntriesPerScope = opts?.maxEntriesPerScope ?? MAX_ENTRIES_PER_SCOPE;
112
+ this.maxBytesPerScope = typeof opts?.maxBytesPerScope === "number" && opts.maxBytesPerScope > 0 ? opts.maxBytesPerScope : 0;
113
+ this.inline = resolveInlineConfig(opts);
114
+ }
115
+ /**
116
+ * Create entries table + indexes (v2 schema) AND the Phase 2 changelog table.
117
+ * Production: schema created by `wrangler d1 migrations apply`.
118
+ * Tests: call this in beforeEach with in-memory DB.
119
+ *
120
+ * Changelog DDL is sourced from `changelog-schema.ts` to keep the four
121
+ * runtimes (D1, desktop, Swift, Kotlin) byte-equivalent — see
122
+ * `scripts/check-changelog-ddl-parity.ts`. Schema-only here; producers come
123
+ * online in Task 2.4.
124
+ */
125
+ async ensureTable() {
126
+ await this.db.batch([
127
+ this.db.prepare(ENTRIES_TABLE_SQL),
128
+ ...ENTRIES_INDEX_SQL.map((sql) => this.db.prepare(sql)),
129
+ this.db.prepare(CHANGELOG_TABLE_SQL),
130
+ ...CHANGELOG_INDEX_SQL.map((sql) => this.db.prepare(sql)),
131
+ this.db.prepare(SCOPE_META_TABLE_SQL),
132
+ ...SCOPE_META_FILE_COUNT_TRIGGER_SQL.map((sql) => this.db.prepare(sql)),
133
+ this.db.prepare(PREFIX_WATERMARK_TABLE_SQL),
134
+ this.db.prepare(DIRTY_PATH_TABLE_SQL),
135
+ ...DIRTY_PATH_INDEX_SQL.map((sql) => this.db.prepare(sql)),
136
+ ...DIRTY_PATH_TRIGGER_SQL.map((sql) => this.db.prepare(sql))
137
+ ]);
138
+ const { results } = await this.db.prepare("SELECT name FROM pragma_table_info('ds_entries')").all();
139
+ const colNames = new Set(results.map((r) => r.name));
140
+ if (!colNames.has("content")) try {
141
+ await this.db.prepare(ENTRIES_INLINE_CONTENT_COLUMN_SQL).run();
142
+ } catch (err) {
143
+ if (!(err instanceof Error ? err.message : String(err)).includes("duplicate column name")) throw err;
144
+ }
145
+ if (!colNames.has("metadata")) try {
146
+ await this.db.prepare(ENTRIES_METADATA_COLUMN_SQL).run();
147
+ } catch (err) {
148
+ if (!(err instanceof Error ? err.message : String(err)).includes("duplicate column name")) throw err;
149
+ }
150
+ if (!colNames.has("dedup_scope")) try {
151
+ await this.db.prepare(ENTRIES_DEDUP_SCOPE_COLUMN_SQL).run();
152
+ } catch (err) {
153
+ if (!(err instanceof Error ? err.message : String(err)).includes("duplicate column name")) throw err;
154
+ }
155
+ if (!colNames.has("dedup_key")) try {
156
+ await this.db.prepare(ENTRIES_DEDUP_KEY_COLUMN_SQL).run();
157
+ } catch (err) {
158
+ if (!(err instanceof Error ? err.message : String(err)).includes("duplicate column name")) throw err;
159
+ }
160
+ await this.db.prepare(ENTRIES_DEDUP_INDEX_SQL).run();
161
+ if (!colNames.has("content_type")) try {
162
+ await this.db.prepare(ENTRIES_CONTENT_TYPE_COLUMN_SQL).run();
163
+ } catch (err) {
164
+ if (!(err instanceof Error ? err.message : String(err)).includes("duplicate column name")) throw err;
165
+ }
166
+ await this.db.prepare(ENTRIES_CONTENT_TYPE_INDEX_DROP_SQL).run();
167
+ const scopeMetaCols = await this.db.prepare("SELECT name FROM pragma_table_info('ds_scope_meta')").all();
168
+ if (!new Set(scopeMetaCols.results.map((r) => r.name)).has("total_bytes")) try {
169
+ await this.db.prepare(SCOPE_META_TOTAL_BYTES_COLUMN_SQL).run();
170
+ } catch (err) {
171
+ if (!(err instanceof Error ? err.message : String(err)).includes("duplicate column name")) throw err;
172
+ }
173
+ await this.db.batch(SCOPE_META_TOTAL_BYTES_TRIGGER_SQL.map((sql) => this.db.prepare(sql)));
174
+ }
175
+ async get(path, opts) {
176
+ const contentCol = (opts?.includeContent ?? true) && this.inline.inlineCache === "on" ? ", content" : "";
177
+ const row = await this.conn.prepare(`SELECT cid, size, mtime, ctime, mime_type, metadata${contentCol} FROM ds_entries WHERE scope = ? AND path = ?`).bind(this.scope, path).first();
178
+ if (!row || !row.cid) return null;
179
+ const entry = {
180
+ cid: row.cid,
181
+ size: row.size,
182
+ mtime: row.mtime,
183
+ ctime: row.ctime,
184
+ mimeType: row.mime_type ?? void 0
185
+ };
186
+ if (row.content != null) entry.content = row.content;
187
+ const meta = parseTreeEntryMetadata(row.metadata);
188
+ if (meta) entry.metadata = meta;
189
+ return entry;
190
+ }
191
+ async getDirTimes(dirPath) {
192
+ const row = await this.conn.prepare("SELECT ctime, mtime FROM ds_entries WHERE scope = ? AND path = ?").bind(this.scope, dirPath).first();
193
+ return row ? {
194
+ ctime: row.ctime,
195
+ mtime: row.mtime
196
+ } : null;
197
+ }
198
+ /** Inline content this entry would store — parity with the loop reference impl. */
199
+ inlineForEntry(entry) {
200
+ return resolveInlineContent(entry, this.inline);
201
+ }
202
+ /** Point seek on the `(scope, dedup_scope, dedup_key)` partial UNIQUE index. */
203
+ async findByDedupKey(dedupScope, dedupKey) {
204
+ const row = await this.conn.prepare(`SELECT path, cid, size, mtime, ctime, mime_type, metadata
205
+ FROM ds_entries WHERE scope = ? AND dedup_scope = ? AND dedup_key = ? LIMIT 1`).bind(this.scope, dedupScope, dedupKey).first();
206
+ if (!row || !row.cid) return null;
207
+ const entry = {
208
+ path: row.path,
209
+ cid: row.cid,
210
+ size: row.size,
211
+ mtime: row.mtime,
212
+ ctime: row.ctime,
213
+ mimeType: row.mime_type ?? void 0
214
+ };
215
+ const meta = parseTreeEntryMetadata(row.metadata);
216
+ if (meta) entry.metadata = meta;
217
+ return entry;
218
+ }
219
+ /** Index-only backfill: set the dedup columns without touching content/changelog. */
220
+ async setDedupKey(path, dedupScope, dedupKey, opts) {
221
+ validateDeviceId(opts.deviceId);
222
+ await this.db.prepare("UPDATE ds_entries SET dedup_scope = ?, dedup_key = ? WHERE scope = ? AND path = ?").bind(dedupScope, dedupKey, this.scope, path).run();
223
+ }
224
+ /**
225
+ * Batch metadata read — one chunked `IN (…)` query per ≤90 paths (D1's
226
+ * 100-bind limit minus the scope bind, design §4.3). Result map carries an
227
+ * entry for EVERY requested path (`null` when absent), keyed verbatim.
228
+ */
229
+ async getMany(paths) {
230
+ const map = /* @__PURE__ */ new Map();
231
+ for (const path of paths) map.set(path, null);
232
+ const contentCol = this.inline.inlineCache === "on" ? ", content" : "";
233
+ for (let offset = 0; offset < paths.length; offset += 90) {
234
+ const chunk = paths.slice(offset, offset + 90);
235
+ const { results } = await this.conn.prepare(`SELECT path, cid, size, mtime, ctime, mime_type, metadata${contentCol} FROM ds_entries
236
+ WHERE scope = ? AND kind = 'file' AND path IN (${chunk.map(() => "?").join(",")})`).bind(this.scope, ...chunk).all();
237
+ for (const row of results) {
238
+ if (!row.cid) continue;
239
+ const entry = {
240
+ cid: row.cid,
241
+ size: row.size,
242
+ mtime: row.mtime,
243
+ ctime: row.ctime,
244
+ mimeType: row.mime_type ?? void 0
245
+ };
246
+ if (row.content != null) entry.content = row.content;
247
+ const meta = parseTreeEntryMetadata(row.metadata);
248
+ if (meta) entry.metadata = meta;
249
+ map.set(row.path, entry);
250
+ }
251
+ }
252
+ return map;
253
+ }
254
+ /**
255
+ * Merged batch put (afs-rpc-batch §4.3 / §6.3) — the CF subrequest-budget
256
+ * fast path: ONE preread (caller-supplied or chunked IN), ONE capacity
257
+ * read, ONE atomic `db.batch` carrying every surviving entry's
258
+ * changelog+upsert (+1 watermark bump per write), then ONE watermark
259
+ * readback per distinct collection root to attribute per-entry seqs
260
+ * (`liveSeq[i] = final − N + 1 + i`, skip/conflict entries take no seq).
261
+ *
262
+ * Statement order inside the batch mirrors single `put`: dirs first, then
263
+ * per entry changelog INSERT (correlated old_cid subquery reads the
264
+ * pre-upsert cid) BEFORE its upsert — D1 preserves batch statement order.
265
+ */
266
+ async batchPutEntries(entries, ctx) {
267
+ if (entries.length === 0) return [];
268
+ for (const e of entries) validateDeviceId(e.opts.deviceId);
269
+ const preread = ctx?.preread ?? await this.getMany(entries.map((e) => e.path));
270
+ const results = new Array(entries.length);
271
+ const live = [];
272
+ let newFileCount = 0;
273
+ for (let i = 0; i < entries.length; i++) {
274
+ const { path, entry, opts } = entries[i];
275
+ const existing = preread.get(path) ?? null;
276
+ if (opts.ifMatch !== void 0 && (!existing || existing.cid !== opts.ifMatch)) {
277
+ results[i] = {
278
+ path,
279
+ conflict: {
280
+ currentCid: existing?.cid ?? null,
281
+ currentMtime: existing?.mtime ?? null,
282
+ currentDeviceId: null
283
+ }
284
+ };
285
+ continue;
286
+ }
287
+ if (existing && existing.cid === entry.cid && existing.mimeType === entry.mimeType && treeEntryMetadataEqual(existing.metadata, entry.metadata)) {
288
+ if (this.inline.inlineCache !== "on") {
289
+ results[i] = {
290
+ path,
291
+ oldCid: existing.cid,
292
+ skipped: true
293
+ };
294
+ continue;
295
+ }
296
+ const wouldBeInline = await resolveInlineContent(entry, this.inline);
297
+ if ((existing.content ?? null) === wouldBeInline) {
298
+ results[i] = {
299
+ path,
300
+ oldCid: existing.cid,
301
+ skipped: true
302
+ };
303
+ continue;
304
+ }
305
+ }
306
+ if (!existing) newFileCount++;
307
+ live.push(i);
308
+ }
309
+ if (newFileCount > 0) {
310
+ const currentCount = await this.getScopeFileCount();
311
+ let remaining = this.maxEntriesPerScope - currentCount;
312
+ if (remaining < newFileCount) {
313
+ const surviving = [];
314
+ for (const i of live) if (!(preread.get(entries[i].path) ?? null)) if (remaining > 0) {
315
+ remaining--;
316
+ surviving.push(i);
317
+ } else results[i] = {
318
+ path: entries[i].path,
319
+ error: `Storage quota exceeded: scope "${this.scope}" has reached the ${this.maxEntriesPerScope} file limit`,
320
+ code: STORAGE_QUOTA_EXCEEDED_CODE
321
+ };
322
+ else surviving.push(i);
323
+ live.length = 0;
324
+ live.push(...surviving);
325
+ }
326
+ }
327
+ if (this.maxBytesPerScope > 0 && live.length > 0) {
328
+ const total = await this.getScopeTotalBytes();
329
+ let used = 0;
330
+ const surviving = [];
331
+ for (const i of live) {
332
+ const e = entries[i];
333
+ const newBytes = e.entry.size - (preread.get(e.path)?.size ?? 0);
334
+ if (newBytes > 0 && total + used + newBytes > this.maxBytesPerScope) results[i] = {
335
+ path: e.path,
336
+ error: `Storage quota exceeded: scope "${this.scope}" has reached the ${this.maxBytesPerScope}-byte limit`,
337
+ code: STORAGE_QUOTA_EXCEEDED_CODE
338
+ };
339
+ else {
340
+ if (newBytes > 0) used += newBytes;
341
+ surviving.push(i);
342
+ }
343
+ }
344
+ live.length = 0;
345
+ live.push(...surviving);
346
+ }
347
+ if (live.length === 0) return results;
348
+ const now = Date.now();
349
+ const stmts = [];
350
+ const dirStamps = /* @__PURE__ */ new Map();
351
+ const allDirs = /* @__PURE__ */ new Set();
352
+ for (const i of live) {
353
+ const path = entries[i].path;
354
+ const stamp = resolveParentDirStamp(entries[i].entry.metadata);
355
+ if (stamp) dirStamps.set(parentOf(path), stamp);
356
+ for (const dirPath of ancestorDirs(path)) {
357
+ if (allDirs.has(dirPath)) break;
358
+ allDirs.add(dirPath);
359
+ }
360
+ }
361
+ for (const dirPath of allDirs) {
362
+ const { sql, args } = buildDirChainStmt(this.scope, dirPath, dirStamps.get(dirPath) ?? null, now);
363
+ stmts.push(this.db.prepare(sql).bind(...args));
364
+ }
365
+ const writesByPrefix = /* @__PURE__ */ new Map();
366
+ for (const i of live) {
367
+ const { path, entry, opts } = entries[i];
368
+ stmts.push(this.db.prepare(`INSERT INTO ds_changelog (scope, path, op, cid, old_cid, size, mtime, device_id)
369
+ VALUES (?, ?, 'put', ?, (SELECT cid FROM ds_entries WHERE scope = ? AND path = ?), ?, ?, ?)`).bind(this.scope, path, entry.cid, this.scope, path, entry.size, entry.mtime, opts.deviceId));
370
+ stmts.push(await this.buildFileUpsert(path, entry));
371
+ const wmPrefix = watermarkWritePrefix(path);
372
+ if (wmPrefix !== null) {
373
+ stmts.push(this.db.prepare(PREFIX_WATERMARK_BUMP_SQL).bind(this.scope, wmPrefix));
374
+ const order = writesByPrefix.get(wmPrefix) ?? [];
375
+ order.push(i);
376
+ writesByPrefix.set(wmPrefix, order);
377
+ }
378
+ }
379
+ await this.db.batch(stmts);
380
+ for (const [wmPrefix, order] of writesByPrefix) {
381
+ const final = (await this.conn.prepare(PREFIX_WATERMARK_READ_SQL).bind(this.scope, wmPrefix).first())?.version ?? 0;
382
+ order.forEach((i, j) => {
383
+ const existing = preread.get(entries[i].path) ?? null;
384
+ results[i] = {
385
+ path: entries[i].path,
386
+ ...existing ? { oldCid: existing.cid } : {},
387
+ liveSeq: final - order.length + 1 + j
388
+ };
389
+ });
390
+ }
391
+ for (const i of live) if (!results[i]) {
392
+ const existing = preread.get(entries[i].path) ?? null;
393
+ results[i] = {
394
+ path: entries[i].path,
395
+ ...existing ? { oldCid: existing.cid } : {}
396
+ };
397
+ }
398
+ return results;
399
+ }
400
+ /**
401
+ * Merged batch delete — same shape as {@link batchPutEntries} without the
402
+ * R2 leg: ONE preread, ONE atomic `db.batch` (per entry: changelog INSERT
403
+ * SELECT + file_count decrement + DELETE, +1 watermark bump), then empty-
404
+ * parent cleanup over the DISTINCT ancestor chain. FILE entries only.
405
+ */
406
+ async batchDeleteEntries(entries, ctx) {
407
+ if (entries.length === 0) return [];
408
+ for (const e of entries) validateDeviceId(e.opts.deviceId);
409
+ const preread = ctx?.preread ?? await this.getMany(entries.map((e) => e.path));
410
+ const results = new Array(entries.length);
411
+ const live = [];
412
+ for (let i = 0; i < entries.length; i++) {
413
+ const { path, opts } = entries[i];
414
+ const existing = preread.get(path) ?? null;
415
+ if (opts.ifMatch !== void 0 && (!existing || existing.cid !== opts.ifMatch)) {
416
+ results[i] = {
417
+ path,
418
+ conflict: {
419
+ currentCid: existing?.cid ?? null,
420
+ currentMtime: existing?.mtime ?? null,
421
+ currentDeviceId: null
422
+ }
423
+ };
424
+ continue;
425
+ }
426
+ if (!existing) {
427
+ results[i] = {
428
+ path,
429
+ notFound: true
430
+ };
431
+ continue;
432
+ }
433
+ live.push(i);
434
+ }
435
+ if (live.length === 0) return results;
436
+ const stmts = [];
437
+ const writesByPrefix = /* @__PURE__ */ new Map();
438
+ for (const i of live) {
439
+ const { path, opts } = entries[i];
440
+ stmts.push(this.db.prepare(`INSERT INTO ds_changelog (scope, path, op, cid, old_cid, size, mtime, device_id)
441
+ SELECT ?, ?, 'delete', NULL, cid, NULL, ?, ?
442
+ FROM ds_entries WHERE scope = ? AND path = ? AND cid IS NOT NULL`).bind(this.scope, path, opts.mtime ?? Date.now(), opts.deviceId, this.scope, path));
443
+ stmts.push(this.db.prepare(SCOPE_META_FILE_COUNT_DELETE_ONE_SQL).bind(this.scope, this.scope, path));
444
+ stmts.push(this.db.prepare(SCOPE_META_TOTAL_BYTES_DELETE_ONE_SQL).bind(this.scope, path, this.scope));
445
+ stmts.push(this.db.prepare("DELETE FROM ds_entries WHERE scope = ? AND path = ?").bind(this.scope, path));
446
+ const wmPrefix = watermarkWritePrefix(path);
447
+ if (wmPrefix !== null) {
448
+ stmts.push(this.db.prepare(PREFIX_WATERMARK_BUMP_SQL).bind(this.scope, wmPrefix));
449
+ const order = writesByPrefix.get(wmPrefix) ?? [];
450
+ order.push(i);
451
+ writesByPrefix.set(wmPrefix, order);
452
+ }
453
+ }
454
+ await this.db.batch(stmts);
455
+ for (const [wmPrefix, order] of writesByPrefix) {
456
+ const final = (await this.conn.prepare(PREFIX_WATERMARK_READ_SQL).bind(this.scope, wmPrefix).first())?.version ?? 0;
457
+ order.forEach((i, j) => {
458
+ results[i] = {
459
+ path: entries[i].path,
460
+ deletedCid: (preread.get(entries[i].path) ?? null)?.cid,
461
+ liveSeq: final - order.length + 1 + j
462
+ };
463
+ });
464
+ }
465
+ for (const i of live) if (!results[i]) results[i] = {
466
+ path: entries[i].path,
467
+ deletedCid: (preread.get(entries[i].path) ?? null)?.cid
468
+ };
469
+ const dirs = /* @__PURE__ */ new Set();
470
+ for (const i of live) for (const dirPath of ancestorDirs(entries[i].path)) dirs.add(dirPath);
471
+ const ordered = [...dirs].sort((a, b) => b.length - a.length);
472
+ for (const dirPath of ordered) {
473
+ if (await this.db.prepare("SELECT 1 FROM ds_entries WHERE scope = ? AND parent = ? LIMIT 1").bind(this.scope, dirPath).first()) continue;
474
+ await this.db.prepare("DELETE FROM ds_entries WHERE scope = ? AND path = ?").bind(this.scope, dirPath).run();
475
+ }
476
+ return results;
477
+ }
478
+ async put(path, entry, opts) {
479
+ validateDeviceId(opts.deviceId);
480
+ parentOf(path);
481
+ nameOf(path);
482
+ const now = Date.now();
483
+ const existing = await this.get(path);
484
+ if (existing && existing.cid === entry.cid && existing.mimeType === entry.mimeType && treeEntryMetadataEqual(existing.metadata, entry.metadata)) {
485
+ if (this.inline.inlineCache !== "on") return { oldCid: existing.cid };
486
+ const wouldBeInline = await resolveInlineContent(entry, this.inline);
487
+ if ((existing.content ?? null) === wouldBeInline) return { oldCid: existing.cid };
488
+ }
489
+ if (!existing) {
490
+ if (await this.getScopeFileCount() >= this.maxEntriesPerScope) throw storageQuotaError(`Storage quota exceeded: scope "${this.scope}" has reached the ${this.maxEntriesPerScope} file limit`);
491
+ }
492
+ if (this.maxBytesPerScope > 0) {
493
+ const newBytes = entry.size - (existing?.size ?? 0);
494
+ if (newBytes > 0) {
495
+ if (await this.getScopeTotalBytes() + newBytes > this.maxBytesPerScope) throw storageQuotaError(`Storage quota exceeded: scope "${this.scope}" has reached the ${this.maxBytesPerScope}-byte limit`);
496
+ }
497
+ }
498
+ const stmts = [];
499
+ const dirStamp = resolveParentDirStamp(entry.metadata);
500
+ const directParent = parentOf(path);
501
+ for (const dirPath of ancestorDirs(path)) {
502
+ const { sql, args } = buildDirChainStmt(this.scope, dirPath, dirStamp && dirPath === directParent ? dirStamp : null, now);
503
+ stmts.push(this.db.prepare(sql).bind(...args));
504
+ }
505
+ if (!opts.skipChangelog) stmts.push(this.db.prepare(`INSERT INTO ds_changelog (scope, path, op, cid, old_cid, size, mtime, device_id)
506
+ VALUES (?, ?, 'put', ?, (SELECT cid FROM ds_entries WHERE scope = ? AND path = ?), ?, ?, ?)`).bind(this.scope, path, entry.cid, this.scope, path, entry.size, entry.mtime, opts.deviceId));
507
+ stmts.push(await this.buildFileUpsert(path, entry));
508
+ if (!opts.skipChangelog) {
509
+ const wmPrefix = watermarkWritePrefix(path);
510
+ if (wmPrefix !== null) stmts.push(this.db.prepare(PREFIX_WATERMARK_BUMP_SQL).bind(this.scope, wmPrefix));
511
+ }
512
+ await this.db.batch(stmts);
513
+ return { oldCid: existing?.cid };
514
+ }
515
+ /**
516
+ * Build the file-row UPSERT. Inline-enabled indexes always write the
517
+ * content column (inline copy or NULL — an overwrite without inline content
518
+ * MUST clear any prior copy so the column never goes stale relative to the
519
+ * new cid); default-off indexes keep the byte-identical legacy statement
520
+ * (and never touch a not-yet-migrated database's missing column).
521
+ */
522
+ async buildFileUpsert(path, entry) {
523
+ const metadataValue = serializeTreeEntryMetadata(entry.metadata);
524
+ const dedupScopeValue = entry.dedupScope ?? null;
525
+ const dedupKeyValue = entry.dedupKey ?? null;
526
+ if (this.inline.inlineCache !== "on") return this.db.prepare(`INSERT INTO ds_entries (scope, path, parent, name, kind, cid, size, mime_type, ctime, mtime, metadata, dedup_scope, dedup_key)
527
+ VALUES (?, ?, ?, ?, 'file', ?, ?, ?, ?, ?, ?, ?, ?)
528
+ ON CONFLICT(scope, path) DO UPDATE SET
529
+ cid = excluded.cid,
530
+ size = excluded.size,
531
+ mtime = excluded.mtime,
532
+ mime_type = excluded.mime_type,
533
+ metadata = excluded.metadata,
534
+ dedup_scope = COALESCE(excluded.dedup_scope, ds_entries.dedup_scope),
535
+ dedup_key = COALESCE(excluded.dedup_key, ds_entries.dedup_key)`).bind(this.scope, path, parentOf(path), nameOf(path), entry.cid, entry.size, entry.mimeType ?? null, entry.ctime, entry.mtime, metadataValue, dedupScopeValue, dedupKeyValue);
536
+ const inlineContent = await resolveInlineContent(entry, this.inline);
537
+ return this.db.prepare(`INSERT INTO ds_entries (scope, path, parent, name, kind, cid, size, mime_type, ctime, mtime, content, metadata, dedup_scope, dedup_key)
538
+ VALUES (?, ?, ?, ?, 'file', ?, ?, ?, ?, ?, ?, ?, ?, ?)
539
+ ON CONFLICT(scope, path) DO UPDATE SET
540
+ cid = excluded.cid,
541
+ size = excluded.size,
542
+ mtime = excluded.mtime,
543
+ mime_type = excluded.mime_type,
544
+ content = excluded.content,
545
+ metadata = excluded.metadata,
546
+ dedup_scope = COALESCE(excluded.dedup_scope, ds_entries.dedup_scope),
547
+ dedup_key = COALESCE(excluded.dedup_key, ds_entries.dedup_key)`).bind(this.scope, path, parentOf(path), nameOf(path), entry.cid, entry.size, entry.mimeType ?? null, entry.ctime, entry.mtime, inlineContent, metadataValue, dedupScopeValue, dedupKeyValue);
548
+ }
549
+ async delete(path, opts) {
550
+ validateDeviceId(opts.deviceId);
551
+ const stmts = [];
552
+ if (!opts.skipChangelog) stmts.push(this.db.prepare(`INSERT INTO ds_changelog (scope, path, op, cid, old_cid, size, mtime, device_id)
553
+ SELECT ?, ?, 'delete', NULL, cid, NULL, ?, ?
554
+ FROM ds_entries WHERE scope = ? AND path = ? AND cid IS NOT NULL`).bind(this.scope, path, opts.mtime ?? Date.now(), opts.deviceId, this.scope, path));
555
+ stmts.push(this.db.prepare(SCOPE_META_FILE_COUNT_DELETE_ONE_SQL).bind(this.scope, this.scope, path));
556
+ stmts.push(this.db.prepare(SCOPE_META_TOTAL_BYTES_DELETE_ONE_SQL).bind(this.scope, path, this.scope));
557
+ stmts.push(this.db.prepare("DELETE FROM ds_entries WHERE scope = ? AND path = ?").bind(this.scope, path));
558
+ if (!opts.skipChangelog) {
559
+ const wmPrefix = watermarkWritePrefix(path);
560
+ if (wmPrefix !== null) stmts.push(this.db.prepare(PREFIX_WATERMARK_BUMP_SQL).bind(this.scope, wmPrefix));
561
+ }
562
+ await this.db.batch(stmts);
563
+ await this.cleanEmptyParents(path);
564
+ }
565
+ /** Walk up parent chain, removing directory entries that have no remaining children */
566
+ async cleanEmptyParents(path) {
567
+ for (const dirPath of ancestorDirs(path)) {
568
+ if (await this.db.prepare("SELECT 1 FROM ds_entries WHERE scope = ? AND parent = ? LIMIT 1").bind(this.scope, dirPath).first()) break;
569
+ await this.db.prepare("DELETE FROM ds_entries WHERE scope = ? AND path = ?").bind(this.scope, dirPath).run();
570
+ }
571
+ }
572
+ static VALID_ORDER_BY = {
573
+ path: "path",
574
+ mtime: "mtime",
575
+ size: "size",
576
+ ctime: "ctime",
577
+ mimeType: "mime_type",
578
+ name: "name"
579
+ };
580
+ /** The only nullable sortable column — NULLs sort last (see tree-index.ts). */
581
+ static NULLABLE_ORDER_COLUMNS = new Set(["mime_type"]);
582
+ async list(prefix, options) {
583
+ const limit = options?.limit ?? 1e3;
584
+ const orderByKey = options?.orderBy ?? "path";
585
+ const order = options?.order ?? "asc";
586
+ const cursor = options?.cursor;
587
+ const col = D1TreeIndex.VALID_ORDER_BY[orderByKey];
588
+ if (!col) throw new Error(`Invalid orderBy: ${orderByKey}`);
589
+ if (limit === 0) return {
590
+ entries: [],
591
+ hasMore: false
592
+ };
593
+ const [lo, hi] = prefixRange(prefix);
594
+ const params = [
595
+ this.scope,
596
+ lo,
597
+ hi
598
+ ];
599
+ let sql = `SELECT path, ${col} as _sort FROM ds_entries WHERE scope = ? AND path >= ? AND path < ? AND kind = 'file'`;
600
+ if (cursor) {
601
+ sql += ` AND ${col} ${order === "asc" ? ">" : "<"} ?`;
602
+ params.push(cursor);
603
+ }
604
+ sql += ` ORDER BY ${col} ${order} LIMIT ?`;
605
+ params.push(limit + 1);
606
+ const { results } = await this.conn.prepare(sql).bind(...params).all();
607
+ const hasMore = results.length > limit;
608
+ const selected = hasMore ? results.slice(0, limit) : results;
609
+ return {
610
+ entries: selected.map((r) => ({ path: r.path })),
611
+ hasMore,
612
+ cursor: hasMore && selected.length > 0 ? String(selected[selected.length - 1]._sort) : void 0
613
+ };
614
+ }
615
+ async listEntries(prefix, options) {
616
+ const limit = options?.limit ?? 1e3;
617
+ const hasOrderBy = options?.orderBy !== void 0;
618
+ const orderByKey = options?.orderBy ?? "path";
619
+ const order = options?.order === "desc" ? "desc" : "asc";
620
+ const col = D1TreeIndex.VALID_ORDER_BY[orderByKey];
621
+ if (!col) throw new Error(`Invalid orderBy: ${orderByKey}`);
622
+ if (limit === 0) return {
623
+ entries: [],
624
+ hasMore: false
625
+ };
626
+ const parsed = parseTreeCursor(options?.cursor, hasOrderBy);
627
+ const [lo, hi] = prefixRange(prefix);
628
+ const params = [
629
+ this.scope,
630
+ lo,
631
+ hi
632
+ ];
633
+ let sql = `SELECT path, cid, size, mtime, ctime, mime_type${options?.includeContent ? ", content" : ""}, ${col} as _sort FROM ds_entries${col === "path" ? " INDEXED BY idx_entries_kind" : ""} WHERE scope = ? AND path >= ? AND path < ? AND kind = 'file'`;
634
+ if (hasOrderBy) {
635
+ const parts = buildOrderedQueryParts({
636
+ col,
637
+ nullable: D1TreeIndex.NULLABLE_ORDER_COLUMNS.has(col),
638
+ order,
639
+ tieCol: "path",
640
+ cursor: parsed.kind === "v2" ? parsed.cursor : void 0
641
+ });
642
+ if (parts.cursorWhereSql) {
643
+ sql += ` AND ${parts.cursorWhereSql}`;
644
+ params.push(...parts.cursorArgs);
645
+ }
646
+ sql += ` ORDER BY ${parts.orderSql} LIMIT ?`;
647
+ } else {
648
+ if (parsed.kind === "legacy") {
649
+ sql += ` AND ${col} ${order === "asc" ? ">" : "<"} ?`;
650
+ params.push(parsed.value);
651
+ }
652
+ sql += ` ORDER BY ${col} ${order} LIMIT ?`;
653
+ }
654
+ params.push(limit + 1);
655
+ const { results } = await this.conn.prepare(sql).bind(...params).all();
656
+ const hasMore = results.length > limit;
657
+ const selected = hasMore ? results.slice(0, limit) : results;
658
+ const last = selected[selected.length - 1];
659
+ return {
660
+ entries: selected.map((r) => {
661
+ const e = {
662
+ path: r.path,
663
+ cid: r.cid,
664
+ size: r.size,
665
+ mtime: r.mtime,
666
+ ctime: r.ctime,
667
+ mimeType: r.mime_type ?? void 0
668
+ };
669
+ if (options?.includeContent && r.content != null) e.content = r.content;
670
+ return e;
671
+ }),
672
+ hasMore,
673
+ cursor: !hasMore || !last ? void 0 : hasOrderBy ? buildKeysetCursor(last._sort ?? null, last.path) : String(last._sort)
674
+ };
675
+ }
676
+ async hasChildren(prefix) {
677
+ const [lo, hi] = prefixRange(prefix);
678
+ return await this.conn.prepare("SELECT 1 FROM ds_entries WHERE scope = ? AND path >= ? AND path < ? AND kind = 'file' LIMIT 1").bind(this.scope, lo, hi).first() !== null;
679
+ }
680
+ async getAndHasChildren(filePath, dirPrefix) {
681
+ const contentCol = this.inline.inlineCache === "on" ? ", content" : "";
682
+ const [lo, hi] = prefixRange(dirPrefix);
683
+ const batchResults = await this.db.batch([this.db.prepare(`SELECT cid, size, mtime, ctime, mime_type, metadata${contentCol} FROM ds_entries WHERE scope = ? AND path = ?`).bind(this.scope, filePath), this.db.prepare("SELECT 1 FROM ds_entries WHERE scope = ? AND path >= ? AND path < ? AND kind = 'file' LIMIT 1").bind(this.scope, lo, hi)]);
684
+ const getResult = batchResults[0];
685
+ const hasChildResult = batchResults[1];
686
+ const row = getResult.results[0];
687
+ let entry = null;
688
+ if (row?.cid) {
689
+ entry = {
690
+ cid: row.cid,
691
+ size: row.size,
692
+ mtime: row.mtime,
693
+ ctime: row.ctime,
694
+ mimeType: row.mime_type ?? void 0
695
+ };
696
+ if (row.content != null) entry.content = row.content;
697
+ const meta = parseTreeEntryMetadata(row.metadata);
698
+ if (meta) entry.metadata = meta;
699
+ }
700
+ return [entry, (hasChildResult.results?.length ?? 0) > 0];
701
+ }
702
+ async count(prefix) {
703
+ const [lo, hi] = prefixRange(prefix);
704
+ return (await this.conn.prepare("SELECT COUNT(*) as cnt FROM ds_entries WHERE scope = ? AND path >= ? AND path < ? AND kind = 'file'").bind(this.scope, lo, hi).first())?.cnt ?? 0;
705
+ }
706
+ /**
707
+ * O(1) read of the trigger-maintained file count for this scope, used by the
708
+ * `put()` capacity guard. A missing `scope_meta` row means "no files counted
709
+ * yet" → 0, which is also the correct answer for a brand-new scope and the
710
+ * safe (permissive) fallback for a scope not yet backfilled. This replaces
711
+ * the per-write full-scope `count("")` scan — the source of the O(N²)
712
+ * `rows_read` blow-up that exhausted the D1 free tier during bulk sync.
713
+ */
714
+ async getScopeFileCount() {
715
+ return (await this.conn.prepare("SELECT file_count FROM ds_scope_meta WHERE scope = ?").bind(this.scope).first())?.file_count ?? 0;
716
+ }
717
+ /**
718
+ * O(1) read of the trigger-maintained byte total for this scope, used by the
719
+ * Phase 5 byte-quota guard (only when `maxBytesPerScope > 0`). Same O(1)
720
+ * contract as {@link getScopeFileCount} — never a `SELECT SUM(size)` scan.
721
+ * Missing row → 0 (new/un-backfilled scope; permissive).
722
+ */
723
+ async getScopeTotalBytes() {
724
+ return (await this.conn.prepare("SELECT total_bytes FROM ds_scope_meta WHERE scope = ?").bind(this.scope).first())?.total_bytes ?? 0;
725
+ }
726
+ async getCompactedBelowSeq() {
727
+ return (await this.conn.prepare("SELECT compacted_below_seq FROM ds_scope_meta WHERE scope = ?").bind(this.scope).first())?.compacted_below_seq ?? 0;
728
+ }
729
+ async deleteByPrefix(prefix, opts) {
730
+ validateDeviceId(opts.deviceId);
731
+ const [lo, hi] = prefixRange(prefix);
732
+ const stmts = [];
733
+ if (!opts.skipChangelog) {
734
+ const now = opts.mtime ?? Date.now();
735
+ stmts.push(this.db.prepare(`INSERT INTO ds_changelog (scope, path, op, cid, old_cid, size, mtime, device_id)
736
+ SELECT ?, path, 'delete', NULL, cid, NULL, ?, ?
737
+ FROM ds_entries INDEXED BY idx_entries_kind
738
+ WHERE scope = ? AND path >= ? AND path < ? AND kind = 'file' AND cid IS NOT NULL`).bind(this.scope, now, opts.deviceId, this.scope, lo, hi));
739
+ }
740
+ stmts.push(this.db.prepare(SCOPE_META_FILE_COUNT_DELETE_RANGE_SQL).bind(this.scope, lo, hi, this.scope));
741
+ stmts.push(this.db.prepare(SCOPE_META_TOTAL_BYTES_DELETE_RANGE_SQL).bind(this.scope, lo, hi, this.scope));
742
+ if (!opts.skipChangelog) {
743
+ const wmPrefix = watermarkPrefix(prefix);
744
+ if (wmPrefix !== null) stmts.push(this.db.prepare(PREFIX_WATERMARK_BUMP_SQL).bind(this.scope, wmPrefix));
745
+ }
746
+ stmts.push(this.db.prepare("DELETE FROM ds_entries WHERE scope = ? AND path >= ? AND path < ?").bind(this.scope, lo, hi));
747
+ const batchResults = await this.db.batch(stmts);
748
+ return batchResults[batchResults.length - 1].meta.changes;
749
+ }
750
+ async listChildren(parent, options) {
751
+ const limit = options?.limit ?? 1e3;
752
+ const order = options?.order === "desc" ? "desc" : "asc";
753
+ if (limit === 0) return {
754
+ entries: [],
755
+ hasMore: false
756
+ };
757
+ const hasOrderBy = options?.orderBy !== void 0;
758
+ const parsed = parseTreeCursor(options?.cursor, hasOrderBy);
759
+ const params = [this.scope, parent];
760
+ const includeContent = options?.includeContent === true;
761
+ const t = "e.";
762
+ const from = buildChildrenFrom(void 0);
763
+ const fromClause = from.sql;
764
+ const contentCol = includeContent ? `, ${t}content` : "";
765
+ params.unshift(...from.args);
766
+ const mapChild = (r) => {
767
+ const e = {
768
+ path: r.path,
769
+ cid: r.cid ?? "",
770
+ size: r.size,
771
+ mtime: r.mtime,
772
+ ctime: r.ctime,
773
+ mimeType: r.mime_type ?? void 0
774
+ };
775
+ if (includeContent && r.content != null) e.content = r.content;
776
+ return e;
777
+ };
778
+ if (hasOrderBy) {
779
+ const col = D1TreeIndex.VALID_ORDER_BY[options.orderBy];
780
+ if (!col) throw new Error(`Invalid orderBy: ${options?.orderBy}`);
781
+ const parts = buildOrderedQueryParts({
782
+ col: `${t}${col}`,
783
+ nullable: D1TreeIndex.NULLABLE_ORDER_COLUMNS.has(col),
784
+ order,
785
+ tieCol: `${t}name`,
786
+ cursor: parsed.kind === "v2" ? parsed.cursor : void 0
787
+ });
788
+ let sql$1 = `SELECT ${t}path, ${t}cid, ${t}size, ${t}mtime, ${t}ctime, ${t}mime_type, ${t}name${contentCol}, ${t}${col} as _sort ${fromClause} WHERE ${t}scope = ? AND ${t}parent = ?`;
789
+ if (parts.cursorWhereSql) {
790
+ sql$1 += ` AND ${parts.cursorWhereSql}`;
791
+ params.push(...parts.cursorArgs);
792
+ }
793
+ sql$1 += ` ORDER BY ${parts.orderSql} LIMIT ?`;
794
+ params.push(limit + 1);
795
+ const { results: results$1 } = await this.conn.prepare(sql$1).bind(...params).all();
796
+ const hasMore$1 = results$1.length > limit;
797
+ const selected$1 = hasMore$1 ? results$1.slice(0, limit) : results$1;
798
+ const last = selected$1[selected$1.length - 1];
799
+ return {
800
+ entries: selected$1.map(mapChild),
801
+ hasMore: hasMore$1,
802
+ cursor: hasMore$1 && last ? buildKeysetCursor(last._sort ?? null, last.name) : void 0
803
+ };
804
+ }
805
+ let sql = `SELECT ${t}path, ${t}cid, ${t}size, ${t}mtime, ${t}ctime, ${t}mime_type, ${t}name${contentCol}, ${t}name as _sort ${fromClause} WHERE ${t}scope = ? AND ${t}parent = ?`;
806
+ if (parsed.kind === "legacy") {
807
+ sql += ` AND ${t}name ${order === "asc" ? ">" : "<"} ?`;
808
+ params.push(parsed.value);
809
+ }
810
+ sql += ` ORDER BY ${t}name ${order} LIMIT ?`;
811
+ params.push(limit + 1);
812
+ const { results } = await this.conn.prepare(sql).bind(...params).all();
813
+ const hasMore = results.length > limit;
814
+ const selected = hasMore ? results.slice(0, limit) : results;
815
+ return {
816
+ entries: selected.map(mapChild),
817
+ hasMore,
818
+ cursor: hasMore && selected.length > 0 ? String(selected[selected.length - 1].name) : void 0
819
+ };
820
+ }
821
+ async countChildren(parent) {
822
+ return (await this.conn.prepare("SELECT COUNT(*) as cnt FROM ds_entries WHERE scope = ? AND parent = ?").bind(this.scope, parent).first())?.cnt ?? 0;
823
+ }
824
+ /**
825
+ * Collection query (afs-collection-query Phase 2) — same shared compiler +
826
+ * result builder as SQLiteTreeIndex (tree-query.ts), executed over the
827
+ * replica-aware `this.conn`: query is read-class and follows the SAME
828
+ * replica routing policy as list (AFS_D1_READ_REPLICA gate upstream picks
829
+ * the session; no separate consistency standard — design T2.1).
830
+ */
831
+ async query(parent, spec) {
832
+ const statements = buildTreeQueryStatements({
833
+ scope: this.scope,
834
+ parent,
835
+ spec
836
+ });
837
+ if (statements.aggregate) {
838
+ const [aggRes, countRow$1] = await Promise.all([this.conn.prepare(statements.aggregate.select.sql).bind(...statements.aggregate.select.args).all(), this.conn.prepare(statements.aggregate.countGroups.sql).bind(...statements.aggregate.countGroups.args).first()]);
839
+ const aggregateGroups = mapAggregateRows(aggRes.results, statements.aggregate.groupKeys, statements.aggregate.aggregations);
840
+ return {
841
+ rows: [],
842
+ total: statements.aggregate.groupKeys.length === 0 ? 1 : countRow$1?.cnt ?? 0,
843
+ aggregateGroups
844
+ };
845
+ }
846
+ const [rowsRes, countRow, groupsRes, seekRow] = await Promise.all([
847
+ this.conn.prepare(statements.rows.sql).bind(...statements.rows.args).all(),
848
+ statements.count ? this.conn.prepare(statements.count.sql).bind(...statements.count.args).first() : Promise.resolve(void 0),
849
+ statements.groups ? this.conn.prepare(statements.groups.sql).bind(...statements.groups.args).all() : Promise.resolve(void 0),
850
+ statements.seek ? this.conn.prepare(statements.seek.sql).bind(...statements.seek.args).first() : Promise.resolve(void 0)
851
+ ]);
852
+ const seekOffset = statements.seek ? seekRow?.rn ?? -1 : void 0;
853
+ return buildTreeQueryResultFromSql({
854
+ rawRows: rowsRes.results,
855
+ total: statements.count ? countRow?.cnt ?? 0 : -1,
856
+ groupRows: groupsRes?.results,
857
+ spec,
858
+ orderField: statements.order.field,
859
+ seekOffset
860
+ });
861
+ }
862
+ /**
863
+ * Page-bounded grouped child counts: ONE chunked `parent IN (…)` aggregate
864
+ * per ≤90 parents (D1's 100-bind limit minus the scope bind — same chunking
865
+ * as `getMany`), index-only seeks on `idx_entries_children(scope, parent,
866
+ * name)`. rows_read is bounded by the listed page's direct grandchildren.
867
+ *
868
+ * Predecessor (`countChildrenGrouped(prefix)`) used a `parent >= lo AND
869
+ * parent < hi` range — a scan/group over EVERY descendant under the listed
870
+ * prefix on EVERY list. Once the aside user space grew to ~1.8k dirs, each
871
+ * list of any ancestor dir read ~3.8k rows; at list frequency that burned
872
+ * ~100M D1 rows_read/day (42× the free tier).
873
+ */
874
+ async countChildrenMany(parents) {
875
+ const map = /* @__PURE__ */ new Map();
876
+ for (let offset = 0; offset < parents.length; offset += 90) {
877
+ const chunk = parents.slice(offset, offset + 90);
878
+ const { results } = await this.conn.prepare(`SELECT parent, COUNT(*) as cnt FROM ds_entries WHERE scope = ? AND parent IN (${chunk.map(() => "?").join(",")}) GROUP BY parent`).bind(this.scope, ...chunk).all();
879
+ for (const row of results) map.set(row.parent, row.cnt);
880
+ }
881
+ return map;
882
+ }
883
+ async aggregate(prefix) {
884
+ const [lo, hi] = prefixRange(prefix);
885
+ const row = await this.conn.prepare(`SELECT COUNT(*) as file_count, COALESCE(SUM(size), 0) as total_size
886
+ FROM ds_entries INDEXED BY idx_entries_kind WHERE scope = ? AND path >= ? AND path < ? AND kind = 'file'`).bind(this.scope, lo, hi).first();
887
+ return {
888
+ fileCount: row?.file_count ?? 0,
889
+ totalSize: row?.total_size ?? 0
890
+ };
891
+ }
892
+ async putBatch(entries, opts) {
893
+ if (entries.length === 0) return;
894
+ validateDeviceId(opts.deviceId);
895
+ const now = Date.now();
896
+ const stmts = [];
897
+ const dirStamps = /* @__PURE__ */ new Map();
898
+ const allDirs = /* @__PURE__ */ new Set();
899
+ for (const { path, entry } of entries) {
900
+ const stamp = resolveParentDirStamp(entry.metadata);
901
+ if (stamp) dirStamps.set(parentOf(path), stamp);
902
+ for (const dirPath of ancestorDirs(path)) {
903
+ if (allDirs.has(dirPath)) break;
904
+ allDirs.add(dirPath);
905
+ }
906
+ }
907
+ for (const dirPath of allDirs) {
908
+ const { sql, args } = buildDirChainStmt(this.scope, dirPath, dirStamps.get(dirPath) ?? null, now);
909
+ stmts.push(this.db.prepare(sql).bind(...args));
910
+ }
911
+ for (const { path, entry } of entries) {
912
+ if (!opts.skipChangelog) stmts.push(this.db.prepare(`INSERT INTO ds_changelog (scope, path, op, cid, old_cid, size, mtime, device_id)
913
+ VALUES (?, ?, 'put', ?, (SELECT cid FROM ds_entries WHERE scope = ? AND path = ?), ?, ?, ?)`).bind(this.scope, path, entry.cid, this.scope, path, entry.size, entry.mtime, opts.deviceId));
914
+ stmts.push(await this.buildFileUpsert(path, entry));
915
+ }
916
+ if (!opts.skipChangelog) {
917
+ const wmPrefixes = /* @__PURE__ */ new Set();
918
+ for (const { path } of entries) {
919
+ const wmPrefix = watermarkWritePrefix(path);
920
+ if (wmPrefix !== null) wmPrefixes.add(wmPrefix);
921
+ }
922
+ for (const wmPrefix of wmPrefixes) stmts.push(this.db.prepare(PREFIX_WATERMARK_BUMP_SQL).bind(this.scope, wmPrefix));
923
+ }
924
+ await this.db.batch(stmts);
925
+ }
926
+ /**
927
+ * Read the per-prefix watermark version (Live Update Resilience §11.4).
928
+ * Reduces `prefix` to its first path segment (collection root) and returns
929
+ * that row's `version`, or 0 when no row exists. Replica-aware via `this.conn`
930
+ * — but reconcile passes a primary-pinned index for read-your-watermark
931
+ * (DEC-5; see CloudflareDIDSpace.getInstanceSpace consistency option).
932
+ */
933
+ async getPrefixWatermark(prefix) {
934
+ const wmPrefix = watermarkPrefix(prefix);
935
+ if (wmPrefix === null) return null;
936
+ return (await this.conn.prepare(PREFIX_WATERMARK_READ_SQL).bind(this.scope, wmPrefix).first())?.version ?? 0;
937
+ }
938
+ async listModifiedSince(prefix, since, options) {
939
+ const limit = options?.limit ?? 1e3;
940
+ const order = options?.order ?? "asc";
941
+ const cursor = options?.cursor;
942
+ if (limit === 0) return {
943
+ entries: [],
944
+ hasMore: false
945
+ };
946
+ const [lo, hi] = prefixRange(prefix);
947
+ const params = [
948
+ this.scope,
949
+ lo,
950
+ hi,
951
+ since
952
+ ];
953
+ let sql = `SELECT path, cid, size, mtime, ctime, mime_type FROM ds_entries INDEXED BY idx_entries_kind
954
+ WHERE scope = ? AND path >= ? AND path < ? AND mtime > ? AND kind = 'file'`;
955
+ if (cursor) {
956
+ sql += ` AND path ${order === "asc" ? ">" : "<"} ?`;
957
+ params.push(cursor);
958
+ }
959
+ sql += ` ORDER BY path ${order} LIMIT ?`;
960
+ params.push(limit + 1);
961
+ const { results } = await this.conn.prepare(sql).bind(...params).all();
962
+ const hasMore = results.length > limit;
963
+ const selected = hasMore ? results.slice(0, limit) : results;
964
+ return {
965
+ entries: selected.map((r) => ({
966
+ path: r.path,
967
+ cid: r.cid,
968
+ size: r.size,
969
+ mtime: r.mtime,
970
+ ctime: r.ctime,
971
+ mimeType: r.mime_type ?? void 0
972
+ })),
973
+ hasMore,
974
+ cursor: hasMore && selected.length > 0 ? String(selected[selected.length - 1].path) : void 0
975
+ };
976
+ }
977
+ async getMaxSeq() {
978
+ const [row, compactedBelow] = await Promise.all([this.conn.prepare("SELECT MAX(seq) as maxSeq FROM ds_changelog WHERE scope = ?").bind(this.scope).first(), this.getCompactedBelowSeq()]);
979
+ return Math.max(row?.maxSeq ?? 0, compactedBelow);
980
+ }
981
+ /**
982
+ * Read-auth reference check (design §4.1) — three-way, scoped, optionally
983
+ * prefix-local. See {@link TreeIndex.isReferenced}.
984
+ */
985
+ async isReferenced(cid, pathPrefix = "") {
986
+ if (!cid) return false;
987
+ if (pathPrefix === "") return await this.conn.prepare(`SELECT 1 FROM (
988
+ SELECT 1 FROM ds_entries WHERE scope = ? AND cid = ?
989
+ UNION ALL
990
+ SELECT 1 FROM ds_changelog WHERE scope = ? AND cid = ?
991
+ UNION ALL
992
+ SELECT 1 FROM ds_changelog WHERE scope = ? AND old_cid = ?
993
+ ) LIMIT 1`).bind(this.scope, cid, this.scope, cid, this.scope, cid).first() !== null;
994
+ const [lo, hi] = prefixRange(pathPrefix);
995
+ return await this.conn.prepare(`SELECT 1 FROM (
996
+ SELECT 1 FROM ds_entries WHERE scope = ? AND cid = ? AND path >= ? AND path < ?
997
+ UNION ALL
998
+ SELECT 1 FROM ds_changelog WHERE scope = ? AND cid = ? AND path >= ? AND path < ?
999
+ UNION ALL
1000
+ SELECT 1 FROM ds_changelog WHERE scope = ? AND old_cid = ? AND path >= ? AND path < ?
1001
+ ) LIMIT 1`).bind(this.scope, cid, lo, hi, this.scope, cid, lo, hi, this.scope, cid, lo, hi).first() !== null;
1002
+ }
1003
+ /**
1004
+ * Batch read-auth (design §7.2 / §14) — three-way-equivalent to per-cid
1005
+ * {@link isReferenced}, sharded under D1's 100-param cap (see
1006
+ * {@link D1_MANY_SHARD_SIZE}). Returns the referenced subset of `cids`.
1007
+ */
1008
+ async isReferencedMany(cids, pathPrefix = "") {
1009
+ const referenced = /* @__PURE__ */ new Set();
1010
+ const unique = [...new Set(cids.filter((c) => c))];
1011
+ if (unique.length === 0) return referenced;
1012
+ const usePrefix = pathPrefix !== "";
1013
+ const [lo, hi] = usePrefix ? prefixRange(pathPrefix) : ["", ""];
1014
+ for (let i = 0; i < unique.length; i += D1_MANY_SHARD_SIZE) {
1015
+ const shard = unique.slice(i, i + D1_MANY_SHARD_SIZE);
1016
+ const ph = shard.map(() => "?").join(",");
1017
+ const tail = usePrefix ? "AND path >= ? AND path < ?" : "";
1018
+ const sql = `SELECT cid AS c FROM ds_entries WHERE scope = ? AND cid IN (${ph}) ${tail}
1019
+ UNION
1020
+ SELECT cid AS c FROM ds_changelog WHERE scope = ? AND cid IN (${ph}) ${tail}
1021
+ UNION
1022
+ SELECT old_cid AS c FROM ds_changelog WHERE scope = ? AND old_cid IN (${ph}) ${tail}`;
1023
+ const bind = [];
1024
+ for (let b = 0; b < 3; b++) {
1025
+ bind.push(this.scope, ...shard);
1026
+ if (usePrefix) bind.push(lo, hi);
1027
+ }
1028
+ const { results } = await this.conn.prepare(sql).bind(...bind).all();
1029
+ for (const row of results) if (row.c) referenced.add(row.c);
1030
+ }
1031
+ return referenced;
1032
+ }
1033
+ async getChangelogSince(sinceSeq, limit) {
1034
+ const safeLimit = limit > 0 ? limit : 0;
1035
+ if (safeLimit === 0) return {
1036
+ entries: [],
1037
+ hasMore: false,
1038
+ cursor: sinceSeq
1039
+ };
1040
+ const { results } = await this.conn.prepare(`SELECT seq, scope, path, op, cid, old_cid, size, mtime, device_id
1041
+ FROM ds_changelog WHERE scope = ? AND seq > ? ORDER BY seq ASC LIMIT ?`).bind(this.scope, sinceSeq, safeLimit + 1).all();
1042
+ const hasMore = results.length > safeLimit;
1043
+ const page = hasMore ? results.slice(0, safeLimit) : results;
1044
+ return {
1045
+ entries: page,
1046
+ hasMore,
1047
+ cursor: page.length > 0 ? page[page.length - 1].seq : sinceSeq
1048
+ };
1049
+ }
1050
+ /**
1051
+ * Ready dirty rows under `prefix` for this scope. `next_attempt_at <= now`
1052
+ * skips poison rows still in backoff; the `(scope, next_attempt_at,
1053
+ * updated_at)` index serves the ordered seek. Reads go through `this.conn`, so
1054
+ * a follower constructed primary-bound (`useReplica:false`, Phase 3) gets
1055
+ * read-your-write; a replica-routed instance would return replica state — the
1056
+ * runtime is responsible for pinning primary for the dirty follower.
1057
+ */
1058
+ async listDirtyPaths(options) {
1059
+ const now = options.now ?? Date.now();
1060
+ const limit = options.limit > 0 ? options.limit : 0;
1061
+ if (limit === 0) return [];
1062
+ const [lo, hi] = prefixRange(options.prefix);
1063
+ const { results } = await this.conn.prepare(DIRTY_PATH_LIST_SQL).bind(this.scope, lo, hi, now, limit).all();
1064
+ return results.map((r) => ({
1065
+ path: r.path,
1066
+ latestSeq: Number(r.latest_seq),
1067
+ op: r.op,
1068
+ cid: r.cid,
1069
+ mtime: Number(r.mtime),
1070
+ attempts: Number(r.attempts)
1071
+ }));
1072
+ }
1073
+ /**
1074
+ * Guarded ack (design §8): delete the dirty row ONLY if its `latest_seq` is
1075
+ * still the one we processed. A concurrent write bumps latest_seq (trigger),
1076
+ * so a stale worker's DELETE affects 0 rows → returns false, and the newer
1077
+ * dirty row survives for the next tick. Writes go to primary (`this.db`).
1078
+ */
1079
+ async ackDirtyPath(path, latestSeq) {
1080
+ return ((await this.db.prepare(DIRTY_PATH_ACK_SQL).bind(this.scope, path, latestSeq).run()).meta?.changes ?? 0) > 0;
1081
+ }
1082
+ /**
1083
+ * Guarded failure record (design §8): bump attempts, stamp last_error +
1084
+ * next_attempt_at, SAME `latest_seq` guard so a fresh write's reset (trigger
1085
+ * sets attempts=0) is never clobbered by a late error from the prior attempt.
1086
+ */
1087
+ async markDirtyPathError(path, latestSeq, error, nextAttemptAt) {
1088
+ return ((await this.db.prepare(DIRTY_PATH_MARK_ERROR_SQL).bind(error, nextAttemptAt, this.scope, path, latestSeq).run()).meta?.changes ?? 0) > 0;
1089
+ }
1090
+ };
1091
+
1092
+ //#endregion
1093
+ export { D1TreeIndex, MAX_ENTRIES_PER_SCOPE, ancestorDirs, buildDirChainStmt, nameOf, parentOf };
1094
+ //# sourceMappingURL=d1-tree-index.mjs.map