@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,267 @@
1
+ import { resolveParentDirStamp } from "./timestamp.mjs";
2
+ import { parseTreeEntryMetadata } from "./tree-index.mjs";
3
+ import { parentOf } from "./d1-tree-index.mjs";
4
+
5
+ //#region src/ctime-backfill.ts
6
+ /**
7
+ * ctime-backfill — one-time backfill of `entries.ctime` from the application
8
+ * `createdAt` for data written BEFORE the feed-sort Rule R landed
9
+ * (planning/did-integration/feed-sort-createdat §7).
10
+ *
11
+ * Pre-fix, a FILE row's ctime was its insert time and its DIRECT-parent DIR row
12
+ * was created `INSERT OR IGNORE` with `ctime = Date.now()` and never updated.
13
+ * After Phase 3 switches the feed to `orderBy ctime`, those historical rows
14
+ * would sort by insert time. This sweep rewrites them to match what a fresh
15
+ * write would now stamp (R1 = file ctime, R2 = direct-parent DIR ctime/mtime).
16
+ *
17
+ * Design (mirrors inline-content-migration / scope-migration):
18
+ * - OFFLINE, code-driven. The core loop {@link backfillCtime} is backend-
19
+ * agnostic; the SQL lives in {@link CtimeBackfillBackend} adapters (D1 vs
20
+ * Node SQLite) so it is unit-testable against a mock D1.
21
+ * - **Host-language ISO parse** (`normalizeTs`) — NEVER SQLite `strftime`,
22
+ * which is unreliable for `Z`/offset/ms ISO-8601 (§7).
23
+ * - **WHERE pins `scope`** and binds scope/path as parameters — never a string
24
+ * interpolation; one scope is migrated at a time, never a cross-scope UPDATE.
25
+ * - **Deterministic `ORDER BY path ASC` keyset cursor** ⇒ for a degenerate
26
+ * DIR with several createdAt-bearing children, the LAST in path order wins
27
+ * (same LWW as the write path's R2 — NEVER MIN/MAX). Aside has exactly one
28
+ * createdAt child (`item.json`) per `{uuid}/`, so the result is deterministic.
29
+ * - **Idempotent**: each row is rewritten to `normalizeTs(createdAt)`, a fixed
30
+ * value, so a second pass writes the same value (0 effective change). The
31
+ * cursor advances past EVERY scanned row (including skips) so one invocation
32
+ * can't loop; a fresh invocation re-scans from the start.
33
+ * - **Only ctime/mtime change** — never cid/size/content/metadata. The FILE
34
+ * row's `mtime` (LWW arbiter) is NEVER touched; only its `ctime`. The DIR
35
+ * row gets both ctime+mtime (dir rows carry no LWW meaning, §1 fact 3).
36
+ * - **Batched / bounded** (`pageSize`, `maxPages`) to respect D1 write quota /
37
+ * avoid the O(N²) trap (cf. the D1 file_count burn-through lesson).
38
+ */
39
+ /** Run `fn` over `items` with at most `limit` in flight (single-threaded pool). */
40
+ async function mapPool(items, limit, fn) {
41
+ if (limit <= 1) {
42
+ for (const item of items) await fn(item);
43
+ return;
44
+ }
45
+ let i = 0;
46
+ const worker = async () => {
47
+ while (i < items.length) {
48
+ const item = items[i++];
49
+ if (item !== void 0) await fn(item);
50
+ }
51
+ };
52
+ await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));
53
+ }
54
+ /**
55
+ * Page over createdAt-bearing FILE rows in one scope and rewrite ctime (R1) +
56
+ * the direct-parent DIR stamp (R2). Returns stats; resumable across calls.
57
+ */
58
+ async function backfillCtime(backend, opts = {}) {
59
+ const pageSize = opts.pageSize ?? 200;
60
+ const maxPages = opts.maxPages ?? Number.POSITIVE_INFINITY;
61
+ const concurrency = Math.max(1, opts.concurrency ?? 1);
62
+ const dryRun = opts.dryRun ?? false;
63
+ const log = opts.log ?? (() => {});
64
+ const stats = {
65
+ scanned: 0,
66
+ stamped: 0,
67
+ alreadyCurrent: 0,
68
+ skippedNoCreatedAt: 0,
69
+ pages: 0,
70
+ lastCursor: "",
71
+ done: false
72
+ };
73
+ let cursor = "";
74
+ while (stats.pages < maxPages) {
75
+ const rows = await backend.selectCandidates(cursor, pageSize);
76
+ if (rows.length === 0) {
77
+ stats.done = true;
78
+ break;
79
+ }
80
+ stats.pages++;
81
+ const tasks = [];
82
+ for (const row of rows) {
83
+ stats.scanned++;
84
+ cursor = row.path;
85
+ const stamp = resolveParentDirStamp(parseTreeEntryMetadata(row.metadata));
86
+ if (!stamp) {
87
+ stats.skippedNoCreatedAt++;
88
+ continue;
89
+ }
90
+ tasks.push({
91
+ path: row.path,
92
+ ctime: stamp.ctime,
93
+ mtime: stamp.mtime
94
+ });
95
+ }
96
+ if (dryRun) stats.stamped += tasks.length;
97
+ else await mapPool(tasks, concurrency, async (t) => {
98
+ let changed = await backend.updateFileCtime(t.path, t.ctime);
99
+ const dir = parentOf(t.path);
100
+ if (dir) changed += await backend.updateDirStamp(dir, t.ctime, t.mtime);
101
+ if (changed > 0) stats.stamped++;
102
+ else stats.alreadyCurrent++;
103
+ });
104
+ stats.lastCursor = cursor;
105
+ log(`[ctime-backfill] page ${stats.pages}: scanned=${stats.scanned} stamped=${stats.stamped} cursor=${cursor}`);
106
+ }
107
+ return stats;
108
+ }
109
+ const SELECT_SQL = "SELECT path, metadata FROM ds_entries WHERE scope = ? AND kind = 'file' AND metadata IS NOT NULL AND path > ? ORDER BY path ASC LIMIT ?";
110
+ const UPDATE_FILE_SQL = "UPDATE ds_entries SET ctime = ? WHERE scope = ? AND path = ? AND kind = 'file' AND ctime != ?";
111
+ const UPDATE_DIR_SQL = "UPDATE ds_entries SET ctime = ?, mtime = ? WHERE scope = ? AND path = ? AND kind = 'dir' AND (ctime != ? OR mtime != ?)";
112
+ /** D1 (Cloudflare) backend adapter for one scope. */
113
+ function makeD1CtimeBackfillBackend(db, scope) {
114
+ return {
115
+ async selectCandidates(cursor, limit) {
116
+ return ((await db.prepare(SELECT_SQL).bind(scope, cursor, limit).all()).results ?? []).map((r) => ({
117
+ path: String(r.path),
118
+ metadata: r.metadata === null ? null : String(r.metadata)
119
+ }));
120
+ },
121
+ async updateFileCtime(path, ctime) {
122
+ return (await db.prepare(UPDATE_FILE_SQL).bind(ctime, scope, path, ctime).run()).meta?.changes ?? 0;
123
+ },
124
+ async updateDirStamp(dirPath, ctime, mtime) {
125
+ return (await db.prepare(UPDATE_DIR_SQL).bind(ctime, mtime, scope, dirPath, ctime, mtime).run()).meta?.changes ?? 0;
126
+ }
127
+ };
128
+ }
129
+ /** Node SQLite (libsql) backend adapter for one scope. */
130
+ function makeSqliteCtimeBackfillBackend(db, scope) {
131
+ return {
132
+ async selectCandidates(cursor, limit) {
133
+ return (await db.execute({
134
+ sql: SELECT_SQL,
135
+ args: [
136
+ scope,
137
+ cursor,
138
+ limit
139
+ ]
140
+ })).rows.map((r) => ({
141
+ path: String(r.path),
142
+ metadata: r.metadata === null ? null : String(r.metadata)
143
+ }));
144
+ },
145
+ async updateFileCtime(path, ctime) {
146
+ return (await db.execute({
147
+ sql: UPDATE_FILE_SQL,
148
+ args: [
149
+ ctime,
150
+ scope,
151
+ path,
152
+ ctime
153
+ ]
154
+ })).rowsAffected ?? 0;
155
+ },
156
+ async updateDirStamp(dirPath, ctime, mtime) {
157
+ return (await db.execute({
158
+ sql: UPDATE_DIR_SQL,
159
+ args: [
160
+ ctime,
161
+ mtime,
162
+ scope,
163
+ dirPath,
164
+ ctime,
165
+ mtime
166
+ ]
167
+ })).rowsAffected ?? 0;
168
+ }
169
+ };
170
+ }
171
+ const DISTINCT_SCOPES_SQL = "SELECT DISTINCT scope FROM ds_entries WHERE kind = 'file' AND metadata IS NOT NULL";
172
+ /** Enumerate scopes that have any createdAt-bearing file row (D1). */
173
+ async function distinctScopesWithMetadataD1(db) {
174
+ return ((await db.prepare(DISTINCT_SCOPES_SQL).bind().all()).results ?? []).map((r) => String(r.scope));
175
+ }
176
+ /** Enumerate scopes that have any createdAt-bearing file row (Node SQLite). */
177
+ async function distinctScopesWithMetadataSqlite(db) {
178
+ return (await db.execute({
179
+ sql: DISTINCT_SCOPES_SQL,
180
+ args: []
181
+ })).rows.map((r) => String(r.scope));
182
+ }
183
+ const defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
184
+ async function d1HttpQuery(cfg, sql, params) {
185
+ const f = cfg.fetchImpl ?? fetch;
186
+ const sleep = cfg.sleep ?? defaultSleep;
187
+ const maxRetries = cfg.maxRetries ?? 4;
188
+ const url = `https://api.cloudflare.com/client/v4/accounts/${cfg.accountId}/d1/database/${cfg.databaseId}/query`;
189
+ for (let attempt = 0;; attempt++) {
190
+ let status = 0;
191
+ let json;
192
+ let networkErr;
193
+ try {
194
+ const res = await f(url, {
195
+ method: "POST",
196
+ headers: {
197
+ authorization: `Bearer ${cfg.apiToken}`,
198
+ "content-type": "application/json"
199
+ },
200
+ body: JSON.stringify({
201
+ sql,
202
+ params
203
+ })
204
+ });
205
+ status = res.status;
206
+ json = await res.json();
207
+ } catch (e) {
208
+ networkErr = e;
209
+ }
210
+ if (!networkErr && status >= 200 && status < 300 && json?.success !== false) {
211
+ const r = json?.result?.[0];
212
+ return {
213
+ results: r?.results ?? [],
214
+ changes: r?.meta?.changes ?? 0
215
+ };
216
+ }
217
+ const transient = networkErr != null || status >= 500 || status === 429;
218
+ const msg = networkErr ? `D1 HTTP network error: ${String(networkErr).slice(0, 160)}` : `D1 HTTP ${status}: ${JSON.stringify(json?.errors ?? json).slice(0, 200)}`;
219
+ if (!transient || attempt >= maxRetries) throw new Error(msg);
220
+ await sleep(Math.min(2e3, 200 * 2 ** attempt));
221
+ }
222
+ }
223
+ /** CF D1 HTTP-API backend adapter for one scope (offline admin channel). */
224
+ function makeD1HttpCtimeBackfillBackend(cfg) {
225
+ return {
226
+ async selectCandidates(cursor, limit) {
227
+ const { results } = await d1HttpQuery(cfg, SELECT_SQL, [
228
+ cfg.scope,
229
+ cursor,
230
+ limit
231
+ ]);
232
+ return results.map((r) => ({
233
+ path: String(r.path),
234
+ metadata: r.metadata === null ? null : String(r.metadata)
235
+ }));
236
+ },
237
+ async updateFileCtime(path, ctime) {
238
+ const { changes } = await d1HttpQuery(cfg, UPDATE_FILE_SQL, [
239
+ ctime,
240
+ cfg.scope,
241
+ path,
242
+ ctime
243
+ ]);
244
+ return changes;
245
+ },
246
+ async updateDirStamp(dirPath, ctime, mtime) {
247
+ const { changes } = await d1HttpQuery(cfg, UPDATE_DIR_SQL, [
248
+ ctime,
249
+ mtime,
250
+ cfg.scope,
251
+ dirPath,
252
+ ctime,
253
+ mtime
254
+ ]);
255
+ return changes;
256
+ }
257
+ };
258
+ }
259
+ /** List distinct createdAt-bearing scopes on a CF D1 over HTTP (bulk sweep). */
260
+ async function distinctScopesWithMetadataD1Http(cfg) {
261
+ const { results } = await d1HttpQuery(cfg, DISTINCT_SCOPES_SQL, []);
262
+ return results.map((r) => String(r.scope));
263
+ }
264
+
265
+ //#endregion
266
+ export { backfillCtime, distinctScopesWithMetadataD1, distinctScopesWithMetadataD1Http, distinctScopesWithMetadataSqlite, makeD1CtimeBackfillBackend, makeD1HttpCtimeBackfillBackend, makeSqliteCtimeBackfillBackend };
267
+ //# sourceMappingURL=ctime-backfill.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ctime-backfill.mjs","names":[],"sources":["../src/ctime-backfill.ts"],"sourcesContent":["/**\n * ctime-backfill — one-time backfill of `entries.ctime` from the application\n * `createdAt` for data written BEFORE the feed-sort Rule R landed\n * (planning/did-integration/feed-sort-createdat §7).\n *\n * Pre-fix, a FILE row's ctime was its insert time and its DIRECT-parent DIR row\n * was created `INSERT OR IGNORE` with `ctime = Date.now()` and never updated.\n * After Phase 3 switches the feed to `orderBy ctime`, those historical rows\n * would sort by insert time. This sweep rewrites them to match what a fresh\n * write would now stamp (R1 = file ctime, R2 = direct-parent DIR ctime/mtime).\n *\n * Design (mirrors inline-content-migration / scope-migration):\n * - OFFLINE, code-driven. The core loop {@link backfillCtime} is backend-\n * agnostic; the SQL lives in {@link CtimeBackfillBackend} adapters (D1 vs\n * Node SQLite) so it is unit-testable against a mock D1.\n * - **Host-language ISO parse** (`normalizeTs`) — NEVER SQLite `strftime`,\n * which is unreliable for `Z`/offset/ms ISO-8601 (§7).\n * - **WHERE pins `scope`** and binds scope/path as parameters — never a string\n * interpolation; one scope is migrated at a time, never a cross-scope UPDATE.\n * - **Deterministic `ORDER BY path ASC` keyset cursor** ⇒ for a degenerate\n * DIR with several createdAt-bearing children, the LAST in path order wins\n * (same LWW as the write path's R2 — NEVER MIN/MAX). Aside has exactly one\n * createdAt child (`item.json`) per `{uuid}/`, so the result is deterministic.\n * - **Idempotent**: each row is rewritten to `normalizeTs(createdAt)`, a fixed\n * value, so a second pass writes the same value (0 effective change). The\n * cursor advances past EVERY scanned row (including skips) so one invocation\n * can't loop; a fresh invocation re-scans from the start.\n * - **Only ctime/mtime change** — never cid/size/content/metadata. The FILE\n * row's `mtime` (LWW arbiter) is NEVER touched; only its `ctime`. The DIR\n * row gets both ctime+mtime (dir rows carry no LWW meaning, §1 fact 3).\n * - **Batched / bounded** (`pageSize`, `maxPages`) to respect D1 write quota /\n * avoid the O(N²) trap (cf. the D1 file_count burn-through lesson).\n */\n\nimport { parentOf } from \"./d1-tree-index.js\";\nimport { resolveParentDirStamp } from \"./timestamp.js\";\nimport { parseTreeEntryMetadata } from \"./tree-index.js\";\n\n/** A FILE row with non-null metadata — a backfill candidate. */\nexport interface CtimeBackfillCandidate {\n path: string;\n /** Raw `entries.metadata` JSON blob (host-parsed here, never in SQL). */\n metadata: string | null;\n}\n\n/** Backend-specific SQL for the sweep (D1 vs Node SQLite). One scope each. */\nexport interface CtimeBackfillBackend {\n /**\n * Select up to `limit` FILE rows with `metadata IS NOT NULL` and\n * `path > cursor`, ordered `path ASC` (the keyset cursor). Empty ⇒ done.\n */\n selectCandidates(cursor: string, limit: number): Promise<CtimeBackfillCandidate[]>;\n /**\n * R1: set the FILE row's `ctime` (NEVER its mtime). The UPDATE is guarded\n * `AND ctime != ?` so an already-migrated row is a 0-row no-op (re-runs burn\n * no D1 write quota). Returns the number of rows actually changed (0 or 1).\n */\n updateFileCtime(path: string, ctime: number): Promise<number>;\n /**\n * R2: set the DIRECT-parent DIR row's `ctime` + `mtime`, guarded so an\n * already-current dir is a no-op. Returns rows actually changed (0 or 1).\n */\n updateDirStamp(dirPath: string, ctime: number, mtime: number): Promise<number>;\n}\n\nexport interface CtimeBackfillOptions {\n /** Rows per page (bounds the D1 write budget per invocation). Default 200. */\n pageSize?: number;\n /** Cap pages this invocation (cross-invocation paging). Default unbounded. */\n maxPages?: number;\n /**\n * Max concurrent UPDATE round-trips WITHIN a page. Default 1 (serial — the\n * deterministic reference behavior). >1 is a throughput win for the high-\n * latency D1-HTTP channel (each row = 2 sequential UPDATEs otherwise).\n *\n * ⚠️ Concurrency > 1 makes the direct-parent DIR stamp NON-deterministic for\n * a degenerate collection where MULTIPLE createdAt-bearing rows share one\n * direct parent (the `ORDER BY path` last-writer-wins guarantee only holds\n * serially). SAFE for dir-per-record collections (Aside: exactly one\n * createdAt child — `item.json` — per `{uuid}/`, no shared parent). Use 1\n * for strict determinism on degenerate shapes. Node SQLite (single\n * connection) gains nothing — keep it 1 there.\n */\n concurrency?: number;\n /**\n * Preview only — scan + count candidates, run NO UPDATE. `stats.stamped`\n * then reports rows that WOULD be stamped (an upper bound; the real run's\n * `AND ctime != ?` guard skips already-current rows as 0-write). `scanned`\n * and `skippedNoCreatedAt` are exact in both modes.\n */\n dryRun?: boolean;\n log?: (msg: string) => void;\n}\n\nexport interface CtimeBackfillStats {\n /** FILE rows examined (metadata IS NOT NULL). */\n scanned: number;\n /** Rows where a ctime/mtime value ACTUALLY changed (file and/or dir). */\n stamped: number;\n /**\n * Rows that had a valid createdAt but were already at the target value — a\n * 0-write no-op. On a clean second run this equals `scanned` and `stamped`\n * is 0 (the spec's \"第二趟 0 改动\" idempotency contract).\n */\n alreadyCurrent: number;\n /** Rows with metadata but no usable createdAt — left at insert time. */\n skippedNoCreatedAt: number;\n pages: number;\n lastCursor: string;\n /** True when a page came back empty (the sweep reached the end). */\n done: boolean;\n}\n\n/** Run `fn` over `items` with at most `limit` in flight (single-threaded pool). */\nasync function mapPool<T>(\n items: T[],\n limit: number,\n fn: (item: T) => Promise<void>,\n): Promise<void> {\n if (limit <= 1) {\n for (const item of items) await fn(item);\n return;\n }\n let i = 0;\n const worker = async () => {\n while (i < items.length) {\n const item = items[i++];\n if (item !== undefined) await fn(item);\n }\n };\n await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));\n}\n\n/**\n * Page over createdAt-bearing FILE rows in one scope and rewrite ctime (R1) +\n * the direct-parent DIR stamp (R2). Returns stats; resumable across calls.\n */\nexport async function backfillCtime(\n backend: CtimeBackfillBackend,\n opts: CtimeBackfillOptions = {},\n): Promise<CtimeBackfillStats> {\n const pageSize = opts.pageSize ?? 200;\n const maxPages = opts.maxPages ?? Number.POSITIVE_INFINITY;\n const concurrency = Math.max(1, opts.concurrency ?? 1);\n const dryRun = opts.dryRun ?? false;\n const log = opts.log ?? (() => {});\n\n const stats: CtimeBackfillStats = {\n scanned: 0,\n stamped: 0,\n alreadyCurrent: 0,\n skippedNoCreatedAt: 0,\n pages: 0,\n lastCursor: \"\",\n done: false,\n };\n\n let cursor = \"\";\n while (stats.pages < maxPages) {\n const rows = await backend.selectCandidates(cursor, pageSize);\n if (rows.length === 0) {\n stats.done = true;\n break;\n }\n stats.pages++;\n // Build the page's task list SERIALLY (preserves `ORDER BY path` for the\n // cursor + the degenerate-parent last-writer determinism at concurrency 1).\n const tasks: Array<{ path: string; ctime: number; mtime: number }> = [];\n for (const row of rows) {\n stats.scanned++;\n cursor = row.path; // advance past EVERY scanned row so a single pass can't loop\n // resolveParentDirStamp = the SAME R2 helper the write path uses:\n // { ctime: normalizeTs(createdAt), mtime: normalizeTs(updatedAt) ?? ctime }\n // or null when createdAt is missing/malformed (→ leave insert time).\n const stamp = resolveParentDirStamp(parseTreeEntryMetadata(row.metadata));\n if (!stamp) {\n stats.skippedNoCreatedAt++;\n continue;\n }\n tasks.push({ path: row.path, ctime: stamp.ctime, mtime: stamp.mtime });\n }\n\n if (dryRun) {\n stats.stamped += tasks.length; // would-stamp (no UPDATE issued)\n } else {\n await mapPool(tasks, concurrency, async (t) => {\n // R1: file ctime = normalizeTs(createdAt) (mtime untouched).\n let changed = await backend.updateFileCtime(t.path, t.ctime);\n // R2: direct parent only — never propagate to ancestors.\n const dir = parentOf(t.path);\n if (dir) changed += await backend.updateDirStamp(dir, t.ctime, t.mtime);\n // Guarded UPDATEs are 0-row no-ops when already at target → a clean\n // re-run changes nothing (spec §7: \"第二趟 0 改动\").\n if (changed > 0) stats.stamped++;\n else stats.alreadyCurrent++;\n });\n }\n // The cursor always ends at this page's last row — resumption is per-page.\n stats.lastCursor = cursor;\n log(\n `[ctime-backfill] page ${stats.pages}: scanned=${stats.scanned} stamped=${stats.stamped} cursor=${cursor}`,\n );\n }\n return stats;\n}\n\n// ── SQL (shared by both adapters; entries PK is (scope, path)) ──────────────\nconst SELECT_SQL =\n \"SELECT path, metadata FROM ds_entries WHERE scope = ? AND kind = 'file' \" +\n \"AND metadata IS NOT NULL AND path > ? ORDER BY path ASC LIMIT ?\";\n// File: ONLY ctime — mtime is the LWW arbiter and stays put. Guarded\n// `AND ctime != ?` so an already-migrated row is a 0-row no-op (idempotent\n// re-runs burn no write quota). Bind order: (ctime, scope, path, ctime).\nconst UPDATE_FILE_SQL =\n \"UPDATE ds_entries SET ctime = ? WHERE scope = ? AND path = ? AND kind = 'file' AND ctime != ?\";\n// Dir: ctime + mtime (dir rows carry no LWW meaning). Guarded so an already-\n// current dir is a no-op. Bind order: (ctime, mtime, scope, path, ctime, mtime).\nconst UPDATE_DIR_SQL =\n \"UPDATE ds_entries SET ctime = ?, mtime = ? WHERE scope = ? AND path = ? AND kind = 'dir' AND (ctime != ? OR mtime != ?)\";\n\n/** Minimal D1 shape used by the sweep (subset of `D1Database`). */\nexport interface D1Like {\n prepare(sql: string): {\n bind(...args: (string | number)[]): {\n all(): Promise<{ results?: unknown[] }>;\n run(): Promise<{ meta?: { changes?: number } }>;\n };\n };\n}\n\n/** D1 (Cloudflare) backend adapter for one scope. */\nexport function makeD1CtimeBackfillBackend(db: D1Like, scope: string): CtimeBackfillBackend {\n return {\n async selectCandidates(cursor, limit) {\n const res = await db.prepare(SELECT_SQL).bind(scope, cursor, limit).all();\n return ((res.results ?? []) as Array<Record<string, unknown>>).map((r) => ({\n path: String(r.path),\n metadata: r.metadata === null ? null : String(r.metadata),\n }));\n },\n async updateFileCtime(path, ctime) {\n const r = await db.prepare(UPDATE_FILE_SQL).bind(ctime, scope, path, ctime).run();\n return r.meta?.changes ?? 0;\n },\n async updateDirStamp(dirPath, ctime, mtime) {\n const r = await db\n .prepare(UPDATE_DIR_SQL)\n .bind(ctime, mtime, scope, dirPath, ctime, mtime)\n .run();\n return r.meta?.changes ?? 0;\n },\n };\n}\n\n/**\n * Minimal libsql client shape (Node `_metadata.db`). `args` narrowed to the\n * exact bound types so a real `@libsql/client` `Client` is assignable.\n */\nexport interface LibsqlLike {\n execute(stmt: { sql: string; args: (string | number)[] }): Promise<{\n rows: Array<Record<string, unknown>>;\n rowsAffected?: number;\n }>;\n}\n\n/** Node SQLite (libsql) backend adapter for one scope. */\nexport function makeSqliteCtimeBackfillBackend(\n db: LibsqlLike,\n scope: string,\n): CtimeBackfillBackend {\n return {\n async selectCandidates(cursor, limit) {\n const res = await db.execute({ sql: SELECT_SQL, args: [scope, cursor, limit] });\n return res.rows.map((r) => ({\n path: String(r.path),\n metadata: r.metadata === null ? null : String(r.metadata),\n }));\n },\n async updateFileCtime(path, ctime) {\n const r = await db.execute({ sql: UPDATE_FILE_SQL, args: [ctime, scope, path, ctime] });\n return r.rowsAffected ?? 0;\n },\n async updateDirStamp(dirPath, ctime, mtime) {\n const r = await db.execute({\n sql: UPDATE_DIR_SQL,\n args: [ctime, mtime, scope, dirPath, ctime, mtime],\n });\n return r.rowsAffected ?? 0;\n },\n };\n}\n\nconst DISTINCT_SCOPES_SQL =\n \"SELECT DISTINCT scope FROM ds_entries WHERE kind = 'file' AND metadata IS NOT NULL\";\n\n/** Enumerate scopes that have any createdAt-bearing file row (D1). */\nexport async function distinctScopesWithMetadataD1(db: D1Like): Promise<string[]> {\n const res = await db.prepare(DISTINCT_SCOPES_SQL).bind().all();\n return ((res.results ?? []) as Array<Record<string, unknown>>).map((r) => String(r.scope));\n}\n\n/** Enumerate scopes that have any createdAt-bearing file row (Node SQLite). */\nexport async function distinctScopesWithMetadataSqlite(db: LibsqlLike): Promise<string[]> {\n const res = await db.execute({ sql: DISTINCT_SCOPES_SQL, args: [] });\n return res.rows.map((r) => String(r.scope));\n}\n\n// ── CF D1 over the HTTP API (offline admin channel for staging/prod) ─────────\n/**\n * Config for the offline CF D1 HTTP backend: a local Node process holding a\n * Cloudflare API token with D1-edit runs the SAME paged sweep SQL the worker\n * would. No R2 involved — ctime backfill is pure index UPDATEs. `fetchImpl` /\n * `sleep` are injectable for tests.\n */\nexport interface CtimeBackfillD1HttpConfig {\n accountId: string;\n databaseId: string;\n apiToken: string;\n scope: string;\n fetchImpl?: typeof fetch;\n /** Bounded retries for TRANSIENT failures (5xx / 429 / network). Default 4. */\n maxRetries?: number;\n sleep?: (ms: number) => Promise<void>;\n}\n\nconst defaultSleep = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));\n\nasync function d1HttpQuery(\n cfg: Omit<CtimeBackfillD1HttpConfig, \"scope\">,\n sql: string,\n params: (string | number)[],\n): Promise<{ results: Array<Record<string, unknown>>; changes: number }> {\n const f = cfg.fetchImpl ?? fetch;\n const sleep = cfg.sleep ?? defaultSleep;\n const maxRetries = cfg.maxRetries ?? 4;\n const url = `https://api.cloudflare.com/client/v4/accounts/${cfg.accountId}/d1/database/${cfg.databaseId}/query`;\n for (let attempt = 0; ; attempt++) {\n let status = 0;\n let json:\n | {\n success?: boolean;\n errors?: unknown;\n result?: Array<{ results?: Array<Record<string, unknown>>; meta?: { changes?: number } }>;\n }\n | undefined;\n let networkErr: unknown;\n try {\n const res = await f(url, {\n method: \"POST\",\n headers: { authorization: `Bearer ${cfg.apiToken}`, \"content-type\": \"application/json\" },\n body: JSON.stringify({ sql, params }),\n });\n status = res.status;\n json = await res.json();\n } catch (e) {\n networkErr = e; // network/parse failure → transient\n }\n if (!networkErr && status >= 200 && status < 300 && json?.success !== false) {\n const r = json?.result?.[0];\n return { results: r?.results ?? [], changes: r?.meta?.changes ?? 0 };\n }\n // 5xx / 429 / network → transient (retry); 4xx (auth/SQL) → permanent.\n const transient = networkErr != null || status >= 500 || status === 429;\n const msg = networkErr\n ? `D1 HTTP network error: ${String(networkErr).slice(0, 160)}`\n : `D1 HTTP ${status}: ${JSON.stringify(json?.errors ?? json).slice(0, 200)}`;\n if (!transient || attempt >= maxRetries) throw new Error(msg);\n await sleep(Math.min(2000, 200 * 2 ** attempt));\n }\n}\n\n/** CF D1 HTTP-API backend adapter for one scope (offline admin channel). */\nexport function makeD1HttpCtimeBackfillBackend(\n cfg: CtimeBackfillD1HttpConfig,\n): CtimeBackfillBackend {\n return {\n async selectCandidates(cursor, limit) {\n const { results } = await d1HttpQuery(cfg, SELECT_SQL, [cfg.scope, cursor, limit]);\n return results.map((r) => ({\n path: String(r.path),\n metadata: r.metadata === null ? null : String(r.metadata),\n }));\n },\n async updateFileCtime(path, ctime) {\n const { changes } = await d1HttpQuery(cfg, UPDATE_FILE_SQL, [ctime, cfg.scope, path, ctime]);\n return changes;\n },\n async updateDirStamp(dirPath, ctime, mtime) {\n const { changes } = await d1HttpQuery(cfg, UPDATE_DIR_SQL, [\n ctime,\n mtime,\n cfg.scope,\n dirPath,\n ctime,\n mtime,\n ]);\n return changes;\n },\n };\n}\n\n/** List distinct createdAt-bearing scopes on a CF D1 over HTTP (bulk sweep). */\nexport async function distinctScopesWithMetadataD1Http(\n cfg: Omit<CtimeBackfillD1HttpConfig, \"scope\">,\n): Promise<string[]> {\n const { results } = await d1HttpQuery(cfg, DISTINCT_SCOPES_SQL, []);\n return results.map((r) => String(r.scope));\n}\n"],"mappings":"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"}
@@ -0,0 +1,80 @@
1
+ const require_d1_schema = require('./d1-schema.cjs');
2
+
3
+ //#region src/d1-gc-queue.ts
4
+ /**
5
+ * D1GCQueue — deferred garbage-collection queue backed by D1 (SQLite).
6
+ *
7
+ * When a file is overwritten or deleted, the old CID is enqueued here.
8
+ * A scheduled GC job later checks `entries` for live references before
9
+ * deleting the corresponding R2 object.
10
+ *
11
+ * Implements the pure-storage {@link GcQueue} interface (`ensureTable`,
12
+ * `enqueue`, `fetchCandidates`, `remove`). Liveness is decided separately:
13
+ * `runGc` uses the entries-only `referencedAnywhere` (design §3, §4.4b) and
14
+ * read-auth uses the scope-aware three-way `TreeIndex.isReferenced(Many)`.
15
+ * (The old scope-blind three-way `isReferenced` method that lived here — the
16
+ * only reader of the now-removed global `idx_changelog_cid`/`_old_cid` indexes
17
+ * — was retired with migration 0110; the `GcQueue` interface deliberately has
18
+ * no liveness method, §0.2.)
19
+ *
20
+ * Schema (migration 0004_gc_queue.sql):
21
+ * CREATE TABLE gc_queue (
22
+ * cid TEXT NOT NULL PRIMARY KEY,
23
+ * queued_at INTEGER NOT NULL
24
+ * )
25
+ */
26
+ var D1GCQueue = class {
27
+ constructor(db) {
28
+ this.db = db;
29
+ }
30
+ /**
31
+ * Create the gc_queue table. Used in tests with in-memory DB.
32
+ * Production: schema created by `wrangler d1 migrations apply`.
33
+ */
34
+ async ensureTable() {
35
+ await this.db.prepare(require_d1_schema.GC_QUEUE_TABLE_SQL).run();
36
+ for (const sql of require_d1_schema.GC_QUEUE_INDEX_SQL) await this.db.prepare(sql).run();
37
+ }
38
+ /**
39
+ * Enqueue CIDs for deferred GC. Idempotent — duplicate CIDs are silently ignored.
40
+ * Empty array is a no-op (no D1 calls made).
41
+ */
42
+ async enqueue(cids) {
43
+ if (cids.length === 0) return;
44
+ const now = Date.now();
45
+ const stmts = cids.map((cid) => this.db.prepare("INSERT INTO ds_gc_queue (cid, queued_at) VALUES (?, ?) ON CONFLICT DO NOTHING").bind(cid, now));
46
+ await this.db.batch(stmts);
47
+ }
48
+ /**
49
+ * Fetch GC candidates whose grace period has expired.
50
+ * @param limit Maximum number of candidates to return (default 500).
51
+ * @param graceMs Grace period in milliseconds (default 2 hours). Only CIDs
52
+ * enqueued more than graceMs ago are returned.
53
+ */
54
+ async fetchCandidates(limit = 500, graceMs = 7200 * 1e3) {
55
+ if (limit === 0) return [];
56
+ const { results } = await this.db.prepare("SELECT cid FROM ds_gc_queue WHERE queued_at <= ? LIMIT ?").bind(Date.now() - graceMs, limit).all();
57
+ return results;
58
+ }
59
+ /**
60
+ * Remove a CID from the queue after its R2 object has been deleted
61
+ * or after confirming it still has live references.
62
+ */
63
+ async remove(cid) {
64
+ await this.db.prepare("DELETE FROM ds_gc_queue WHERE cid = ?").bind(cid).run();
65
+ }
66
+ /** DELETE … IN, chunked at ≤90 binds (D1 limit) but issued as ONE
67
+ * db.batch round trip — afs-rpc-batch P3. */
68
+ async removeMany(cids) {
69
+ if (cids.length === 0) return;
70
+ const stmts = [];
71
+ for (let offset = 0; offset < cids.length; offset += 90) {
72
+ const chunk = cids.slice(offset, offset + 90);
73
+ stmts.push(this.db.prepare(`DELETE FROM ds_gc_queue WHERE cid IN (${chunk.map(() => "?").join(",")})`).bind(...chunk));
74
+ }
75
+ await this.db.batch(stmts);
76
+ }
77
+ };
78
+
79
+ //#endregion
80
+ exports.D1GCQueue = D1GCQueue;
@@ -0,0 +1,59 @@
1
+ import { GcQueue } from "./gc-queue.cjs";
2
+
3
+ //#region src/d1-gc-queue.d.ts
4
+ /**
5
+ * D1GCQueue — deferred garbage-collection queue backed by D1 (SQLite).
6
+ *
7
+ * When a file is overwritten or deleted, the old CID is enqueued here.
8
+ * A scheduled GC job later checks `entries` for live references before
9
+ * deleting the corresponding R2 object.
10
+ *
11
+ * Implements the pure-storage {@link GcQueue} interface (`ensureTable`,
12
+ * `enqueue`, `fetchCandidates`, `remove`). Liveness is decided separately:
13
+ * `runGc` uses the entries-only `referencedAnywhere` (design §3, §4.4b) and
14
+ * read-auth uses the scope-aware three-way `TreeIndex.isReferenced(Many)`.
15
+ * (The old scope-blind three-way `isReferenced` method that lived here — the
16
+ * only reader of the now-removed global `idx_changelog_cid`/`_old_cid` indexes
17
+ * — was retired with migration 0110; the `GcQueue` interface deliberately has
18
+ * no liveness method, §0.2.)
19
+ *
20
+ * Schema (migration 0004_gc_queue.sql):
21
+ * CREATE TABLE gc_queue (
22
+ * cid TEXT NOT NULL PRIMARY KEY,
23
+ * queued_at INTEGER NOT NULL
24
+ * )
25
+ */
26
+ declare class D1GCQueue implements GcQueue {
27
+ private readonly db;
28
+ constructor(db: D1Database);
29
+ /**
30
+ * Create the gc_queue table. Used in tests with in-memory DB.
31
+ * Production: schema created by `wrangler d1 migrations apply`.
32
+ */
33
+ ensureTable(): Promise<void>;
34
+ /**
35
+ * Enqueue CIDs for deferred GC. Idempotent — duplicate CIDs are silently ignored.
36
+ * Empty array is a no-op (no D1 calls made).
37
+ */
38
+ enqueue(cids: string[]): Promise<void>;
39
+ /**
40
+ * Fetch GC candidates whose grace period has expired.
41
+ * @param limit Maximum number of candidates to return (default 500).
42
+ * @param graceMs Grace period in milliseconds (default 2 hours). Only CIDs
43
+ * enqueued more than graceMs ago are returned.
44
+ */
45
+ fetchCandidates(limit?: number, graceMs?: number): Promise<Array<{
46
+ cid: string;
47
+ }>>;
48
+ /**
49
+ * Remove a CID from the queue after its R2 object has been deleted
50
+ * or after confirming it still has live references.
51
+ */
52
+ remove(cid: string): Promise<void>;
53
+ /** DELETE … IN, chunked at ≤90 binds (D1 limit) but issued as ONE
54
+ * db.batch round trip — afs-rpc-batch P3. */
55
+ removeMany(cids: string[]): Promise<void>;
56
+ }
57
+ //#endregion
58
+ export { D1GCQueue };
59
+ //# sourceMappingURL=d1-gc-queue.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"d1-gc-queue.d.cts","names":[],"sources":["../src/d1-gc-queue.ts"],"mappings":";;;;;AAyBA;;;;;;;;;;;;;;;;;;;;cAAa,SAAA,YAAqB,OAAA;EAAA,iBACH,EAAA;cAAA,EAAA,EAAI,UAAA;EAgC3B;;;;EA1BA,WAAA,CAAA,GAAe,OAAA;EA6BF;;;;EApBb,OAAA,CAAQ,IAAA,aAAiB,OAAA;EAuCd;;;;;;EAtBX,eAAA,CACJ,KAAA,WACA,OAAA,YACC,OAAA,CAAQ,KAAA;IAAQ,GAAA;EAAA;;;;;EAab,MAAA,CAAO,GAAA,WAAc,OAAA;;;EAMrB,UAAA,CAAW,IAAA,aAAiB,OAAA;AAAA"}
@@ -0,0 +1,59 @@
1
+ import { GcQueue } from "./gc-queue.mjs";
2
+
3
+ //#region src/d1-gc-queue.d.ts
4
+ /**
5
+ * D1GCQueue — deferred garbage-collection queue backed by D1 (SQLite).
6
+ *
7
+ * When a file is overwritten or deleted, the old CID is enqueued here.
8
+ * A scheduled GC job later checks `entries` for live references before
9
+ * deleting the corresponding R2 object.
10
+ *
11
+ * Implements the pure-storage {@link GcQueue} interface (`ensureTable`,
12
+ * `enqueue`, `fetchCandidates`, `remove`). Liveness is decided separately:
13
+ * `runGc` uses the entries-only `referencedAnywhere` (design §3, §4.4b) and
14
+ * read-auth uses the scope-aware three-way `TreeIndex.isReferenced(Many)`.
15
+ * (The old scope-blind three-way `isReferenced` method that lived here — the
16
+ * only reader of the now-removed global `idx_changelog_cid`/`_old_cid` indexes
17
+ * — was retired with migration 0110; the `GcQueue` interface deliberately has
18
+ * no liveness method, §0.2.)
19
+ *
20
+ * Schema (migration 0004_gc_queue.sql):
21
+ * CREATE TABLE gc_queue (
22
+ * cid TEXT NOT NULL PRIMARY KEY,
23
+ * queued_at INTEGER NOT NULL
24
+ * )
25
+ */
26
+ declare class D1GCQueue implements GcQueue {
27
+ private readonly db;
28
+ constructor(db: D1Database);
29
+ /**
30
+ * Create the gc_queue table. Used in tests with in-memory DB.
31
+ * Production: schema created by `wrangler d1 migrations apply`.
32
+ */
33
+ ensureTable(): Promise<void>;
34
+ /**
35
+ * Enqueue CIDs for deferred GC. Idempotent — duplicate CIDs are silently ignored.
36
+ * Empty array is a no-op (no D1 calls made).
37
+ */
38
+ enqueue(cids: string[]): Promise<void>;
39
+ /**
40
+ * Fetch GC candidates whose grace period has expired.
41
+ * @param limit Maximum number of candidates to return (default 500).
42
+ * @param graceMs Grace period in milliseconds (default 2 hours). Only CIDs
43
+ * enqueued more than graceMs ago are returned.
44
+ */
45
+ fetchCandidates(limit?: number, graceMs?: number): Promise<Array<{
46
+ cid: string;
47
+ }>>;
48
+ /**
49
+ * Remove a CID from the queue after its R2 object has been deleted
50
+ * or after confirming it still has live references.
51
+ */
52
+ remove(cid: string): Promise<void>;
53
+ /** DELETE … IN, chunked at ≤90 binds (D1 limit) but issued as ONE
54
+ * db.batch round trip — afs-rpc-batch P3. */
55
+ removeMany(cids: string[]): Promise<void>;
56
+ }
57
+ //#endregion
58
+ export { D1GCQueue };
59
+ //# sourceMappingURL=d1-gc-queue.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"d1-gc-queue.d.mts","names":[],"sources":["../src/d1-gc-queue.ts"],"mappings":";;;;;AAyBA;;;;;;;;;;;;;;;;;;;;cAAa,SAAA,YAAqB,OAAA;EAAA,iBACH,EAAA;cAAA,EAAA,EAAI,UAAA;EAgC3B;;;;EA1BA,WAAA,CAAA,GAAe,OAAA;EA6BF;;;;EApBb,OAAA,CAAQ,IAAA,aAAiB,OAAA;EAuCd;;;;;;EAtBX,eAAA,CACJ,KAAA,WACA,OAAA,YACC,OAAA,CAAQ,KAAA;IAAQ,GAAA;EAAA;;;;;EAab,MAAA,CAAO,GAAA,WAAc,OAAA;;;EAMrB,UAAA,CAAW,IAAA,aAAiB,OAAA;AAAA"}
@@ -0,0 +1,81 @@
1
+ import { GC_QUEUE_INDEX_SQL, GC_QUEUE_TABLE_SQL } from "./d1-schema.mjs";
2
+
3
+ //#region src/d1-gc-queue.ts
4
+ /**
5
+ * D1GCQueue — deferred garbage-collection queue backed by D1 (SQLite).
6
+ *
7
+ * When a file is overwritten or deleted, the old CID is enqueued here.
8
+ * A scheduled GC job later checks `entries` for live references before
9
+ * deleting the corresponding R2 object.
10
+ *
11
+ * Implements the pure-storage {@link GcQueue} interface (`ensureTable`,
12
+ * `enqueue`, `fetchCandidates`, `remove`). Liveness is decided separately:
13
+ * `runGc` uses the entries-only `referencedAnywhere` (design §3, §4.4b) and
14
+ * read-auth uses the scope-aware three-way `TreeIndex.isReferenced(Many)`.
15
+ * (The old scope-blind three-way `isReferenced` method that lived here — the
16
+ * only reader of the now-removed global `idx_changelog_cid`/`_old_cid` indexes
17
+ * — was retired with migration 0110; the `GcQueue` interface deliberately has
18
+ * no liveness method, §0.2.)
19
+ *
20
+ * Schema (migration 0004_gc_queue.sql):
21
+ * CREATE TABLE gc_queue (
22
+ * cid TEXT NOT NULL PRIMARY KEY,
23
+ * queued_at INTEGER NOT NULL
24
+ * )
25
+ */
26
+ var D1GCQueue = class {
27
+ constructor(db) {
28
+ this.db = db;
29
+ }
30
+ /**
31
+ * Create the gc_queue table. Used in tests with in-memory DB.
32
+ * Production: schema created by `wrangler d1 migrations apply`.
33
+ */
34
+ async ensureTable() {
35
+ await this.db.prepare(GC_QUEUE_TABLE_SQL).run();
36
+ for (const sql of GC_QUEUE_INDEX_SQL) await this.db.prepare(sql).run();
37
+ }
38
+ /**
39
+ * Enqueue CIDs for deferred GC. Idempotent — duplicate CIDs are silently ignored.
40
+ * Empty array is a no-op (no D1 calls made).
41
+ */
42
+ async enqueue(cids) {
43
+ if (cids.length === 0) return;
44
+ const now = Date.now();
45
+ const stmts = cids.map((cid) => this.db.prepare("INSERT INTO ds_gc_queue (cid, queued_at) VALUES (?, ?) ON CONFLICT DO NOTHING").bind(cid, now));
46
+ await this.db.batch(stmts);
47
+ }
48
+ /**
49
+ * Fetch GC candidates whose grace period has expired.
50
+ * @param limit Maximum number of candidates to return (default 500).
51
+ * @param graceMs Grace period in milliseconds (default 2 hours). Only CIDs
52
+ * enqueued more than graceMs ago are returned.
53
+ */
54
+ async fetchCandidates(limit = 500, graceMs = 7200 * 1e3) {
55
+ if (limit === 0) return [];
56
+ const { results } = await this.db.prepare("SELECT cid FROM ds_gc_queue WHERE queued_at <= ? LIMIT ?").bind(Date.now() - graceMs, limit).all();
57
+ return results;
58
+ }
59
+ /**
60
+ * Remove a CID from the queue after its R2 object has been deleted
61
+ * or after confirming it still has live references.
62
+ */
63
+ async remove(cid) {
64
+ await this.db.prepare("DELETE FROM ds_gc_queue WHERE cid = ?").bind(cid).run();
65
+ }
66
+ /** DELETE … IN, chunked at ≤90 binds (D1 limit) but issued as ONE
67
+ * db.batch round trip — afs-rpc-batch P3. */
68
+ async removeMany(cids) {
69
+ if (cids.length === 0) return;
70
+ const stmts = [];
71
+ for (let offset = 0; offset < cids.length; offset += 90) {
72
+ const chunk = cids.slice(offset, offset + 90);
73
+ stmts.push(this.db.prepare(`DELETE FROM ds_gc_queue WHERE cid IN (${chunk.map(() => "?").join(",")})`).bind(...chunk));
74
+ }
75
+ await this.db.batch(stmts);
76
+ }
77
+ };
78
+
79
+ //#endregion
80
+ export { D1GCQueue };
81
+ //# sourceMappingURL=d1-gc-queue.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"d1-gc-queue.mjs","names":[],"sources":["../src/d1-gc-queue.ts"],"sourcesContent":["import { GC_QUEUE_INDEX_SQL, GC_QUEUE_TABLE_SQL } from \"./d1-schema.js\";\nimport type { GcQueue } from \"./gc-queue.js\";\n\n/**\n * D1GCQueue — deferred garbage-collection queue backed by D1 (SQLite).\n *\n * When a file is overwritten or deleted, the old CID is enqueued here.\n * A scheduled GC job later checks `entries` for live references before\n * deleting the corresponding R2 object.\n *\n * Implements the pure-storage {@link GcQueue} interface (`ensureTable`,\n * `enqueue`, `fetchCandidates`, `remove`). Liveness is decided separately:\n * `runGc` uses the entries-only `referencedAnywhere` (design §3, §4.4b) and\n * read-auth uses the scope-aware three-way `TreeIndex.isReferenced(Many)`.\n * (The old scope-blind three-way `isReferenced` method that lived here — the\n * only reader of the now-removed global `idx_changelog_cid`/`_old_cid` indexes\n * — was retired with migration 0110; the `GcQueue` interface deliberately has\n * no liveness method, §0.2.)\n *\n * Schema (migration 0004_gc_queue.sql):\n * CREATE TABLE gc_queue (\n * cid TEXT NOT NULL PRIMARY KEY,\n * queued_at INTEGER NOT NULL\n * )\n */\nexport class D1GCQueue implements GcQueue {\n constructor(private readonly db: D1Database) {}\n\n /**\n * Create the gc_queue table. Used in tests with in-memory DB.\n * Production: schema created by `wrangler d1 migrations apply`.\n */\n async ensureTable(): Promise<void> {\n await this.db.prepare(GC_QUEUE_TABLE_SQL).run();\n for (const sql of GC_QUEUE_INDEX_SQL) await this.db.prepare(sql).run();\n }\n\n /**\n * Enqueue CIDs for deferred GC. Idempotent — duplicate CIDs are silently ignored.\n * Empty array is a no-op (no D1 calls made).\n */\n async enqueue(cids: string[]): Promise<void> {\n if (cids.length === 0) return;\n const now = Date.now();\n const stmts = cids.map((cid) =>\n this.db\n .prepare(\"INSERT INTO ds_gc_queue (cid, queued_at) VALUES (?, ?) ON CONFLICT DO NOTHING\")\n .bind(cid, now),\n );\n await this.db.batch(stmts);\n }\n\n /**\n * Fetch GC candidates whose grace period has expired.\n * @param limit Maximum number of candidates to return (default 500).\n * @param graceMs Grace period in milliseconds (default 2 hours). Only CIDs\n * enqueued more than graceMs ago are returned.\n */\n async fetchCandidates(\n limit = 500,\n graceMs = 2 * 60 * 60 * 1000,\n ): Promise<Array<{ cid: string }>> {\n if (limit === 0) return [];\n const { results } = await this.db\n .prepare(\"SELECT cid FROM ds_gc_queue WHERE queued_at <= ? LIMIT ?\")\n .bind(Date.now() - graceMs, limit)\n .all<{ cid: string }>();\n return results;\n }\n\n /**\n * Remove a CID from the queue after its R2 object has been deleted\n * or after confirming it still has live references.\n */\n async remove(cid: string): Promise<void> {\n await this.db.prepare(\"DELETE FROM ds_gc_queue WHERE cid = ?\").bind(cid).run();\n }\n\n /** DELETE … IN, chunked at ≤90 binds (D1 limit) but issued as ONE\n * db.batch round trip — afs-rpc-batch P3. */\n async removeMany(cids: string[]): Promise<void> {\n if (cids.length === 0) return;\n const stmts: D1PreparedStatement[] = [];\n for (let offset = 0; offset < cids.length; offset += 90) {\n const chunk = cids.slice(offset, offset + 90);\n stmts.push(\n this.db\n .prepare(`DELETE FROM ds_gc_queue WHERE cid IN (${chunk.map(() => \"?\").join(\",\")})`)\n .bind(...chunk),\n );\n }\n await this.db.batch(stmts);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAyBA,IAAa,YAAb,MAA0C;CACxC,YAAY,AAAiB,IAAgB;EAAhB;;;;;;CAM7B,MAAM,cAA6B;AACjC,QAAM,KAAK,GAAG,QAAQ,mBAAmB,CAAC,KAAK;AAC/C,OAAK,MAAM,OAAO,mBAAoB,OAAM,KAAK,GAAG,QAAQ,IAAI,CAAC,KAAK;;;;;;CAOxE,MAAM,QAAQ,MAA+B;AAC3C,MAAI,KAAK,WAAW,EAAG;EACvB,MAAM,MAAM,KAAK,KAAK;EACtB,MAAM,QAAQ,KAAK,KAAK,QACtB,KAAK,GACF,QAAQ,gFAAgF,CACxF,KAAK,KAAK,IAAI,CAClB;AACD,QAAM,KAAK,GAAG,MAAM,MAAM;;;;;;;;CAS5B,MAAM,gBACJ,QAAQ,KACR,UAAU,OAAc,KACS;AACjC,MAAI,UAAU,EAAG,QAAO,EAAE;EAC1B,MAAM,EAAE,YAAY,MAAM,KAAK,GAC5B,QAAQ,2DAA2D,CACnE,KAAK,KAAK,KAAK,GAAG,SAAS,MAAM,CACjC,KAAsB;AACzB,SAAO;;;;;;CAOT,MAAM,OAAO,KAA4B;AACvC,QAAM,KAAK,GAAG,QAAQ,wCAAwC,CAAC,KAAK,IAAI,CAAC,KAAK;;;;CAKhF,MAAM,WAAW,MAA+B;AAC9C,MAAI,KAAK,WAAW,EAAG;EACvB,MAAM,QAA+B,EAAE;AACvC,OAAK,IAAI,SAAS,GAAG,SAAS,KAAK,QAAQ,UAAU,IAAI;GACvD,MAAM,QAAQ,KAAK,MAAM,QAAQ,SAAS,GAAG;AAC7C,SAAM,KACJ,KAAK,GACF,QAAQ,yCAAyC,MAAM,UAAU,IAAI,CAAC,KAAK,IAAI,CAAC,GAAG,CACnF,KAAK,GAAG,MAAM,CAClB;;AAEH,QAAM,KAAK,GAAG,MAAM,MAAM"}