@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 @@
1
+ {"version":3,"file":"data-backend.mjs","names":[],"sources":["../src/data-backend.ts"],"sourcesContent":["/**\n * DIDSpaceBlockletDataBackend — BlockletDataBackend backed by DID Space.\n *\n * Storage layout per blocklet app space ({blockletId}):\n * system/ — blocklet content tree (role root, mounted at `/`)\n * config/mounts.toml — per-instance mount overrides (sibling fragment via getConfigSpace)\n */\n\nimport type { AFS, BlockletDataBackend, MountOverride } from \"@aigne/afs\";\nimport type { DIDSpaceLike } from \"./did-space-like.js\";\nimport { normalizeId } from \"./did-utils.js\";\n\nexport interface DIDSpaceBlockletDataBackendOptions {\n didSpace: DIDSpaceLike;\n}\n\nexport class DIDSpaceBlockletDataBackend implements BlockletDataBackend {\n private readonly didSpace: DIDSpaceLike;\n\n constructor(options: DIDSpaceBlockletDataBackendOptions) {\n this.didSpace = options.didSpace;\n }\n\n /** Resolve the system-role instance space (content tree, mounted at `/`). */\n private instanceSpace(blockletDid: string): Promise<AFS> {\n return this.didSpace.getInstanceSpace({\n instanceDid: normalizeId(blockletDid),\n role: \"system\",\n });\n }\n\n /**\n * Resolve the runtime-internal config fragment root (`/`), or null if the\n * backend has none. /files-prefix-removal: config moved out of the role-AFS\n * into a sibling fragment, reached via `getConfigSpace` instead of the old\n * `system/config` sub-mount.\n */\n private async configSpace(blockletDid: string): Promise<AFS | null> {\n if (typeof this.didSpace.getConfigSpace !== \"function\") return null;\n return this.didSpace.getConfigSpace(normalizeId(blockletDid));\n }\n\n // ── Data management ──\n\n async getInstanceAFS(blockletDid: string): Promise<AFS> {\n return this.instanceSpace(blockletDid);\n }\n\n async removeData(blockletDid: string): Promise<void> {\n const appAFS = await this.instanceSpace(blockletDid);\n\n try {\n const result = await appAFS.list(\"/\", { maxDepth: 99 });\n const files = result.data.filter(\n (e) => e.meta?.childrenCount === undefined || e.meta.childrenCount === null,\n );\n for (const file of files) {\n try {\n await appAFS.delete!(file.path);\n } catch {\n // Best-effort\n }\n }\n } catch {\n // No files — ok\n }\n }\n\n // ── Mount overrides ──\n\n async readMountOverrides(blockletDid: string): Promise<MountOverride[]> {\n const cfg = await this.configSpace(blockletDid);\n if (!cfg) return [];\n const configPath = \"/mounts.toml\";\n\n try {\n const result = await cfg.read!(configPath);\n const content = String(result.data?.content ?? \"\");\n if (!content.trim()) return [];\n\n return this.parseMountsToml(content);\n } catch {\n return [];\n }\n }\n\n async writeMountOverrides(blockletDid: string, overrides: MountOverride[]): Promise<void> {\n if (overrides.length === 0) return;\n\n const cfg = await this.configSpace(blockletDid);\n if (!cfg) return;\n const configPath = \"/mounts.toml\";\n\n // Merge with existing\n const existing = await this.readMountOverrides(blockletDid);\n const byTarget = new Map(existing.map((o) => [o.target, o]));\n\n for (const override of overrides) {\n const entry: MountOverride = { target: override.target, uri: override.uri };\n if (override.options && Object.keys(override.options).length > 0) {\n entry.options = { ...override.options };\n }\n byTarget.set(override.target, entry);\n }\n\n const content = this.stringifyMountsToml([...byTarget.values()]);\n await cfg.write!(configPath, { content });\n }\n\n async removeMountOverrides(blockletDid: string, targets: string[]): Promise<void> {\n if (targets.length === 0) return;\n\n const targetSet = new Set(targets);\n const remaining = (await this.readMountOverrides(blockletDid)).filter(\n (override) => !targetSet.has(override.target),\n );\n const cfg = await this.configSpace(blockletDid);\n if (!cfg) return;\n const configPath = \"/mounts.toml\";\n\n if (remaining.length === 0) {\n try {\n await cfg.delete!(configPath);\n return;\n } catch {\n // Some backends may not support delete; an empty file is equivalent.\n }\n }\n\n await cfg.write!(configPath, { content: this.stringifyMountsToml(remaining) });\n }\n\n // ── Private helpers ──\n\n private parseMountsToml(content: string): MountOverride[] {\n try {\n const result: MountOverride[] = [];\n const lines = content.split(\"\\n\");\n let current: Partial<MountOverride> | null = null;\n let inOptions = false;\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (trimmed === \"[[mounts]]\") {\n if (current?.target && current.uri) {\n result.push({\n target: current.target,\n uri: current.uri,\n ...(current.options ? { options: current.options } : {}),\n });\n }\n current = {};\n inOptions = false;\n continue;\n }\n if (!current) continue;\n if (trimmed === \"[mounts.options]\") {\n inOptions = true;\n if (!current.options) current.options = {};\n continue;\n }\n\n // Parse key = value (string, number, boolean, array)\n const match = trimmed.match(/^(\\w+)\\s*=\\s*(.+)$/);\n if (match) {\n const [, key, rawVal] = match;\n let value: unknown;\n const v = rawVal!.trim();\n if (v.startsWith('\"') && v.endsWith('\"')) value = v.slice(1, -1);\n else if (v === \"true\") value = true;\n else if (v === \"false\") value = false;\n else if (v.startsWith(\"[\") && v.endsWith(\"]\")) {\n value = v\n .slice(1, -1)\n .split(\",\")\n .map((s) => s.trim().replace(/^\"|\"$/g, \"\"))\n .filter(Boolean);\n } else if (!Number.isNaN(Number(v))) value = Number(v);\n else value = v;\n\n if (inOptions && current.options) {\n current.options[key!] = value;\n } else {\n if (key === \"path\") current.target = value as string;\n else if (key === \"uri\") current.uri = value as string;\n }\n }\n }\n if (current?.target && current.uri) {\n result.push({\n target: current.target,\n uri: current.uri,\n ...(current.options ? { options: current.options } : {}),\n });\n }\n return result;\n } catch {\n return [];\n }\n }\n\n private stringifyMountsToml(overrides: MountOverride[]): string {\n return overrides\n .map((o) => {\n let block = `[[mounts]]\\npath = \"${o.target}\"\\nuri = \"${o.uri}\"`;\n if (o.options && Object.keys(o.options).length > 0) {\n block += \"\\n\\n[mounts.options]\";\n for (const [k, v] of Object.entries(o.options)) {\n if (typeof v === \"string\") block += `\\n${k} = \"${v}\"`;\n else if (typeof v === \"number\" || typeof v === \"boolean\") block += `\\n${k} = ${v}`;\n else if (Array.isArray(v)) block += `\\n${k} = [${v.map((i) => `\"${i}\"`).join(\", \")}]`;\n }\n }\n return block;\n })\n .join(\"\\n\\n\");\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,278 @@
1
+ const require_url_normalize = require('./url-normalize.cjs');
2
+
3
+ //#region src/dedup-backfill.ts
4
+ /**
5
+ * dedup-backfill — one-time backfill of the dedup-on-write secondary index
6
+ * (`ds_entries.dedup_scope` / `dedup_key`) for records written BEFORE #1159
7
+ * landed (planning: dedup-on-write, issue #1159).
8
+ *
9
+ * Pre-#1159 the dedup entry lived at `{collection}/{cid(url)}.json` and had no
10
+ * `dedup_key` column; older layouts (`{slug}.json`, `{uuid}/item.json`) never
11
+ * had one either. After #1159 dedup resolves through the
12
+ * `(scope, dedup_scope, dedup_key)` UNIQUE index, so those historical rows are
13
+ * INVISIBLE to `findByDedupKey` — an already-saved URL's first re-save creates
14
+ * a duplicate. This sweep populates their columns so a re-save merges in place.
15
+ *
16
+ * Design (mirrors ctime-backfill / inline-content-migration):
17
+ * - OFFLINE, code-driven. The core loop {@link backfillDedup} is backend-
18
+ * agnostic; the SQL lives in {@link DedupBackfillBackend} adapters (D1 binding
19
+ * vs Node SQLite vs the CF D1 HTTP API) so it is unit-testable against a mock.
20
+ * - **Host-language normalization** — the `dedup_key` is
21
+ * `normalizeItemUrl(record[dedupBy])`, computed in JS with the SAME function
22
+ * the write path uses. It CANNOT be done in SQL (`json_extract` would store
23
+ * the RAW url, which would never match the Worker's normalized lookups), so
24
+ * the sweep MUST read the record body (`content` column) and normalize here.
25
+ * - **JS uniqueness pre-filter, never DB rejection.** The `(scope, dedup_scope,
26
+ * dedup_key)` UNIQUE index means two rows can't share a key. A `seen` set —
27
+ * seeded from the keys ALREADY indexed (e.g. entries a post-#1159 client
28
+ * already wrote) and grown as the sweep assigns — decides the winner in JS so
29
+ * NO `UPDATE` ever violates the constraint. This matters over the D1 HTTP
30
+ * channel where a constraint error is a 4xx that would abort the whole run.
31
+ * A genuine pre-existing duplicate is left un-indexed (reported), NEVER merged
32
+ * (the tool does not touch content).
33
+ * - **WHERE pins `scope` + the collection prefix**, binds everything as
34
+ * parameters — never a cross-scope / cross-collection UPDATE.
35
+ * - **Deterministic `ORDER BY path ASC` keyset cursor** — resumable across
36
+ * invocations; among genuine duplicates the FIRST in path order wins the key.
37
+ * - **Idempotent**: `UPDATE … WHERE dedup_key IS NULL` — a re-run never
38
+ * re-touches an already-populated row (0 write). The cursor advances past
39
+ * EVERY scanned row so one invocation can't loop.
40
+ * - **Only dedup_scope/dedup_key change** — never cid/size/content/mtime/ctime
41
+ * (index-only; no changelog / no sync fan-out).
42
+ */
43
+ /** Extract the record body's `dedupBy` field as a non-empty string, or null. */
44
+ function extractKeyField(content, dedupBy) {
45
+ if (content == null) return null;
46
+ let body;
47
+ try {
48
+ body = JSON.parse(content);
49
+ } catch {
50
+ return null;
51
+ }
52
+ if (body === null || typeof body !== "object" || Array.isArray(body)) return null;
53
+ const v = body[dedupBy];
54
+ return typeof v === "string" && v.length > 0 ? v : null;
55
+ }
56
+ /**
57
+ * Page over NULL-`dedup_key` FILE rows in one `(scope, collection)` and populate
58
+ * their dedup columns. Returns stats; resumable across calls (a fresh call
59
+ * re-seeds `seen` from the DB, so previously-indexed keys stay winners).
60
+ */
61
+ async function backfillDedup(backend, opts) {
62
+ const pageSize = opts.pageSize ?? 100;
63
+ const maxPages = opts.maxPages ?? Number.POSITIVE_INFINITY;
64
+ const dryRun = opts.dryRun ?? false;
65
+ const log = opts.log ?? (() => {});
66
+ const stats = {
67
+ scanned: 0,
68
+ indexed: 0,
69
+ skippedNoContent: 0,
70
+ skippedNoField: 0,
71
+ skippedDuplicate: 0,
72
+ pages: 0,
73
+ lastCursor: "",
74
+ done: false
75
+ };
76
+ const seen = new Set(await backend.existingKeys());
77
+ let cursor = "";
78
+ while (stats.pages < maxPages) {
79
+ const rows = await backend.selectCandidates(cursor, pageSize);
80
+ if (rows.length === 0) {
81
+ stats.done = true;
82
+ break;
83
+ }
84
+ stats.pages++;
85
+ for (const row of rows) {
86
+ stats.scanned++;
87
+ cursor = row.path;
88
+ const raw = extractKeyField(row.content, opts.dedupBy);
89
+ if (raw === null) {
90
+ if (row.content == null) stats.skippedNoContent++;
91
+ else stats.skippedNoField++;
92
+ continue;
93
+ }
94
+ const key = require_url_normalize.normalizeItemUrl(raw);
95
+ if (seen.has(key)) {
96
+ stats.skippedDuplicate++;
97
+ continue;
98
+ }
99
+ seen.add(key);
100
+ if (dryRun) {
101
+ stats.indexed++;
102
+ continue;
103
+ }
104
+ if (await backend.setDedupKey(row.path, key) > 0) stats.indexed++;
105
+ }
106
+ stats.lastCursor = cursor;
107
+ log(`[dedup-backfill] page ${stats.pages}: scanned=${stats.scanned} indexed=${stats.indexed} dup=${stats.skippedDuplicate} cursor=${cursor}`);
108
+ }
109
+ return stats;
110
+ }
111
+ const SELECT_SQL = "SELECT path, content FROM ds_entries WHERE scope = ? AND kind = 'file' AND dedup_key IS NULL AND path > ? AND substr(path, 1, ?) = ? ORDER BY path ASC LIMIT ?";
112
+ const EXISTING_KEYS_SQL = "SELECT dedup_key FROM ds_entries WHERE scope = ? AND dedup_scope = ? AND dedup_key IS NOT NULL";
113
+ const UPDATE_SQL = "UPDATE ds_entries SET dedup_scope = ?, dedup_key = ? WHERE scope = ? AND path = ? AND kind = 'file' AND dedup_key IS NULL";
114
+ const DISTINCT_SCOPES_SQL = "SELECT DISTINCT scope FROM ds_entries WHERE kind = 'file' AND dedup_key IS NULL AND substr(path, 1, ?) = ?";
115
+ /** The `path` prefix (`{collection}/`) + its length, for the substr filter. */
116
+ function collectionPrefix(collection) {
117
+ const prefix = collection.endsWith("/") ? collection : `${collection}/`;
118
+ return {
119
+ prefix,
120
+ len: prefix.length
121
+ };
122
+ }
123
+ /** D1 (Cloudflare binding) backend adapter for one (scope, collection). */
124
+ function makeD1DedupBackfillBackend(db, scope, collection) {
125
+ const { prefix, len } = collectionPrefix(collection);
126
+ return {
127
+ async existingKeys() {
128
+ return ((await db.prepare(EXISTING_KEYS_SQL).bind(scope, collection).all()).results ?? []).map((r) => String(r.dedup_key));
129
+ },
130
+ async selectCandidates(cursor, limit) {
131
+ return ((await db.prepare(SELECT_SQL).bind(scope, cursor, len, prefix, limit).all()).results ?? []).map((r) => ({
132
+ path: String(r.path),
133
+ content: r.content === null || r.content === void 0 ? null : String(r.content)
134
+ }));
135
+ },
136
+ async setDedupKey(path, dedupKey) {
137
+ return (await db.prepare(UPDATE_SQL).bind(collection, dedupKey, scope, path).run()).meta?.changes ?? 0;
138
+ }
139
+ };
140
+ }
141
+ /** Node SQLite (libsql) backend adapter for one (scope, collection). */
142
+ function makeSqliteDedupBackfillBackend(db, scope, collection) {
143
+ const { prefix, len } = collectionPrefix(collection);
144
+ return {
145
+ async existingKeys() {
146
+ return (await db.execute({
147
+ sql: EXISTING_KEYS_SQL,
148
+ args: [scope, collection]
149
+ })).rows.map((r) => String(r.dedup_key));
150
+ },
151
+ async selectCandidates(cursor, limit) {
152
+ return (await db.execute({
153
+ sql: SELECT_SQL,
154
+ args: [
155
+ scope,
156
+ cursor,
157
+ len,
158
+ prefix,
159
+ limit
160
+ ]
161
+ })).rows.map((r) => ({
162
+ path: String(r.path),
163
+ content: r.content === null || r.content === void 0 ? null : String(r.content)
164
+ }));
165
+ },
166
+ async setDedupKey(path, dedupKey) {
167
+ return (await db.execute({
168
+ sql: UPDATE_SQL,
169
+ args: [
170
+ collection,
171
+ dedupKey,
172
+ scope,
173
+ path
174
+ ]
175
+ })).rowsAffected ?? 0;
176
+ }
177
+ };
178
+ }
179
+ /** Enumerate scopes with any NULL-dedup_key file row under the collection (D1). */
180
+ async function distinctScopesWithDedupCandidatesD1(db, collection) {
181
+ const { prefix, len } = collectionPrefix(collection);
182
+ return ((await db.prepare(DISTINCT_SCOPES_SQL).bind(len, prefix).all()).results ?? []).map((r) => String(r.scope));
183
+ }
184
+ /** Enumerate candidate scopes under the collection (Node SQLite). */
185
+ async function distinctScopesWithDedupCandidatesSqlite(db, collection) {
186
+ const { prefix, len } = collectionPrefix(collection);
187
+ return (await db.execute({
188
+ sql: DISTINCT_SCOPES_SQL,
189
+ args: [len, prefix]
190
+ })).rows.map((r) => String(r.scope));
191
+ }
192
+ const defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
193
+ async function d1HttpQuery(cfg, sql, params) {
194
+ const f = cfg.fetchImpl ?? fetch;
195
+ const sleep = cfg.sleep ?? defaultSleep;
196
+ const maxRetries = cfg.maxRetries ?? 4;
197
+ const url = `https://api.cloudflare.com/client/v4/accounts/${cfg.accountId}/d1/database/${cfg.databaseId}/query`;
198
+ for (let attempt = 0;; attempt++) {
199
+ let status = 0;
200
+ let json;
201
+ let networkErr;
202
+ try {
203
+ const res = await f(url, {
204
+ method: "POST",
205
+ headers: {
206
+ authorization: `Bearer ${cfg.apiToken}`,
207
+ "content-type": "application/json"
208
+ },
209
+ body: JSON.stringify({
210
+ sql,
211
+ params
212
+ })
213
+ });
214
+ status = res.status;
215
+ json = await res.json();
216
+ } catch (e) {
217
+ networkErr = e;
218
+ }
219
+ if (!networkErr && status >= 200 && status < 300 && json?.success !== false) {
220
+ const r = json?.result?.[0];
221
+ return {
222
+ results: r?.results ?? [],
223
+ changes: r?.meta?.changes ?? 0
224
+ };
225
+ }
226
+ const transient = networkErr != null || status >= 500 || status === 429;
227
+ const msg = networkErr ? `D1 HTTP network error: ${String(networkErr).slice(0, 160)}` : `D1 HTTP ${status}: ${JSON.stringify(json?.errors ?? json).slice(0, 200)}`;
228
+ if (!transient || attempt >= maxRetries) throw new Error(msg);
229
+ await sleep(Math.min(2e3, 200 * 2 ** attempt));
230
+ }
231
+ }
232
+ /** CF D1 HTTP-API backend adapter for one (scope, collection) — offline channel. */
233
+ function makeD1HttpDedupBackfillBackend(cfg, scope, collection) {
234
+ const { prefix, len } = collectionPrefix(collection);
235
+ return {
236
+ async existingKeys() {
237
+ const { results } = await d1HttpQuery(cfg, EXISTING_KEYS_SQL, [scope, collection]);
238
+ return results.map((r) => String(r.dedup_key));
239
+ },
240
+ async selectCandidates(cursor, limit) {
241
+ const { results } = await d1HttpQuery(cfg, SELECT_SQL, [
242
+ scope,
243
+ cursor,
244
+ len,
245
+ prefix,
246
+ limit
247
+ ]);
248
+ return results.map((r) => ({
249
+ path: String(r.path),
250
+ content: r.content === null || r.content === void 0 ? null : String(r.content)
251
+ }));
252
+ },
253
+ async setDedupKey(path, dedupKey) {
254
+ const { changes } = await d1HttpQuery(cfg, UPDATE_SQL, [
255
+ collection,
256
+ dedupKey,
257
+ scope,
258
+ path
259
+ ]);
260
+ return changes;
261
+ }
262
+ };
263
+ }
264
+ /** List distinct candidate scopes under the collection on a CF D1 over HTTP. */
265
+ async function distinctScopesWithDedupCandidatesD1Http(cfg, collection) {
266
+ const { prefix, len } = collectionPrefix(collection);
267
+ const { results } = await d1HttpQuery(cfg, DISTINCT_SCOPES_SQL, [len, prefix]);
268
+ return results.map((r) => String(r.scope));
269
+ }
270
+
271
+ //#endregion
272
+ exports.backfillDedup = backfillDedup;
273
+ exports.distinctScopesWithDedupCandidatesD1 = distinctScopesWithDedupCandidatesD1;
274
+ exports.distinctScopesWithDedupCandidatesD1Http = distinctScopesWithDedupCandidatesD1Http;
275
+ exports.distinctScopesWithDedupCandidatesSqlite = distinctScopesWithDedupCandidatesSqlite;
276
+ exports.makeD1DedupBackfillBackend = makeD1DedupBackfillBackend;
277
+ exports.makeD1HttpDedupBackfillBackend = makeD1HttpDedupBackfillBackend;
278
+ exports.makeSqliteDedupBackfillBackend = makeSqliteDedupBackfillBackend;
@@ -0,0 +1,165 @@
1
+ //#region src/dedup-backfill.d.ts
2
+ /**
3
+ * dedup-backfill — one-time backfill of the dedup-on-write secondary index
4
+ * (`ds_entries.dedup_scope` / `dedup_key`) for records written BEFORE #1159
5
+ * landed (planning: dedup-on-write, issue #1159).
6
+ *
7
+ * Pre-#1159 the dedup entry lived at `{collection}/{cid(url)}.json` and had no
8
+ * `dedup_key` column; older layouts (`{slug}.json`, `{uuid}/item.json`) never
9
+ * had one either. After #1159 dedup resolves through the
10
+ * `(scope, dedup_scope, dedup_key)` UNIQUE index, so those historical rows are
11
+ * INVISIBLE to `findByDedupKey` — an already-saved URL's first re-save creates
12
+ * a duplicate. This sweep populates their columns so a re-save merges in place.
13
+ *
14
+ * Design (mirrors ctime-backfill / inline-content-migration):
15
+ * - OFFLINE, code-driven. The core loop {@link backfillDedup} is backend-
16
+ * agnostic; the SQL lives in {@link DedupBackfillBackend} adapters (D1 binding
17
+ * vs Node SQLite vs the CF D1 HTTP API) so it is unit-testable against a mock.
18
+ * - **Host-language normalization** — the `dedup_key` is
19
+ * `normalizeItemUrl(record[dedupBy])`, computed in JS with the SAME function
20
+ * the write path uses. It CANNOT be done in SQL (`json_extract` would store
21
+ * the RAW url, which would never match the Worker's normalized lookups), so
22
+ * the sweep MUST read the record body (`content` column) and normalize here.
23
+ * - **JS uniqueness pre-filter, never DB rejection.** The `(scope, dedup_scope,
24
+ * dedup_key)` UNIQUE index means two rows can't share a key. A `seen` set —
25
+ * seeded from the keys ALREADY indexed (e.g. entries a post-#1159 client
26
+ * already wrote) and grown as the sweep assigns — decides the winner in JS so
27
+ * NO `UPDATE` ever violates the constraint. This matters over the D1 HTTP
28
+ * channel where a constraint error is a 4xx that would abort the whole run.
29
+ * A genuine pre-existing duplicate is left un-indexed (reported), NEVER merged
30
+ * (the tool does not touch content).
31
+ * - **WHERE pins `scope` + the collection prefix**, binds everything as
32
+ * parameters — never a cross-scope / cross-collection UPDATE.
33
+ * - **Deterministic `ORDER BY path ASC` keyset cursor** — resumable across
34
+ * invocations; among genuine duplicates the FIRST in path order wins the key.
35
+ * - **Idempotent**: `UPDATE … WHERE dedup_key IS NULL` — a re-run never
36
+ * re-touches an already-populated row (0 write). The cursor advances past
37
+ * EVERY scanned row so one invocation can't loop.
38
+ * - **Only dedup_scope/dedup_key change** — never cid/size/content/mtime/ctime
39
+ * (index-only; no changelog / no sync fan-out).
40
+ */
41
+ /** A dedup-index candidate: a FILE row with a NULL `dedup_key`. */
42
+ interface DedupBackfillCandidate {
43
+ path: string;
44
+ /** Raw `entries.content` inline record body (host-parsed here, never in SQL). */
45
+ content: string | null;
46
+ }
47
+ /**
48
+ * Backend-specific SQL for the sweep (D1 binding vs Node SQLite vs D1 HTTP).
49
+ * One `(scope, collection)` per adapter — `collection` is the full stored
50
+ * collection path (e.g. `blocklets/aside/user/items`), which is BOTH the value
51
+ * written to `dedup_scope` and the `path` prefix that scopes the sweep.
52
+ */
53
+ interface DedupBackfillBackend {
54
+ /**
55
+ * `dedup_key`s already claimed within this `(scope, collection)` — seeds the
56
+ * uniqueness `seen` set so the sweep never collides with a row a post-#1159
57
+ * client already indexed.
58
+ */
59
+ existingKeys(): Promise<string[]>;
60
+ /**
61
+ * Select up to `limit` FILE rows with `dedup_key IS NULL` under the collection
62
+ * and `path > cursor`, ordered `path ASC` (the keyset cursor). Empty ⇒ done.
63
+ */
64
+ selectCandidates(cursor: string, limit: number): Promise<DedupBackfillCandidate[]>;
65
+ /**
66
+ * Set `dedup_scope` (= the collection) + `dedup_key` on the row, guarded
67
+ * `AND dedup_key IS NULL` so an already-populated row is a 0-row no-op.
68
+ * Returns the number of rows actually changed (0 or 1).
69
+ */
70
+ setDedupKey(path: string, dedupKey: string): Promise<number>;
71
+ }
72
+ interface DedupBackfillOptions {
73
+ /** Record field to dedup by (e.g. `"url"`). Required. */
74
+ dedupBy: string;
75
+ /** Rows per page. Default 100 (content is heavier than a metadata blob). */
76
+ pageSize?: number;
77
+ /** Cap pages this invocation (cross-invocation paging). Default unbounded. */
78
+ maxPages?: number;
79
+ /**
80
+ * Preview only — scan + count, run NO UPDATE. `indexed` then reports rows that
81
+ * WOULD be indexed; all skip counts are exact in both modes.
82
+ */
83
+ dryRun?: boolean;
84
+ log?: (msg: string) => void;
85
+ }
86
+ interface DedupBackfillStats {
87
+ /** FILE rows examined (dedup_key IS NULL under the collection). */
88
+ scanned: number;
89
+ /** Rows whose dedup columns were populated (or WOULD be, in dryRun). */
90
+ indexed: number;
91
+ /** Rows with no inline `content` — cannot extract the field, left as-is. */
92
+ skippedNoContent: number;
93
+ /** Rows whose body lacked a usable `dedupBy` field — not dedup-able. */
94
+ skippedNoField: number;
95
+ /** Rows whose key was already claimed — a genuine duplicate, left un-indexed. */
96
+ skippedDuplicate: number;
97
+ pages: number;
98
+ lastCursor: string;
99
+ /** True when a page came back empty (the sweep reached the end). */
100
+ done: boolean;
101
+ }
102
+ /**
103
+ * Page over NULL-`dedup_key` FILE rows in one `(scope, collection)` and populate
104
+ * their dedup columns. Returns stats; resumable across calls (a fresh call
105
+ * re-seeds `seen` from the DB, so previously-indexed keys stay winners).
106
+ */
107
+ declare function backfillDedup(backend: DedupBackfillBackend, opts: DedupBackfillOptions): Promise<DedupBackfillStats>;
108
+ /** Minimal D1 shape used by the sweep (subset of `D1Database`). */
109
+ interface D1Like {
110
+ prepare(sql: string): {
111
+ bind(...args: (string | number)[]): {
112
+ all(): Promise<{
113
+ results?: unknown[];
114
+ }>;
115
+ run(): Promise<{
116
+ meta?: {
117
+ changes?: number;
118
+ };
119
+ }>;
120
+ };
121
+ };
122
+ }
123
+ /** D1 (Cloudflare binding) backend adapter for one (scope, collection). */
124
+ declare function makeD1DedupBackfillBackend(db: D1Like, scope: string, collection: string): DedupBackfillBackend;
125
+ /**
126
+ * Minimal libsql client shape (Node `_metadata.db`). `args` narrowed to the
127
+ * exact bound types so a real `@libsql/client` `Client` is assignable.
128
+ */
129
+ interface LibsqlLike {
130
+ execute(stmt: {
131
+ sql: string;
132
+ args: (string | number)[];
133
+ }): Promise<{
134
+ rows: Array<Record<string, unknown>>;
135
+ rowsAffected?: number;
136
+ }>;
137
+ }
138
+ /** Node SQLite (libsql) backend adapter for one (scope, collection). */
139
+ declare function makeSqliteDedupBackfillBackend(db: LibsqlLike, scope: string, collection: string): DedupBackfillBackend;
140
+ /** Enumerate scopes with any NULL-dedup_key file row under the collection (D1). */
141
+ declare function distinctScopesWithDedupCandidatesD1(db: D1Like, collection: string): Promise<string[]>;
142
+ /** Enumerate candidate scopes under the collection (Node SQLite). */
143
+ declare function distinctScopesWithDedupCandidatesSqlite(db: LibsqlLike, collection: string): Promise<string[]>;
144
+ /**
145
+ * Config for the offline CF D1 HTTP backend: a local Node process holding a
146
+ * Cloudflare API token with D1-edit runs the SAME paged sweep SQL the Worker
147
+ * would. No R2 — dedup backfill is pure index UPDATEs (record bodies come from
148
+ * the inline `content` column). `fetchImpl` / `sleep` are injectable for tests.
149
+ */
150
+ interface DedupBackfillD1HttpConfig {
151
+ accountId: string;
152
+ databaseId: string;
153
+ apiToken: string;
154
+ fetchImpl?: typeof fetch;
155
+ /** Bounded retries for TRANSIENT failures (5xx / 429 / network). Default 4. */
156
+ maxRetries?: number;
157
+ sleep?: (ms: number) => Promise<void>;
158
+ }
159
+ /** CF D1 HTTP-API backend adapter for one (scope, collection) — offline channel. */
160
+ declare function makeD1HttpDedupBackfillBackend(cfg: DedupBackfillD1HttpConfig, scope: string, collection: string): DedupBackfillBackend;
161
+ /** List distinct candidate scopes under the collection on a CF D1 over HTTP. */
162
+ declare function distinctScopesWithDedupCandidatesD1Http(cfg: DedupBackfillD1HttpConfig, collection: string): Promise<string[]>;
163
+ //#endregion
164
+ export { D1Like, DedupBackfillBackend, DedupBackfillCandidate, DedupBackfillD1HttpConfig, DedupBackfillOptions, DedupBackfillStats, LibsqlLike, backfillDedup, distinctScopesWithDedupCandidatesD1, distinctScopesWithDedupCandidatesD1Http, distinctScopesWithDedupCandidatesSqlite, makeD1DedupBackfillBackend, makeD1HttpDedupBackfillBackend, makeSqliteDedupBackfillBackend };
165
+ //# sourceMappingURL=dedup-backfill.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dedup-backfill.d.cts","names":[],"sources":["../src/dedup-backfill.ts"],"mappings":";;AA2CA;;;;;AAYA;;;;;;;;;;;;;;;;;;;;;;AAoBA;;;;;;;;;;;;UAhCiB,sBAAA;EACf,IAAA;;EAEA,OAAA;AAAA;;;;;;;UASe,oBAAA;EAiDX;;AAsBN;;;EAjEE,YAAA,IAAgB,OAAA;EAmEV;;;;EA9DN,gBAAA,CAAiB,MAAA,UAAgB,KAAA,WAAgB,OAAA,CAAQ,sBAAA;EA6DhD;;;;;EAvDT,WAAA,CAAY,IAAA,UAAc,QAAA,WAAmB,OAAA;AAAA;AAAA,UAG9B,oBAAA;EA2IA;EAzIf,OAAA;;EAEA,QAAA;EAwIA;EAtIA,QAAA;EAuIE;;;;EAlIF,MAAA;EACA,GAAA,IAAO,GAAA;AAAA;AAAA,UAGQ,kBAAA;EAgIe;EA9H9B,OAAA;EA8HqC;EA5HrC,OAAA;EAkIwC;EAhIxC,gBAAA;EAoIqB;EAlIrB,cAAA;EA+HA;EA7HA,gBAAA;EACA,KAAA;EACA,UAAA;EA8HqB;EA5HrB,IAAA;AAAA;;;;;;iBAsBoB,aAAA,CACpB,OAAA,EAAS,oBAAA,EACT,IAAA,EAAM,oBAAA,GACL,OAAA,CAAQ,kBAAA;;UAqFM,MAAA;EACf,OAAA,CAAQ,GAAA;IACN,IAAA,IAAQ,IAAA;MACN,GAAA,IAAO,OAAA;QAAU,OAAA;MAAA;MACjB,GAAA,IAAO,OAAA;QAAU,IAAA;UAAS,OAAA;QAAA;MAAA;IAAA;EAAA;AAAA;;iBAMhB,0BAAA,CACd,EAAA,EAAI,MAAA,EACJ,KAAA,UACA,UAAA,WACC,oBAAA;;;;;UA2Bc,UAAA;EACf,OAAA,CAAQ,IAAA;IAAQ,GAAA;IAAa,IAAA;EAAA,IAA8B,OAAA;IACzD,IAAA,EAAM,KAAA,CAAM,MAAA;IACZ,YAAA;EAAA;AAAA;;iBAKY,8BAAA,CACd,EAAA,EAAI,UAAA,EACJ,KAAA,UACA,UAAA,WACC,oBAAA;;iBAsBmB,mCAAA,CACpB,EAAA,EAAI,MAAA,EACJ,UAAA,WACC,OAAA;;iBAOmB,uCAAA,CACpB,EAAA,EAAI,UAAA,EACJ,UAAA,WACC,OAAA;;;;;;;UAac,yBAAA;EACf,SAAA;EACA,UAAA;EACA,QAAA;EACA,SAAA,UAAmB,KAAA;EAHnB;EAKA,UAAA;EACA,KAAA,IAAS,EAAA,aAAe,OAAA;AAAA;;iBAkDV,8BAAA,CACd,GAAA,EAAK,yBAAA,EACL,KAAA,UACA,UAAA,WACC,oBAAA;;iBAsBmB,uCAAA,CACpB,GAAA,EAAK,yBAAA,EACL,UAAA,WACC,OAAA"}
@@ -0,0 +1,165 @@
1
+ //#region src/dedup-backfill.d.ts
2
+ /**
3
+ * dedup-backfill — one-time backfill of the dedup-on-write secondary index
4
+ * (`ds_entries.dedup_scope` / `dedup_key`) for records written BEFORE #1159
5
+ * landed (planning: dedup-on-write, issue #1159).
6
+ *
7
+ * Pre-#1159 the dedup entry lived at `{collection}/{cid(url)}.json` and had no
8
+ * `dedup_key` column; older layouts (`{slug}.json`, `{uuid}/item.json`) never
9
+ * had one either. After #1159 dedup resolves through the
10
+ * `(scope, dedup_scope, dedup_key)` UNIQUE index, so those historical rows are
11
+ * INVISIBLE to `findByDedupKey` — an already-saved URL's first re-save creates
12
+ * a duplicate. This sweep populates their columns so a re-save merges in place.
13
+ *
14
+ * Design (mirrors ctime-backfill / inline-content-migration):
15
+ * - OFFLINE, code-driven. The core loop {@link backfillDedup} is backend-
16
+ * agnostic; the SQL lives in {@link DedupBackfillBackend} adapters (D1 binding
17
+ * vs Node SQLite vs the CF D1 HTTP API) so it is unit-testable against a mock.
18
+ * - **Host-language normalization** — the `dedup_key` is
19
+ * `normalizeItemUrl(record[dedupBy])`, computed in JS with the SAME function
20
+ * the write path uses. It CANNOT be done in SQL (`json_extract` would store
21
+ * the RAW url, which would never match the Worker's normalized lookups), so
22
+ * the sweep MUST read the record body (`content` column) and normalize here.
23
+ * - **JS uniqueness pre-filter, never DB rejection.** The `(scope, dedup_scope,
24
+ * dedup_key)` UNIQUE index means two rows can't share a key. A `seen` set —
25
+ * seeded from the keys ALREADY indexed (e.g. entries a post-#1159 client
26
+ * already wrote) and grown as the sweep assigns — decides the winner in JS so
27
+ * NO `UPDATE` ever violates the constraint. This matters over the D1 HTTP
28
+ * channel where a constraint error is a 4xx that would abort the whole run.
29
+ * A genuine pre-existing duplicate is left un-indexed (reported), NEVER merged
30
+ * (the tool does not touch content).
31
+ * - **WHERE pins `scope` + the collection prefix**, binds everything as
32
+ * parameters — never a cross-scope / cross-collection UPDATE.
33
+ * - **Deterministic `ORDER BY path ASC` keyset cursor** — resumable across
34
+ * invocations; among genuine duplicates the FIRST in path order wins the key.
35
+ * - **Idempotent**: `UPDATE … WHERE dedup_key IS NULL` — a re-run never
36
+ * re-touches an already-populated row (0 write). The cursor advances past
37
+ * EVERY scanned row so one invocation can't loop.
38
+ * - **Only dedup_scope/dedup_key change** — never cid/size/content/mtime/ctime
39
+ * (index-only; no changelog / no sync fan-out).
40
+ */
41
+ /** A dedup-index candidate: a FILE row with a NULL `dedup_key`. */
42
+ interface DedupBackfillCandidate {
43
+ path: string;
44
+ /** Raw `entries.content` inline record body (host-parsed here, never in SQL). */
45
+ content: string | null;
46
+ }
47
+ /**
48
+ * Backend-specific SQL for the sweep (D1 binding vs Node SQLite vs D1 HTTP).
49
+ * One `(scope, collection)` per adapter — `collection` is the full stored
50
+ * collection path (e.g. `blocklets/aside/user/items`), which is BOTH the value
51
+ * written to `dedup_scope` and the `path` prefix that scopes the sweep.
52
+ */
53
+ interface DedupBackfillBackend {
54
+ /**
55
+ * `dedup_key`s already claimed within this `(scope, collection)` — seeds the
56
+ * uniqueness `seen` set so the sweep never collides with a row a post-#1159
57
+ * client already indexed.
58
+ */
59
+ existingKeys(): Promise<string[]>;
60
+ /**
61
+ * Select up to `limit` FILE rows with `dedup_key IS NULL` under the collection
62
+ * and `path > cursor`, ordered `path ASC` (the keyset cursor). Empty ⇒ done.
63
+ */
64
+ selectCandidates(cursor: string, limit: number): Promise<DedupBackfillCandidate[]>;
65
+ /**
66
+ * Set `dedup_scope` (= the collection) + `dedup_key` on the row, guarded
67
+ * `AND dedup_key IS NULL` so an already-populated row is a 0-row no-op.
68
+ * Returns the number of rows actually changed (0 or 1).
69
+ */
70
+ setDedupKey(path: string, dedupKey: string): Promise<number>;
71
+ }
72
+ interface DedupBackfillOptions {
73
+ /** Record field to dedup by (e.g. `"url"`). Required. */
74
+ dedupBy: string;
75
+ /** Rows per page. Default 100 (content is heavier than a metadata blob). */
76
+ pageSize?: number;
77
+ /** Cap pages this invocation (cross-invocation paging). Default unbounded. */
78
+ maxPages?: number;
79
+ /**
80
+ * Preview only — scan + count, run NO UPDATE. `indexed` then reports rows that
81
+ * WOULD be indexed; all skip counts are exact in both modes.
82
+ */
83
+ dryRun?: boolean;
84
+ log?: (msg: string) => void;
85
+ }
86
+ interface DedupBackfillStats {
87
+ /** FILE rows examined (dedup_key IS NULL under the collection). */
88
+ scanned: number;
89
+ /** Rows whose dedup columns were populated (or WOULD be, in dryRun). */
90
+ indexed: number;
91
+ /** Rows with no inline `content` — cannot extract the field, left as-is. */
92
+ skippedNoContent: number;
93
+ /** Rows whose body lacked a usable `dedupBy` field — not dedup-able. */
94
+ skippedNoField: number;
95
+ /** Rows whose key was already claimed — a genuine duplicate, left un-indexed. */
96
+ skippedDuplicate: number;
97
+ pages: number;
98
+ lastCursor: string;
99
+ /** True when a page came back empty (the sweep reached the end). */
100
+ done: boolean;
101
+ }
102
+ /**
103
+ * Page over NULL-`dedup_key` FILE rows in one `(scope, collection)` and populate
104
+ * their dedup columns. Returns stats; resumable across calls (a fresh call
105
+ * re-seeds `seen` from the DB, so previously-indexed keys stay winners).
106
+ */
107
+ declare function backfillDedup(backend: DedupBackfillBackend, opts: DedupBackfillOptions): Promise<DedupBackfillStats>;
108
+ /** Minimal D1 shape used by the sweep (subset of `D1Database`). */
109
+ interface D1Like {
110
+ prepare(sql: string): {
111
+ bind(...args: (string | number)[]): {
112
+ all(): Promise<{
113
+ results?: unknown[];
114
+ }>;
115
+ run(): Promise<{
116
+ meta?: {
117
+ changes?: number;
118
+ };
119
+ }>;
120
+ };
121
+ };
122
+ }
123
+ /** D1 (Cloudflare binding) backend adapter for one (scope, collection). */
124
+ declare function makeD1DedupBackfillBackend(db: D1Like, scope: string, collection: string): DedupBackfillBackend;
125
+ /**
126
+ * Minimal libsql client shape (Node `_metadata.db`). `args` narrowed to the
127
+ * exact bound types so a real `@libsql/client` `Client` is assignable.
128
+ */
129
+ interface LibsqlLike {
130
+ execute(stmt: {
131
+ sql: string;
132
+ args: (string | number)[];
133
+ }): Promise<{
134
+ rows: Array<Record<string, unknown>>;
135
+ rowsAffected?: number;
136
+ }>;
137
+ }
138
+ /** Node SQLite (libsql) backend adapter for one (scope, collection). */
139
+ declare function makeSqliteDedupBackfillBackend(db: LibsqlLike, scope: string, collection: string): DedupBackfillBackend;
140
+ /** Enumerate scopes with any NULL-dedup_key file row under the collection (D1). */
141
+ declare function distinctScopesWithDedupCandidatesD1(db: D1Like, collection: string): Promise<string[]>;
142
+ /** Enumerate candidate scopes under the collection (Node SQLite). */
143
+ declare function distinctScopesWithDedupCandidatesSqlite(db: LibsqlLike, collection: string): Promise<string[]>;
144
+ /**
145
+ * Config for the offline CF D1 HTTP backend: a local Node process holding a
146
+ * Cloudflare API token with D1-edit runs the SAME paged sweep SQL the Worker
147
+ * would. No R2 — dedup backfill is pure index UPDATEs (record bodies come from
148
+ * the inline `content` column). `fetchImpl` / `sleep` are injectable for tests.
149
+ */
150
+ interface DedupBackfillD1HttpConfig {
151
+ accountId: string;
152
+ databaseId: string;
153
+ apiToken: string;
154
+ fetchImpl?: typeof fetch;
155
+ /** Bounded retries for TRANSIENT failures (5xx / 429 / network). Default 4. */
156
+ maxRetries?: number;
157
+ sleep?: (ms: number) => Promise<void>;
158
+ }
159
+ /** CF D1 HTTP-API backend adapter for one (scope, collection) — offline channel. */
160
+ declare function makeD1HttpDedupBackfillBackend(cfg: DedupBackfillD1HttpConfig, scope: string, collection: string): DedupBackfillBackend;
161
+ /** List distinct candidate scopes under the collection on a CF D1 over HTTP. */
162
+ declare function distinctScopesWithDedupCandidatesD1Http(cfg: DedupBackfillD1HttpConfig, collection: string): Promise<string[]>;
163
+ //#endregion
164
+ export { D1Like, DedupBackfillBackend, DedupBackfillCandidate, DedupBackfillD1HttpConfig, DedupBackfillOptions, DedupBackfillStats, LibsqlLike, backfillDedup, distinctScopesWithDedupCandidatesD1, distinctScopesWithDedupCandidatesD1Http, distinctScopesWithDedupCandidatesSqlite, makeD1DedupBackfillBackend, makeD1HttpDedupBackfillBackend, makeSqliteDedupBackfillBackend };
165
+ //# sourceMappingURL=dedup-backfill.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dedup-backfill.d.mts","names":[],"sources":["../src/dedup-backfill.ts"],"mappings":";;AA2CA;;;;;AAYA;;;;;;;;;;;;;;;;;;;;;;AAoBA;;;;;;;;;;;;UAhCiB,sBAAA;EACf,IAAA;;EAEA,OAAA;AAAA;;;;;;;UASe,oBAAA;EAiDX;;AAsBN;;;EAjEE,YAAA,IAAgB,OAAA;EAmEV;;;;EA9DN,gBAAA,CAAiB,MAAA,UAAgB,KAAA,WAAgB,OAAA,CAAQ,sBAAA;EA6DhD;;;;;EAvDT,WAAA,CAAY,IAAA,UAAc,QAAA,WAAmB,OAAA;AAAA;AAAA,UAG9B,oBAAA;EA2IA;EAzIf,OAAA;;EAEA,QAAA;EAwIA;EAtIA,QAAA;EAuIE;;;;EAlIF,MAAA;EACA,GAAA,IAAO,GAAA;AAAA;AAAA,UAGQ,kBAAA;EAgIe;EA9H9B,OAAA;EA8HqC;EA5HrC,OAAA;EAkIwC;EAhIxC,gBAAA;EAoIqB;EAlIrB,cAAA;EA+HA;EA7HA,gBAAA;EACA,KAAA;EACA,UAAA;EA8HqB;EA5HrB,IAAA;AAAA;;;;;;iBAsBoB,aAAA,CACpB,OAAA,EAAS,oBAAA,EACT,IAAA,EAAM,oBAAA,GACL,OAAA,CAAQ,kBAAA;;UAqFM,MAAA;EACf,OAAA,CAAQ,GAAA;IACN,IAAA,IAAQ,IAAA;MACN,GAAA,IAAO,OAAA;QAAU,OAAA;MAAA;MACjB,GAAA,IAAO,OAAA;QAAU,IAAA;UAAS,OAAA;QAAA;MAAA;IAAA;EAAA;AAAA;;iBAMhB,0BAAA,CACd,EAAA,EAAI,MAAA,EACJ,KAAA,UACA,UAAA,WACC,oBAAA;;;;;UA2Bc,UAAA;EACf,OAAA,CAAQ,IAAA;IAAQ,GAAA;IAAa,IAAA;EAAA,IAA8B,OAAA;IACzD,IAAA,EAAM,KAAA,CAAM,MAAA;IACZ,YAAA;EAAA;AAAA;;iBAKY,8BAAA,CACd,EAAA,EAAI,UAAA,EACJ,KAAA,UACA,UAAA,WACC,oBAAA;;iBAsBmB,mCAAA,CACpB,EAAA,EAAI,MAAA,EACJ,UAAA,WACC,OAAA;;iBAOmB,uCAAA,CACpB,EAAA,EAAI,UAAA,EACJ,UAAA,WACC,OAAA;;;;;;;UAac,yBAAA;EACf,SAAA;EACA,UAAA;EACA,QAAA;EACA,SAAA,UAAmB,KAAA;EAHnB;EAKA,UAAA;EACA,KAAA,IAAS,EAAA,aAAe,OAAA;AAAA;;iBAkDV,8BAAA,CACd,GAAA,EAAK,yBAAA,EACL,KAAA,UACA,UAAA,WACC,oBAAA;;iBAsBmB,uCAAA,CACpB,GAAA,EAAK,yBAAA,EACL,UAAA,WACC,OAAA"}