@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,322 @@
1
+ import { makeNsLog } from "@aigne/afs";
2
+
3
+ //#region src/tree-index.ts
4
+ /**
5
+ * TreeIndex — typed interface for file metadata indexing.
6
+ *
7
+ * Replaces KeyValueStore for DID Space tree entries.
8
+ * Implementations: MemoryTreeIndex (testing), D1TreeIndex (production).
9
+ */
10
+ const log = makeNsLog("provider:did-space");
11
+ /**
12
+ * Stable code on a per-scope quota rejection (file-count OR byte quota,
13
+ * upload-limits Phase 5 / T5.2). Surfaced via the thrown Error's `.code`
14
+ * (single-put/delete) and the batch result entry's `.code` (batchPut) so a
15
+ * consumer can distinguish quota from other failures — previously these were
16
+ * message-only. The message text is preserved verbatim for parity.
17
+ */
18
+ const STORAGE_QUOTA_EXCEEDED_CODE = "STORAGE_QUOTA_EXCEEDED";
19
+ /** Build a quota-rejection Error carrying {@link STORAGE_QUOTA_EXCEEDED_CODE}. */
20
+ function storageQuotaError(message) {
21
+ return Object.assign(new Error(message), { code: STORAGE_QUOTA_EXCEEDED_CODE });
22
+ }
23
+ /**
24
+ * Parse a raw `entries.metadata` column value back into an object.
25
+ *
26
+ * Contract:
27
+ * - NULL / empty / non-string → undefined (no metadata)
28
+ * - valid JSON object → that object
29
+ * - parse error or non-object JSON → undefined + console.warn (best-effort:
30
+ * one bad row must not break list/read of the whole collection)
31
+ */
32
+ function parseTreeEntryMetadata(raw) {
33
+ if (raw == null) return void 0;
34
+ if (typeof raw !== "string" || raw.length === 0) return void 0;
35
+ try {
36
+ const parsed = JSON.parse(raw);
37
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
38
+ log.warn(`[did-space] entries.metadata is not a JSON object; treating as empty`);
39
+ return;
40
+ } catch (err) {
41
+ const msg = err instanceof Error ? err.message : String(err);
42
+ log.warn(`[did-space] failed to parse entries.metadata (${msg}); treating as empty`);
43
+ return;
44
+ }
45
+ }
46
+ /**
47
+ * Serialize a TreeEntry.metadata object for storage in the
48
+ * `entries.metadata` column.
49
+ *
50
+ * Contract:
51
+ * - undefined / null / empty object → null (don't waste a JSON literal)
52
+ * - non-empty object → JSON.stringify result
53
+ */
54
+ function serializeTreeEntryMetadata(metadata) {
55
+ if (metadata == null) return null;
56
+ if (Object.keys(metadata).length === 0) return null;
57
+ return JSON.stringify(metadata);
58
+ }
59
+ /** Order-insensitive deep equality: object keys are unordered, array order matters. */
60
+ function deepEqual(a, b) {
61
+ if (a === b) return true;
62
+ if (a === null || b === null || typeof a !== "object" || typeof b !== "object") return false;
63
+ const aArr = Array.isArray(a);
64
+ const bArr = Array.isArray(b);
65
+ if (aArr !== bArr) return false;
66
+ if (aArr && bArr) {
67
+ if (a.length !== b.length) return false;
68
+ for (let i = 0; i < a.length; i++) if (!deepEqual(a[i], b[i])) return false;
69
+ return true;
70
+ }
71
+ const ao = a;
72
+ const bo = b;
73
+ const ak = Object.keys(ao);
74
+ if (ak.length !== Object.keys(bo).length) return false;
75
+ for (const k of ak) {
76
+ if (!Object.hasOwn(bo, k)) return false;
77
+ if (!deepEqual(ao[k], bo[k])) return false;
78
+ }
79
+ return true;
80
+ }
81
+ /**
82
+ * Are two TreeEntry.metadata objects equivalent for skip-if-unchanged purposes?
83
+ * (did-space-metadata G4). This MUST be order-insensitive for object keys —
84
+ * {@link serializeTreeEntryMetadata} is a bare `JSON.stringify` that preserves
85
+ * insertion order, so its string equality would treat a key reorder as a
86
+ * "change" and forge a phantom changelog row / sync churn (the very thing skip
87
+ * exists to avoid). Empty / null / undefined all compare equal because they all
88
+ * serialize to a NULL column. Used ONLY for the skip decision; storage still
89
+ * uses {@link serializeTreeEntryMetadata} verbatim.
90
+ */
91
+ function treeEntryMetadataEqual(a, b) {
92
+ const aEmpty = a == null || Object.keys(a).length === 0;
93
+ const bEmpty = b == null || Object.keys(b).length === 0;
94
+ if (aEmpty || bEmpty) return aEmpty && bEmpty;
95
+ return deepEqual(a, b);
96
+ }
97
+ /** UTF-8 safe base64 (btoa alone corrupts non-latin1 file names). */
98
+ function b64encode(s) {
99
+ const bytes = new TextEncoder().encode(s);
100
+ let bin = "";
101
+ for (const b of bytes) bin += String.fromCharCode(b);
102
+ return btoa(bin);
103
+ }
104
+ function b64decode(s) {
105
+ const bin = atob(s);
106
+ return new TextDecoder().decode(Uint8Array.from(bin, (c) => c.charCodeAt(0)));
107
+ }
108
+ /** Build a v2 keyset cursor: `v2:<b64(json(sortValue))>:<b64(tie)>`. */
109
+ function buildKeysetCursor(sortValue, tie) {
110
+ return `v2:${b64encode(JSON.stringify(sortValue))}:${b64encode(tie)}`;
111
+ }
112
+ /**
113
+ * Parse a list cursor. Shared by all three TreeIndex impls so the
114
+ * legacy-vs-v2 dance can never drift between them.
115
+ *
116
+ * - undefined → none
117
+ * - `v2:`-prefixed → keyset cursor; REQUIRES `hasOrderBy` (a keyset cursor
118
+ * replayed onto a default-order listing would be a wrong page); malformed
119
+ * payloads throw a clean error instead of producing a wrong page.
120
+ * - anything else → legacy plain cursor, accepted ONLY for default
121
+ * (no-orderBy) order.
122
+ */
123
+ function parseTreeCursor(cursor, hasOrderBy) {
124
+ if (!cursor) return { kind: "none" };
125
+ if (cursor.startsWith("v2:")) {
126
+ if (!hasOrderBy) throw new Error("Invalid cursor: v2 keyset cursor requires a matching orderBy");
127
+ const parts = cursor.split(":");
128
+ if (parts.length !== 3 || !parts[1] || !parts[2]) throw new Error("Invalid cursor: malformed v2 keyset cursor");
129
+ let sortValue;
130
+ let tie;
131
+ try {
132
+ sortValue = JSON.parse(b64decode(parts[1]));
133
+ tie = b64decode(parts[2]);
134
+ } catch {
135
+ throw new Error("Invalid cursor: malformed v2 keyset cursor");
136
+ }
137
+ if (sortValue !== null && typeof sortValue !== "string" && typeof sortValue !== "number") throw new Error("Invalid cursor: malformed v2 keyset cursor");
138
+ return {
139
+ kind: "v2",
140
+ cursor: {
141
+ sortValue,
142
+ tie
143
+ }
144
+ };
145
+ }
146
+ if (hasOrderBy) throw new Error("Invalid cursor: legacy cursor cannot be combined with orderBy");
147
+ return {
148
+ kind: "legacy",
149
+ value: cursor
150
+ };
151
+ }
152
+ /**
153
+ * Build the ORDER BY + keyset-resume WHERE for an ordered listing. Shared
154
+ * by SQLiteTreeIndex and D1TreeIndex (identical SQL dialect).
155
+ *
156
+ * - Missing values sort LAST regardless of direction: nullable columns get
157
+ * a leading `(col IS NULL) ASC` term. Non-nullable columns skip it so the
158
+ * `(scope, parent, col, name)` indexes stay usable.
159
+ * - Resume uses the expansion `col > ? OR (col = ? AND tie > ?)` (row-value
160
+ * free — works on every SQLite the runtimes target) plus NULL-run
161
+ * handling for nullable columns.
162
+ */
163
+ function buildOrderedQueryParts(opts) {
164
+ const { col, nullable, tieCol, cursor } = opts;
165
+ const dir = opts.order === "desc" ? "DESC" : "ASC";
166
+ const op = opts.order === "desc" ? "<" : ">";
167
+ const orderSql = nullable ? `(${col} IS NULL) ASC, ${col} ${dir}, ${tieCol} ${dir}` : `${col} ${dir}, ${tieCol} ${dir}`;
168
+ if (!cursor) return {
169
+ cursorWhereSql: "",
170
+ cursorArgs: [],
171
+ orderSql
172
+ };
173
+ if (cursor.sortValue === null) {
174
+ if (!nullable) throw new Error("Invalid cursor: malformed v2 keyset cursor");
175
+ return {
176
+ cursorWhereSql: `(${col} IS NULL AND ${tieCol} ${op} ?)`,
177
+ cursorArgs: [cursor.tie],
178
+ orderSql
179
+ };
180
+ }
181
+ if (nullable) return {
182
+ cursorWhereSql: `((${col} IS NOT NULL AND (${col} ${op} ? OR (${col} = ? AND ${tieCol} ${op} ?))) OR ${col} IS NULL)`,
183
+ cursorArgs: [
184
+ cursor.sortValue,
185
+ cursor.sortValue,
186
+ cursor.tie
187
+ ],
188
+ orderSql
189
+ };
190
+ return {
191
+ cursorWhereSql: `(${col} ${op} ? OR (${col} = ? AND ${tieCol} ${op} ?))`,
192
+ cursorArgs: [
193
+ cursor.sortValue,
194
+ cursor.sortValue,
195
+ cursor.tie
196
+ ],
197
+ orderSql
198
+ };
199
+ }
200
+ /**
201
+ * In-memory equivalents of the SQL ordering semantics, used by
202
+ * MemoryTreeIndex so all three impls stay in lockstep.
203
+ */
204
+ function compareTreeSortValues(a, b, order) {
205
+ if (a === null && b === null) return 0;
206
+ if (a === null) return 1;
207
+ if (b === null) return -1;
208
+ let cmp;
209
+ if (typeof a === "number" && typeof b === "number") cmp = a === b ? 0 : a < b ? -1 : 1;
210
+ else {
211
+ const as = String(a);
212
+ const bs = String(b);
213
+ cmp = as === bs ? 0 : as < bs ? -1 : 1;
214
+ }
215
+ return order === "desc" ? -cmp : cmp;
216
+ }
217
+ /** Keyset predicate mirroring {@link buildOrderedQueryParts}'s WHERE. */
218
+ function keysetAfter(rowSort, rowTie, cursor, order) {
219
+ const tieAfter = order === "desc" ? rowTie < cursor.tie : rowTie > cursor.tie;
220
+ if (cursor.sortValue === null) return rowSort === null && tieAfter;
221
+ if (rowSort === null) return true;
222
+ const cmp = compareTreeSortValues(rowSort, cursor.sortValue, order);
223
+ if (cmp !== 0) return cmp > 0;
224
+ return tieAfter;
225
+ }
226
+ /**
227
+ * Loop reference implementation of `batchPutEntries` — single-op semantics
228
+ * per entry, in order. SQLite/memory indexes delegate here (semantic
229
+ * equivalence with the merged D1 path is pinned by conformance).
230
+ */
231
+ async function loopBatchPutEntries(index, entries, ctx) {
232
+ const results = [];
233
+ for (const { path, entry, opts } of entries) {
234
+ const existing = ctx?.preread?.has(path) ? ctx.preread.get(path) ?? null : await index.get(path);
235
+ if (opts.ifMatch !== void 0 && (!existing || existing.cid !== opts.ifMatch)) {
236
+ results.push({
237
+ path,
238
+ conflict: {
239
+ currentCid: existing?.cid ?? null,
240
+ currentMtime: existing?.mtime ?? null,
241
+ currentDeviceId: null
242
+ }
243
+ });
244
+ continue;
245
+ }
246
+ if (existing && existing.cid === entry.cid && existing.mimeType === entry.mimeType && treeEntryMetadataEqual(existing.metadata, entry.metadata)) {
247
+ const wouldBeInline = index.inlineForEntry ? await index.inlineForEntry(entry) : void 0;
248
+ if (wouldBeInline === void 0 || (existing.content ?? null) === wouldBeInline) {
249
+ results.push({
250
+ path,
251
+ oldCid: existing.cid,
252
+ skipped: true
253
+ });
254
+ continue;
255
+ }
256
+ }
257
+ try {
258
+ const { oldCid } = await index.put(path, entry, { deviceId: opts.deviceId });
259
+ const liveSeq = await index.getPrefixWatermark?.(path) ?? void 0;
260
+ results.push({
261
+ path,
262
+ ...oldCid !== void 0 ? { oldCid } : {},
263
+ ...typeof liveSeq === "number" && liveSeq > 0 ? { liveSeq } : {}
264
+ });
265
+ } catch (err) {
266
+ const code = err?.code;
267
+ results.push({
268
+ path,
269
+ error: err instanceof Error ? err.message : String(err),
270
+ ...typeof code === "string" ? { code } : {}
271
+ });
272
+ }
273
+ }
274
+ return results;
275
+ }
276
+ /** Loop reference implementation of `batchDeleteEntries` — see {@link loopBatchPutEntries}. */
277
+ async function loopBatchDeleteEntries(index, entries, ctx) {
278
+ const results = [];
279
+ for (const { path, opts } of entries) {
280
+ const existing = ctx?.preread?.has(path) ? ctx.preread.get(path) ?? null : await index.get(path);
281
+ if (opts.ifMatch !== void 0 && (!existing || existing.cid !== opts.ifMatch)) {
282
+ results.push({
283
+ path,
284
+ conflict: {
285
+ currentCid: existing?.cid ?? null,
286
+ currentMtime: existing?.mtime ?? null,
287
+ currentDeviceId: null
288
+ }
289
+ });
290
+ continue;
291
+ }
292
+ if (!existing) {
293
+ results.push({
294
+ path,
295
+ notFound: true
296
+ });
297
+ continue;
298
+ }
299
+ try {
300
+ await index.delete(path, {
301
+ deviceId: opts.deviceId,
302
+ ...opts.mtime !== void 0 ? { mtime: opts.mtime } : {}
303
+ });
304
+ const liveSeq = await index.getPrefixWatermark?.(path) ?? void 0;
305
+ results.push({
306
+ path,
307
+ deletedCid: existing.cid,
308
+ ...typeof liveSeq === "number" && liveSeq > 0 ? { liveSeq } : {}
309
+ });
310
+ } catch (err) {
311
+ results.push({
312
+ path,
313
+ error: err instanceof Error ? err.message : String(err)
314
+ });
315
+ }
316
+ }
317
+ return results;
318
+ }
319
+
320
+ //#endregion
321
+ export { STORAGE_QUOTA_EXCEEDED_CODE, buildKeysetCursor, buildOrderedQueryParts, compareTreeSortValues, keysetAfter, loopBatchDeleteEntries, loopBatchPutEntries, parseTreeCursor, parseTreeEntryMetadata, serializeTreeEntryMetadata, storageQuotaError, treeEntryMetadataEqual };
322
+ //# sourceMappingURL=tree-index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tree-index.mjs","names":[],"sources":["../src/tree-index.ts"],"sourcesContent":["/**\n * TreeIndex — typed interface for file metadata indexing.\n *\n * Replaces KeyValueStore for DID Space tree entries.\n * Implementations: MemoryTreeIndex (testing), D1TreeIndex (production).\n */\n\nimport { makeNsLog } from \"@aigne/afs\";\nimport type { DirtyPathRow, ListDirtyPathOptions } from \"./dirty-path-queue.js\";\n\nconst log = makeNsLog(\"provider:did-space\");\n\n/**\n * Stable code on a per-scope quota rejection (file-count OR byte quota,\n * upload-limits Phase 5 / T5.2). Surfaced via the thrown Error's `.code`\n * (single-put/delete) and the batch result entry's `.code` (batchPut) so a\n * consumer can distinguish quota from other failures — previously these were\n * message-only. The message text is preserved verbatim for parity.\n */\nexport const STORAGE_QUOTA_EXCEEDED_CODE = \"STORAGE_QUOTA_EXCEEDED\";\n\n/** Build a quota-rejection Error carrying {@link STORAGE_QUOTA_EXCEEDED_CODE}. */\nexport function storageQuotaError(message: string): Error {\n return Object.assign(new Error(message), { code: STORAGE_QUOTA_EXCEEDED_CODE });\n}\n\nexport interface TreeEntry {\n cid: string;\n size: number;\n mtime: number;\n ctime: number;\n mimeType?: string;\n /**\n * Inline copy of the entry's bytes (Layer 0 inline-content capability, see\n * `inline-content.ts`). cid-verified: when present, `hash(content) === cid`\n * holds — the cid stays the identity; content is a co-located copy.\n *\n * On `put`: an index constructed with `inlineCache: \"on\"` stores text\n * content ≤ its threshold (verified against cid, mismatch rejected);\n * default-off indexes ignore the field (byte-identical legacy behavior).\n * Passing NO content on overwrite clears any prior inline copy — the\n * column can never go stale relative to the cid.\n *\n * On reads: `get` returns it when stored; `listEntries`/`listChildren`\n * include it ONLY under `{ includeContent: true }` (default projection\n * stays content-free).\n */\n content?: string;\n /**\n * Application metadata (planning/agent-hub/metadata-foundation.md).\n * Stores `AFSWriteEntryPayload.meta` — application-level dimension\n * fields the application uses for collection-query `meta.*` paths.\n *\n * Provider-computed fields (`kind / size / cid / mimeType`) are NOT in\n * here — they live in their own TreeEntry properties / columns and the\n * provider merges them on top at read time.\n *\n * On `put`: persisted as a JSON blob to the `entries.metadata` column.\n * Undefined / empty object → stored as NULL.\n *\n * On reads: returned exactly as written (after provider-reserved fields\n * are stripped by the application-layer write path).\n */\n metadata?: Record<string, unknown>;\n /**\n * Dedup-on-write secondary-index columns (#1159). Together they back the\n * `(scope, dedup_scope, dedup_key)` partial UNIQUE index that resolves a\n * dedup write to at-most-one entry — replacing the old cid-in-path scheme.\n *\n * - `dedupScope`: the COLLECTION the record dedups within (the write `path`\n * argument, e.g. `user/items`). Decoupled from the row's physical `parent`\n * so a flat `{coll}/{uuid}.json` and a legacy dir-per-record\n * `{coll}/{uuid}/item.json` dedup against the SAME scope.\n * - `dedupKey`: the normalized identity value (e.g. `normalizeItemUrl(url)`).\n *\n * Set ONLY by the provider's dedup write path (and the reindex seam); a plain\n * write leaves them undefined. On overwrite the SQL upserts COALESCE the\n * existing value when the incoming entry omits them, so a non-dedup edit of a\n * dedup row never drops it out of the index.\n */\n dedupScope?: string;\n dedupKey?: string;\n}\n\n/**\n * Parse a raw `entries.metadata` column value back into an object.\n *\n * Contract:\n * - NULL / empty / non-string → undefined (no metadata)\n * - valid JSON object → that object\n * - parse error or non-object JSON → undefined + console.warn (best-effort:\n * one bad row must not break list/read of the whole collection)\n */\nexport function parseTreeEntryMetadata(raw: unknown): Record<string, unknown> | undefined {\n if (raw == null) return undefined;\n if (typeof raw !== \"string\" || raw.length === 0) return undefined;\n try {\n const parsed = JSON.parse(raw);\n if (parsed && typeof parsed === \"object\" && !Array.isArray(parsed)) {\n return parsed as Record<string, unknown>;\n }\n log.warn(`[did-space] entries.metadata is not a JSON object; treating as empty`);\n return undefined;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n log.warn(`[did-space] failed to parse entries.metadata (${msg}); treating as empty`);\n return undefined;\n }\n}\n\n/**\n * Serialize a TreeEntry.metadata object for storage in the\n * `entries.metadata` column.\n *\n * Contract:\n * - undefined / null / empty object → null (don't waste a JSON literal)\n * - non-empty object → JSON.stringify result\n */\nexport function serializeTreeEntryMetadata(\n metadata: Record<string, unknown> | undefined,\n): string | null {\n if (metadata == null) return null;\n const keys = Object.keys(metadata);\n if (keys.length === 0) return null;\n return JSON.stringify(metadata);\n}\n\n/** Order-insensitive deep equality: object keys are unordered, array order matters. */\nfunction deepEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (a === null || b === null || typeof a !== \"object\" || typeof b !== \"object\") return false;\n const aArr = Array.isArray(a);\n const bArr = Array.isArray(b);\n if (aArr !== bArr) return false;\n if (aArr && bArr) {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) if (!deepEqual(a[i], b[i])) return false;\n return true;\n }\n const ao = a as Record<string, unknown>;\n const bo = b as Record<string, unknown>;\n const ak = Object.keys(ao);\n if (ak.length !== Object.keys(bo).length) return false;\n for (const k of ak) {\n if (!Object.hasOwn(bo, k)) return false;\n if (!deepEqual(ao[k], bo[k])) return false;\n }\n return true;\n}\n\n/**\n * Are two TreeEntry.metadata objects equivalent for skip-if-unchanged purposes?\n * (did-space-metadata G4). This MUST be order-insensitive for object keys —\n * {@link serializeTreeEntryMetadata} is a bare `JSON.stringify` that preserves\n * insertion order, so its string equality would treat a key reorder as a\n * \"change\" and forge a phantom changelog row / sync churn (the very thing skip\n * exists to avoid). Empty / null / undefined all compare equal because they all\n * serialize to a NULL column. Used ONLY for the skip decision; storage still\n * uses {@link serializeTreeEntryMetadata} verbatim.\n */\nexport function treeEntryMetadataEqual(\n a: Record<string, unknown> | undefined | null,\n b: Record<string, unknown> | undefined | null,\n): boolean {\n const aEmpty = a == null || Object.keys(a).length === 0;\n const bEmpty = b == null || Object.keys(b).length === 0;\n if (aEmpty || bEmpty) return aEmpty && bEmpty;\n return deepEqual(a, b);\n}\n\nexport interface TreeIndexListOptions {\n limit?: number;\n cursor?: string;\n /**\n * Include each entry's inline `content` (when stored) in the result.\n * Default OFF — the default SELECT never touches the content column, so\n * every existing list path is zero-regression, zero extra bytes. Entries\n * without an inline copy come back without `content` (best-effort).\n */\n includeContent?: boolean;\n /**\n * Sort column. `name` is `listChildren`'s historical hardcoded default,\n * now explicit (the `entries` table has a `name` column, so `list` /\n * `listEntries` map it naturally too).\n *\n * Semantics when set (all three impls, enforced by\n * `test/tree-index-order.test.ts`):\n * - tiebreak on the listing's unique key (`name` for `listChildren`,\n * `path` for `list`/`listEntries`), same direction;\n * - missing values (NULL `mime_type`) sort LAST regardless of direction;\n * - the resume cursor is the keyset v2 format (`v2:<b64>:<b64>`, see\n * {@link buildKeysetCursor}) so duplicate sort values never skip rows\n * across a page boundary;\n * - a legacy (non-v2) cursor combined with `orderBy` is rejected.\n *\n * When UNSET the listing keeps its historical byte-identical behavior:\n * `listChildren` orders by `name` with a plain name-string cursor;\n * `list`/`listEntries` order by `path` with a plain sort-value cursor.\n */\n orderBy?: \"path\" | \"mtime\" | \"size\" | \"ctime\" | \"mimeType\" | \"name\";\n order?: \"asc\" | \"desc\";\n}\n\n// ========== Keyset cursor v2 (did-space-sorting Phase 2) ==========\n\n/** Decoded keyset cursor: the last row's sort value + unique tiebreak value. */\nexport interface TreeKeysetCursor {\n /** Raw sort-column value of the last row; `null` for NULL (missing) values. */\n sortValue: string | number | null;\n /** Unique tiebreak value of the last row (`name` or `path`). */\n tie: string;\n}\n\n/** UTF-8 safe base64 (btoa alone corrupts non-latin1 file names). */\nfunction b64encode(s: string): string {\n const bytes = new TextEncoder().encode(s);\n let bin = \"\";\n for (const b of bytes) bin += String.fromCharCode(b);\n return btoa(bin);\n}\n\nfunction b64decode(s: string): string {\n const bin = atob(s);\n return new TextDecoder().decode(Uint8Array.from(bin, (c) => c.charCodeAt(0)));\n}\n\n/** Build a v2 keyset cursor: `v2:<b64(json(sortValue))>:<b64(tie)>`. */\nexport function buildKeysetCursor(sortValue: string | number | null, tie: string): string {\n return `v2:${b64encode(JSON.stringify(sortValue))}:${b64encode(tie)}`;\n}\n\nexport type ParsedTreeCursor =\n | { kind: \"none\" }\n | { kind: \"legacy\"; value: string }\n | { kind: \"v2\"; cursor: TreeKeysetCursor };\n\n/**\n * Parse a list cursor. Shared by all three TreeIndex impls so the\n * legacy-vs-v2 dance can never drift between them.\n *\n * - undefined → none\n * - `v2:`-prefixed → keyset cursor; REQUIRES `hasOrderBy` (a keyset cursor\n * replayed onto a default-order listing would be a wrong page); malformed\n * payloads throw a clean error instead of producing a wrong page.\n * - anything else → legacy plain cursor, accepted ONLY for default\n * (no-orderBy) order.\n */\nexport function parseTreeCursor(cursor: string | undefined, hasOrderBy: boolean): ParsedTreeCursor {\n if (!cursor) return { kind: \"none\" };\n if (cursor.startsWith(\"v2:\")) {\n if (!hasOrderBy) {\n throw new Error(\"Invalid cursor: v2 keyset cursor requires a matching orderBy\");\n }\n const parts = cursor.split(\":\");\n if (parts.length !== 3 || !parts[1] || !parts[2]) {\n throw new Error(\"Invalid cursor: malformed v2 keyset cursor\");\n }\n let sortValue: unknown;\n let tie: string;\n try {\n sortValue = JSON.parse(b64decode(parts[1]));\n tie = b64decode(parts[2]);\n } catch {\n throw new Error(\"Invalid cursor: malformed v2 keyset cursor\");\n }\n if (sortValue !== null && typeof sortValue !== \"string\" && typeof sortValue !== \"number\") {\n throw new Error(\"Invalid cursor: malformed v2 keyset cursor\");\n }\n return { kind: \"v2\", cursor: { sortValue, tie } };\n }\n if (hasOrderBy) {\n throw new Error(\"Invalid cursor: legacy cursor cannot be combined with orderBy\");\n }\n return { kind: \"legacy\", value: cursor };\n}\n\n/** SQL fragments for an ordered, keyset-resumable listing. */\nexport interface OrderedQueryParts {\n /** Extra WHERE condition (empty string when no cursor). */\n cursorWhereSql: string;\n cursorArgs: Array<string | number>;\n /** ORDER BY clause body (without the `ORDER BY` keyword). */\n orderSql: string;\n}\n\n/**\n * Build the ORDER BY + keyset-resume WHERE for an ordered listing. Shared\n * by SQLiteTreeIndex and D1TreeIndex (identical SQL dialect).\n *\n * - Missing values sort LAST regardless of direction: nullable columns get\n * a leading `(col IS NULL) ASC` term. Non-nullable columns skip it so the\n * `(scope, parent, col, name)` indexes stay usable.\n * - Resume uses the expansion `col > ? OR (col = ? AND tie > ?)` (row-value\n * free — works on every SQLite the runtimes target) plus NULL-run\n * handling for nullable columns.\n */\nexport function buildOrderedQueryParts(opts: {\n col: string;\n nullable: boolean;\n order: \"asc\" | \"desc\";\n tieCol: string;\n cursor?: TreeKeysetCursor;\n}): OrderedQueryParts {\n const { col, nullable, tieCol, cursor } = opts;\n const dir = opts.order === \"desc\" ? \"DESC\" : \"ASC\";\n const op = opts.order === \"desc\" ? \"<\" : \">\";\n const orderSql = nullable\n ? `(${col} IS NULL) ASC, ${col} ${dir}, ${tieCol} ${dir}`\n : `${col} ${dir}, ${tieCol} ${dir}`;\n\n if (!cursor) {\n return { cursorWhereSql: \"\", cursorArgs: [], orderSql };\n }\n if (cursor.sortValue === null) {\n if (!nullable) {\n throw new Error(\"Invalid cursor: malformed v2 keyset cursor\");\n }\n // Resuming inside the trailing NULL run: only NULL rows past the tie.\n return {\n cursorWhereSql: `(${col} IS NULL AND ${tieCol} ${op} ?)`,\n cursorArgs: [cursor.tie],\n orderSql,\n };\n }\n if (nullable) {\n // Non-null cursor on a nullable column: the rest of the non-null run,\n // then the entire NULL tail (NULLs sort last in BOTH directions).\n return {\n cursorWhereSql: `((${col} IS NOT NULL AND (${col} ${op} ? OR (${col} = ? AND ${tieCol} ${op} ?))) OR ${col} IS NULL)`,\n cursorArgs: [cursor.sortValue, cursor.sortValue, cursor.tie],\n orderSql,\n };\n }\n return {\n cursorWhereSql: `(${col} ${op} ? OR (${col} = ? AND ${tieCol} ${op} ?))`,\n cursorArgs: [cursor.sortValue, cursor.sortValue, cursor.tie],\n orderSql,\n };\n}\n\n/**\n * In-memory equivalents of the SQL ordering semantics, used by\n * MemoryTreeIndex so all three impls stay in lockstep.\n */\nexport function compareTreeSortValues(\n a: string | number | null,\n b: string | number | null,\n order: \"asc\" | \"desc\",\n): number {\n // Missing sorts LAST regardless of direction.\n if (a === null && b === null) return 0;\n if (a === null) return 1;\n if (b === null) return -1;\n let cmp: number;\n if (typeof a === \"number\" && typeof b === \"number\") {\n cmp = a === b ? 0 : a < b ? -1 : 1;\n } else {\n const as = String(a);\n const bs = String(b);\n cmp = as === bs ? 0 : as < bs ? -1 : 1;\n }\n return order === \"desc\" ? -cmp : cmp;\n}\n\n/** Keyset predicate mirroring {@link buildOrderedQueryParts}'s WHERE. */\nexport function keysetAfter(\n rowSort: string | number | null,\n rowTie: string,\n cursor: TreeKeysetCursor,\n order: \"asc\" | \"desc\",\n): boolean {\n const tieAfter = order === \"desc\" ? rowTie < cursor.tie : rowTie > cursor.tie;\n if (cursor.sortValue === null) {\n // Cursor in the NULL tail → only later NULL rows remain.\n return rowSort === null && tieAfter;\n }\n if (rowSort === null) return true; // NULL tail comes after every non-null row\n const cmp = compareTreeSortValues(rowSort, cursor.sortValue, order);\n if (cmp !== 0) return cmp > 0;\n return tieAfter;\n}\n\nexport interface TreeIndexListResult {\n entries: Array<{ path: string }>;\n hasMore: boolean;\n cursor?: string;\n}\n\nexport interface TreeIndexListEntriesResult {\n entries: Array<{ path: string } & TreeEntry>;\n hasMore: boolean;\n cursor?: string;\n}\n\n/**\n * Options for mutation methods (Task 2.4a — atomic changelog write).\n *\n * Every put/delete/putBatch/deleteByPrefix call carries:\n * - `deviceId`: opaque per-device identifier persisted into the changelog row.\n * Required, non-empty, ≤ 128 bytes. Validated at runtime by\n * `validateDeviceId()` in `changelog-write.ts`.\n * - `skipChangelog`: when `true`, the underlying TreeIndex writes the `entries`\n * row but skips the corresponding `changelog` INSERT. Used by\n * Task 2.4b to exempt blocklet-publish CLI uploads (the publish\n * flow is already batched and does not need per-file sync fan-out).\n *\n * Strict-TypeScript design: `opts` is a REQUIRED trailing parameter on every\n * mutation method (see `tree-index.ts`). Callers that go through the `TreeIndex`\n * interface (e.g. `CloudflareDIDSpace`, `DIDSpaceProvider`) get a compile-time\n * error if they omit `opts` — that is the enforcement point for Phase 2 sync.\n */\nexport interface PutOpts {\n /** Required. Non-empty string ≤ 128 bytes. Written into `changelog.device_id`. */\n deviceId: string;\n /**\n * Optional client mtime (unix millis) written into `changelog.mtime` for\n * delete/deleteByPrefix rows (T1.5). When absent, implementations fall back to\n * the server clock (`Date.now()`). `put` carries its mtime via the `TreeEntry`,\n * so this only affects the delete paths where there is no entry to read it from.\n */\n mtime?: number;\n /** When `true`, skip the `changelog` INSERT. Default `false`. */\n skipChangelog?: boolean;\n}\n\n/**\n * In-memory shape of a `changelog` row.\n *\n * Matches the D1/libsql row shape 1:1 so MemoryTreeIndex tests can assert\n * against the same fields that production code will see on the wire. Exposed\n * via `MemoryTreeIndex.getChangelogSnapshot()` for unit tests.\n */\nexport interface ChangelogEntry {\n /** Monotonically increasing row ID (1-based in MemoryTreeIndex; AUTOINCREMENT in D1). */\n seq: number;\n scope: string;\n path: string;\n op: \"put\" | \"delete\";\n /** `null` on delete rows; non-null on put rows. */\n cid: string | null;\n /** `null` on first write; prior cid on overwrite/delete. */\n old_cid: string | null;\n /** `null` on delete rows; file size on put rows. */\n size: number | null;\n mtime: number;\n device_id: string;\n}\n\n// ───────────────── batch sync fast path (afs-rpc-batch P3) ─────────────────\n//\n// NOTE: deliberately distinct from the legacy `putBatch` (reconcile/rsync):\n// putBatch returns void, shares ONE opts across the batch, has no\n// skip-if-unchanged / oldCid / conflict semantics, and bumps the watermark\n// once per collection root. The sync batch contract below is per-entry\n// throughout, and its watermark semantics match the single `put` (+1 per\n// write, each entry carrying back its own seq for change-event re-emission).\n\n/** Per-entry options for the batch sync fast path. */\nexport interface BatchEntryOpts {\n /** Required. Written into `changelog.device_id` (echo prevention / LWW). */\n deviceId: string;\n /** Client mtime for DELETE changelog rows (put rows take mtime from the entry). */\n mtime?: number;\n /** Optimistic-concurrency precondition against the current entry cid. */\n ifMatch?: string;\n}\n\nexport interface BatchPutEntry {\n path: string;\n entry: TreeEntry;\n opts: BatchEntryOpts;\n}\n\nexport interface BatchDeleteEntry {\n path: string;\n opts: BatchEntryOpts;\n}\n\n/** Conflict triple — mirrors the single-op AFSConflictError fields\n * (deviceId is null, exactly like the single write path which never reads it). */\nexport interface BatchEntryConflict {\n currentCid: string | null;\n currentMtime: number | null;\n currentDeviceId: string | null;\n}\n\nexport interface BatchPutEntryResult {\n path: string;\n /** Prior cid when the path existed (drives the caller's GC enqueue). */\n oldCid?: string;\n /** Skip-if-unchanged hit: same cid+mimeType — no changelog row, no watermark bump. */\n skipped?: boolean;\n /** Present iff the entry failed its ifMatch precondition (nothing written). */\n conflict?: BatchEntryConflict;\n /** Non-conflict per-entry failure (e.g. quota) — message parity with single put. */\n error?: string;\n /**\n * Stable code for a non-conflict failure (upload-limits Phase 5):\n * `STORAGE_QUOTA_EXCEEDED` on a file-count OR byte-quota rejection. Lets a\n * consumer distinguish quota from other per-entry errors (the single-put path\n * carries it on the thrown Error's `.code`).\n */\n code?: string;\n /** This entry's own watermark seq (+1 per write, batch-order ascending).\n * Absent for skip/conflict/error entries and root-level paths (no collection root). */\n liveSeq?: number;\n}\n\nexport interface BatchDeleteEntryResult {\n path: string;\n /** The deleted entry's cid (drives the caller's GC enqueue). */\n deletedCid?: string;\n /** Path had no file entry — per-entry not-found, never aborts neighbors. */\n notFound?: boolean;\n conflict?: BatchEntryConflict;\n error?: string;\n liveSeq?: number;\n}\n\n/** Shared context: a caller-supplied preread snapshot (one IN query reused\n * across the provider's preflight AND the index batch — design §4.3). */\nexport interface BatchIndexContext {\n preread?: Map<string, TreeEntry | null>;\n}\n\n/**\n * Loop reference implementation of `batchPutEntries` — single-op semantics\n * per entry, in order. SQLite/memory indexes delegate here (semantic\n * equivalence with the merged D1 path is pinned by conformance).\n */\nexport async function loopBatchPutEntries(\n index: TreeIndex,\n entries: BatchPutEntry[],\n ctx?: BatchIndexContext,\n): Promise<BatchPutEntryResult[]> {\n const results: BatchPutEntryResult[] = [];\n for (const { path, entry, opts } of entries) {\n const existing = ctx?.preread?.has(path)\n ? (ctx.preread.get(path) ?? null)\n : await index.get(path);\n if (opts.ifMatch !== undefined && (!existing || existing.cid !== opts.ifMatch)) {\n results.push({\n path,\n conflict: {\n currentCid: existing?.cid ?? null,\n currentMtime: existing?.mtime ?? null,\n currentDeviceId: null,\n },\n });\n continue;\n }\n if (\n existing &&\n existing.cid === entry.cid &&\n existing.mimeType === entry.mimeType &&\n treeEntryMetadataEqual(existing.metadata, entry.metadata)\n ) {\n // Metadata-aware skip (did-space-metadata G4): a metadata-only overwrite\n // keeps the same cid, so without this clause it would be wrongly skipped.\n // Content-aware skip (mirror D1TreeIndex.batchPutEntries `:316`): when the\n // index tracks inline content, skip ONLY if the stored column already\n // matches what this write would store; an out-of-sync inline copy falls\n // through to put() so the UPSERT re-fills it (closing the latent loop↔D1\n // divergence). Indexes without inline support keep the legacy cid+mime skip.\n const wouldBeInline = index.inlineForEntry ? await index.inlineForEntry(entry) : undefined;\n if (wouldBeInline === undefined || (existing.content ?? null) === wouldBeInline) {\n results.push({ path, oldCid: existing.cid, skipped: true });\n continue;\n }\n // out of sync — fall through to put() to re-fill the inline column.\n }\n try {\n const { oldCid } = await index.put(path, entry, { deviceId: opts.deviceId });\n const liveSeq = (await index.getPrefixWatermark?.(path)) ?? undefined;\n results.push({\n path,\n ...(oldCid !== undefined ? { oldCid } : {}),\n ...(typeof liveSeq === \"number\" && liveSeq > 0 ? { liveSeq } : {}),\n });\n } catch (err) {\n // Forward a stable `.code` (e.g. STORAGE_QUOTA_EXCEEDED from the byte/file\n // quota) so a batch consumer can distinguish quota from other failures —\n // parity with D1TreeIndex.batchPutEntries' explicit per-entry code.\n const code = (err as { code?: unknown })?.code;\n results.push({\n path,\n error: err instanceof Error ? err.message : String(err),\n ...(typeof code === \"string\" ? { code } : {}),\n });\n }\n }\n return results;\n}\n\n/** Loop reference implementation of `batchDeleteEntries` — see {@link loopBatchPutEntries}. */\nexport async function loopBatchDeleteEntries(\n index: TreeIndex,\n entries: BatchDeleteEntry[],\n ctx?: BatchIndexContext,\n): Promise<BatchDeleteEntryResult[]> {\n const results: BatchDeleteEntryResult[] = [];\n for (const { path, opts } of entries) {\n const existing = ctx?.preread?.has(path)\n ? (ctx.preread.get(path) ?? null)\n : await index.get(path);\n if (opts.ifMatch !== undefined && (!existing || existing.cid !== opts.ifMatch)) {\n results.push({\n path,\n conflict: {\n currentCid: existing?.cid ?? null,\n currentMtime: existing?.mtime ?? null,\n currentDeviceId: null,\n },\n });\n continue;\n }\n if (!existing) {\n results.push({ path, notFound: true });\n continue;\n }\n try {\n await index.delete(path, {\n deviceId: opts.deviceId,\n ...(opts.mtime !== undefined ? { mtime: opts.mtime } : {}),\n });\n const liveSeq = (await index.getPrefixWatermark?.(path)) ?? undefined;\n results.push({\n path,\n deletedCid: existing.cid,\n ...(typeof liveSeq === \"number\" && liveSeq > 0 ? { liveSeq } : {}),\n });\n } catch (err) {\n results.push({ path, error: err instanceof Error ? err.message : String(err) });\n }\n }\n return results;\n}\n\nexport interface TreeIndex {\n /**\n * Read a single file's metadata. `includeContent` (default true) projects the\n * inline `content` copy; metadata-only callers (stat / list / readMeta) pass\n * `false` to skip marshalling the inline bytes. A projection hint — an\n * in-memory backend may still return content (zero marshalling cost there).\n */\n get(path: string, opts?: { includeContent?: boolean }): Promise<TreeEntry | null>;\n\n /**\n * Read a directory row's own `ctime`/`mtime`. Directory rows are scaffolded\n * by the put path (`ancestorDirs` + `buildDirChainStmt`) with no `cid`, so\n * {@link get} — which returns null for cid-less rows — never surfaces them.\n * This is the read used to expose a directory's created/modified time on\n * `stat` (the file path already returns them via {@link get}).\n *\n * `dirPath` is the trailing-slash index key (e.g. `user/photos/`), i.e. the\n * value {@link treeListPrefix} produces. Returns null for a directory with\n * no scaffolded row (the absolute scope root, or an as-yet-unwritten dir).\n *\n * Optional: callers MUST guard and simply omit the timestamps when an\n * implementation does not provide it.\n */\n getDirTimes?(dirPath: string): Promise<{ ctime: number; mtime: number } | null>;\n\n /**\n * Batch metadata read for the sync fast path's ONE-query preflight\n * (afs-rpc-batch §4.3). Returns a map with an entry for EVERY requested\n * path (`null` when absent). Optional: callers fall back to per-path `get`.\n */\n getMany?(paths: string[]): Promise<Map<string, TreeEntry | null>>;\n\n /**\n * Batch `get(filePath)` + `hasChildren(dirPrefix)` in a single storage\n * round-trip. Used by `readHandler` to cut miss-path D1 calls in half:\n * a non-existent file previously paid `get` + `hasChildren` sequentially\n * (2 HTTP round-trips); this collapses both into one batch call.\n * Optional — callers fall back to sequential `get` + `hasChildren` when absent.\n */\n getAndHasChildren?(filePath: string, dirPrefix: string): Promise<[TreeEntry | null, boolean]>;\n\n /**\n * Batch sync fast path (afs-rpc-batch P3) — per-entry opts\n * (deviceId/mtime/ifMatch) and per-entry results (oldCid / skipped /\n * conflict / liveSeq). Entries arrive with UNIQUE paths (the core splits\n * duplicate-path batches) so a one-shot preread is serial-equivalent.\n * Watermark semantics match single `put`: +1 per written entry, each\n * result carrying its own seq. See the section comment above for why this\n * is NOT the legacy `putBatch`.\n */\n batchPutEntries?(\n entries: BatchPutEntry[],\n ctx?: BatchIndexContext,\n ): Promise<BatchPutEntryResult[]>;\n\n /** Delete twin of {@link batchPutEntries}. FILE entries only (no dir fallback). */\n batchDeleteEntries?(\n entries: BatchDeleteEntry[],\n ctx?: BatchIndexContext,\n ): Promise<BatchDeleteEntryResult[]>;\n\n /** Write/update file metadata (ctime preserved by implementation). Returns previous CID if entry existed. */\n put(path: string, entry: TreeEntry, opts: PutOpts): Promise<{ oldCid?: string }>;\n\n /**\n * Dedup-on-write lookup (#1159): resolve the single entry whose\n * `(dedup_scope, dedup_key)` matches within this scope, or `null`. Backed by\n * the `(scope, dedup_scope, dedup_key)` partial UNIQUE index → O(log n) point\n * seek, never a `json_extract` full scan. Returns the entry's REAL path so the\n * provider can merge into it in place.\n *\n * Optional during rollout: a TreeIndex without it makes the owning provider\n * treat `dedupBy` writes as unsupported (explicit error, never silent).\n */\n findByDedupKey?(\n dedupScope: string,\n dedupKey: string,\n ): Promise<({ path: string } & TreeEntry) | null>;\n\n /**\n * Populate the dedup columns of an EXISTING row WITHOUT rewriting its\n * content/cid or emitting a changelog row — the one-time reindex/backfill seam\n * (#1159, deferred script). Index-only UPDATE keyed on `(scope, path)`. No-op\n * if the path has no row. Optional: reindex callers guard on its presence.\n */\n setDedupKey?(path: string, dedupScope: string, dedupKey: string, opts: PutOpts): Promise<void>;\n\n /**\n * Resolve the inline `content` this entry WOULD store under the index's\n * configured inline policy (or `null` for \"store NULL\"). Exposed so\n * {@link loopBatchPutEntries} can mirror D1's content-aware skip — a cid+mime\n * match whose inline column is out of sync must fall through to `put()` to\n * self-repair, instead of being early-skipped. Optional: indexes without\n * inline support omit it and the loop keeps the legacy cid+mime skip.\n */\n inlineForEntry?(entry: TreeEntry): Promise<string | null>;\n\n /** Delete a single file */\n delete(path: string, opts: PutOpts): Promise<void>;\n\n /** List entries under a prefix (for directory browsing) */\n list(prefix: string, options?: TreeIndexListOptions): Promise<TreeIndexListResult>;\n\n /** List entries with full metadata under a prefix */\n listEntries(prefix: string, options?: TreeIndexListOptions): Promise<TreeIndexListEntriesResult>;\n\n /** Check if prefix has any child entries (directory existence) */\n hasChildren(prefix: string): Promise<boolean>;\n\n /** Count entries under a prefix (exact value) */\n count(prefix: string): Promise<number>;\n\n /** Bulk-delete all entries under a prefix, return count deleted */\n deleteByPrefix(prefix: string, opts: PutOpts): Promise<number>;\n\n // ========== v2 methods (Phase 0) ==========\n\n /** List direct children of a parent directory (uses parent index) */\n listChildren(parent: string, options?: TreeIndexListOptions): Promise<TreeIndexListEntriesResult>;\n\n /** Count direct children of a parent directory (exact, O(log n)) */\n countChildren(parent: string): Promise<number>;\n\n /**\n * Count direct children for EACH of the given parent directories in one\n * round-trip, returned as `Map<parentPath, count>` keyed by the same path\n * form `listChildren` yields for a directory child (trailing-slash, and —\n * for {@link PrefixedTreeIndex} — prefix-stripped).\n *\n * PAGE-BOUNDED by contract: callers pass only the directories on the page\n * being listed, so the cost is Σ direct children of those dirs. This\n * replaces the retired subtree-wide `countChildrenGrouped(prefix)`, whose\n * `parent >= lo AND parent < hi` range scanned EVERY descendant (all\n * depths) under the listed prefix on every list — ~100M+ D1 rows_read/day\n * once a hot collection grew to ~1k dirs (2026-06 free-tier exhaustion).\n *\n * Optional: callers MUST fall back to per-path {@link countChildren} when\n * an implementation does not provide it. The returned map omits empty\n * directories (count 0) — look up with `map.get(path) ?? 0`.\n */\n countChildrenMany?(parents: string[]): Promise<Map<string, number>>;\n\n /** Get aggregate stats (file count + total size) for a prefix */\n aggregate(prefix: string): Promise<{ fileCount: number; totalSize: number }>;\n\n /** Batch write entries in a transaction */\n putBatch(entries: Array<{ path: string; entry: TreeEntry }>, opts: PutOpts): Promise<void>;\n\n /** List entries modified since a timestamp */\n listModifiedSince(\n prefix: string,\n since: number,\n options?: TreeIndexListOptions,\n ): Promise<TreeIndexListEntriesResult>;\n\n /**\n * Return the maximum changelog seq for this scope.\n * Used by `DIDSpaceProvider.list(path, { maxDepth: -1 })` to include\n * the snapshot waterline in `meta.cursor`. Returns 0 when the changelog\n * is empty (no writes have been recorded yet).\n */\n getMaxSeq(): Promise<number>;\n\n /**\n * Read the per-prefix watermark `version` for a collection root (Live Update\n * Resilience §11.4). `prefix` is a scope-relative path; the implementation\n * reduces it to its first path segment (the `/user` first-level collection\n * root) and returns that row's `version`, or `0` when no row exists yet\n * (\"nothing written under this collection root\").\n *\n * This is the O(1) change-detector that web reconcile reads on every\n * connection edge: it never scans the changelog and never grows with history\n * or global write volume. Optional during rollout — a TreeIndex without it\n * leaves the owning provider unable to emit `liveSeq` (consumer falls back to\n * full re-list on every edge/poll, still correct).\n *\n * Tri-state return:\n * - `null` — the path has no collection root (no watermark key): the owner\n * emits NO `liveSeq`, consumers stay on the conservative full-refetch path.\n * - `0` — valid collection root with no writes yet: the owner emits\n * `liveSeq: \"0\"` so consumers baseline at 0 and DON'T refetch every poll\n * tick on a read-only collection (the first write bumps to 1 > 0).\n * - `>0` — the row's version.\n */\n getPrefixWatermark?(prefix: string): Promise<number | null>;\n\n /**\n * Return changelog rows with `seq > sinceSeq` (ascending), capped at `limit`.\n *\n * `cursor` is the highest **inner** seq this scan observed — NOT the seq of\n * the last returned entry. This distinction matters for {@link\n * PrefixedTreeIndex}: when a prefix filter drops every row on a page, the page\n * returns `entries: []` but `cursor` still advances to the max inner seq so\n * the caller never re-requests the same window (no infinite loop on empty\n * pages). `hasMore` follows the inner pagination (\"are there more inner rows\n * with seq > cursor\"), not \"are there more rows under this prefix\".\n *\n * On an empty changelog (or no rows past `sinceSeq`) returns\n * `{ entries: [], hasMore: false, cursor: sinceSeq }` — cursor never goes\n * backwards.\n */\n getChangelogSince(\n sinceSeq: number,\n limit: number,\n ): Promise<{ entries: ChangelogEntry[]; hasMore: boolean; cursor: number }>;\n\n /**\n * Compaction watermark for this scope (Phase 1 of changelog compaction).\n * Returns 0 when no compaction has ever run (safe permissive default).\n */\n getCompactedBelowSeq?(): Promise<number>;\n\n /**\n * Collection query over DIRECT children of `parent` (afs-collection-query\n * Phase 1, design §3.5): typed where σ on the record document (doc-joined\n * for dir-per-record, inline content for file-per-record), entry-column\n * orderBy, exact post-WHERE total, keyset/offset pagination, groupBy\n * counting. Spec semantics are locked to the core reference evaluator by\n * conformance + the cross-backend lockstep test (`tree-query.ts`).\n *\n * Optional during rollout: implementations without it leave the owning\n * provider undeclared (the root action then fails AFS_UNSUPPORTED).\n */\n query?(\n parent: string,\n spec: import(\"./tree-query.js\").TreeQuerySpec,\n ): Promise<import(\"./tree-query.js\").TreeQueryResult>;\n\n /**\n * True if `cid` is referenced within THIS index's VIEW (read-**auth**,\n * design §4.1) — three-way `entries.cid ∪ changelog.cid ∪ changelog.old_cid`,\n * always `WHERE scope = ?`.\n *\n * `pathPrefix` (default `\"\"`) restricts the check to a sub-tree\n * (`path >= lo AND path < hi`). {@link PrefixedTreeIndex} passes its own\n * prefix so a blocklet/role provider authorizes ONLY cids its own fragment\n * references (P0#2 blocklet isolation); the unprefixed space-root provider\n * leaves it `\"\"` and stays whole-scope.\n *\n * This is the authorization check, distinct from the scope-blind,\n * entries-only GC-liveness check `referencedAnywhere` (design §3, §4.4b).\n * Returns false for empty `cid`.\n */\n isReferenced(cid: string, pathPrefix?: string): Promise<boolean>;\n\n /**\n * Batch read-auth (download plane, design §7.2): given a set of cids, return\n * the SUBSET that is referenced within THIS index's VIEW — exactly the cids\n * for which {@link isReferenced} would return `true`. A cid NOT in the result\n * is `forbidden` (covers both unreferenced AND not-found — never distinguish).\n *\n * MUST be three-way-equivalent to per-cid `isReferenced`\n * (`entries.cid ∪ changelog.cid ∪ changelog.old_cid`, scope-aware, optionally\n * prefix-local) — a superseded cid that left `entries` but lives in\n * `changelog.old_cid` MUST still be returned, or the read-auth invariant\n * breaks (design Finding 2). The SQL backends shard the `IN (?,…)` list to\n * stay under the bound-parameter cap (D1 = 100; the three-way UNION binds each\n * cid 3×, so the per-query cid budget is small). Empty input → empty set, no\n * query (avoid `IN ()`).\n */\n isReferencedMany(cids: string[], pathPrefix?: string): Promise<Set<string>>;\n\n // ========== Source-side dirty queue (index-follower-dirty-path) ==========\n //\n // The `ds_dirty_path` projection of the changelog, drained by the index\n // follower instead of replaying `/.changelog` (design §7). These three form\n // the {@link import(\"./dirty-path-queue.js\").DirtyPathQueue} contract; a\n // TreeIndex that implements all three IS a DirtyPathQueue structurally. They\n // are RUNTIME-INTERNAL (never a public `/user` AFS action — design §7\n // security boundary). Optional during rollout: raw backends (D1/SQLite/Memory)\n // serve full-path rows; {@link PrefixedTreeIndex} adds the prefix on the way\n // in and strips it off the returned `path`.\n\n /** Ready dirty rows under `prefix` with `next_attempt_at <= now`, capped at `limit`. */\n listDirtyPaths?(options: ListDirtyPathOptions): Promise<DirtyPathRow[]>;\n\n /** Guarded ack: delete the dirty row iff its `latest_seq` still equals `latestSeq`. */\n ackDirtyPath?(path: string, latestSeq: number): Promise<boolean>;\n\n /** Guarded failure record: bump attempts + set last_error/next_attempt_at on the matching row. */\n markDirtyPathError?(\n path: string,\n latestSeq: number,\n error: string,\n nextAttemptAt: number,\n ): Promise<boolean>;\n}\n"],"mappings":";;;;;;;;;AAUA,MAAM,MAAM,UAAU,qBAAqB;;;;;;;;AAS3C,MAAa,8BAA8B;;AAG3C,SAAgB,kBAAkB,SAAwB;AACxD,QAAO,OAAO,OAAO,IAAI,MAAM,QAAQ,EAAE,EAAE,MAAM,6BAA6B,CAAC;;;;;;;;;;;AAsEjF,SAAgB,uBAAuB,KAAmD;AACxF,KAAI,OAAO,KAAM,QAAO;AACxB,KAAI,OAAO,QAAQ,YAAY,IAAI,WAAW,EAAG,QAAO;AACxD,KAAI;EACF,MAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,MAAI,UAAU,OAAO,WAAW,YAAY,CAAC,MAAM,QAAQ,OAAO,CAChE,QAAO;AAET,MAAI,KAAK,uEAAuE;AAChF;UACO,KAAK;EACZ,MAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAC5D,MAAI,KAAK,iDAAiD,IAAI,sBAAsB;AACpF;;;;;;;;;;;AAYJ,SAAgB,2BACd,UACe;AACf,KAAI,YAAY,KAAM,QAAO;AAE7B,KADa,OAAO,KAAK,SAAS,CACzB,WAAW,EAAG,QAAO;AAC9B,QAAO,KAAK,UAAU,SAAS;;;AAIjC,SAAS,UAAU,GAAY,GAAqB;AAClD,KAAI,MAAM,EAAG,QAAO;AACpB,KAAI,MAAM,QAAQ,MAAM,QAAQ,OAAO,MAAM,YAAY,OAAO,MAAM,SAAU,QAAO;CACvF,MAAM,OAAO,MAAM,QAAQ,EAAE;CAC7B,MAAM,OAAO,MAAM,QAAQ,EAAE;AAC7B,KAAI,SAAS,KAAM,QAAO;AAC1B,KAAI,QAAQ,MAAM;AAChB,MAAI,EAAE,WAAW,EAAE,OAAQ,QAAO;AAClC,OAAK,IAAI,IAAI,GAAG,IAAI,EAAE,QAAQ,IAAK,KAAI,CAAC,UAAU,EAAE,IAAI,EAAE,GAAG,CAAE,QAAO;AACtE,SAAO;;CAET,MAAM,KAAK;CACX,MAAM,KAAK;CACX,MAAM,KAAK,OAAO,KAAK,GAAG;AAC1B,KAAI,GAAG,WAAW,OAAO,KAAK,GAAG,CAAC,OAAQ,QAAO;AACjD,MAAK,MAAM,KAAK,IAAI;AAClB,MAAI,CAAC,OAAO,OAAO,IAAI,EAAE,CAAE,QAAO;AAClC,MAAI,CAAC,UAAU,GAAG,IAAI,GAAG,GAAG,CAAE,QAAO;;AAEvC,QAAO;;;;;;;;;;;;AAaT,SAAgB,uBACd,GACA,GACS;CACT,MAAM,SAAS,KAAK,QAAQ,OAAO,KAAK,EAAE,CAAC,WAAW;CACtD,MAAM,SAAS,KAAK,QAAQ,OAAO,KAAK,EAAE,CAAC,WAAW;AACtD,KAAI,UAAU,OAAQ,QAAO,UAAU;AACvC,QAAO,UAAU,GAAG,EAAE;;;AA+CxB,SAAS,UAAU,GAAmB;CACpC,MAAM,QAAQ,IAAI,aAAa,CAAC,OAAO,EAAE;CACzC,IAAI,MAAM;AACV,MAAK,MAAM,KAAK,MAAO,QAAO,OAAO,aAAa,EAAE;AACpD,QAAO,KAAK,IAAI;;AAGlB,SAAS,UAAU,GAAmB;CACpC,MAAM,MAAM,KAAK,EAAE;AACnB,QAAO,IAAI,aAAa,CAAC,OAAO,WAAW,KAAK,MAAM,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC;;;AAI/E,SAAgB,kBAAkB,WAAmC,KAAqB;AACxF,QAAO,MAAM,UAAU,KAAK,UAAU,UAAU,CAAC,CAAC,GAAG,UAAU,IAAI;;;;;;;;;;;;;AAmBrE,SAAgB,gBAAgB,QAA4B,YAAuC;AACjG,KAAI,CAAC,OAAQ,QAAO,EAAE,MAAM,QAAQ;AACpC,KAAI,OAAO,WAAW,MAAM,EAAE;AAC5B,MAAI,CAAC,WACH,OAAM,IAAI,MAAM,+DAA+D;EAEjF,MAAM,QAAQ,OAAO,MAAM,IAAI;AAC/B,MAAI,MAAM,WAAW,KAAK,CAAC,MAAM,MAAM,CAAC,MAAM,GAC5C,OAAM,IAAI,MAAM,6CAA6C;EAE/D,IAAI;EACJ,IAAI;AACJ,MAAI;AACF,eAAY,KAAK,MAAM,UAAU,MAAM,GAAG,CAAC;AAC3C,SAAM,UAAU,MAAM,GAAG;UACnB;AACN,SAAM,IAAI,MAAM,6CAA6C;;AAE/D,MAAI,cAAc,QAAQ,OAAO,cAAc,YAAY,OAAO,cAAc,SAC9E,OAAM,IAAI,MAAM,6CAA6C;AAE/D,SAAO;GAAE,MAAM;GAAM,QAAQ;IAAE;IAAW;IAAK;GAAE;;AAEnD,KAAI,WACF,OAAM,IAAI,MAAM,gEAAgE;AAElF,QAAO;EAAE,MAAM;EAAU,OAAO;EAAQ;;;;;;;;;;;;;AAuB1C,SAAgB,uBAAuB,MAMjB;CACpB,MAAM,EAAE,KAAK,UAAU,QAAQ,WAAW;CAC1C,MAAM,MAAM,KAAK,UAAU,SAAS,SAAS;CAC7C,MAAM,KAAK,KAAK,UAAU,SAAS,MAAM;CACzC,MAAM,WAAW,WACb,IAAI,IAAI,iBAAiB,IAAI,GAAG,IAAI,IAAI,OAAO,GAAG,QAClD,GAAG,IAAI,GAAG,IAAI,IAAI,OAAO,GAAG;AAEhC,KAAI,CAAC,OACH,QAAO;EAAE,gBAAgB;EAAI,YAAY,EAAE;EAAE;EAAU;AAEzD,KAAI,OAAO,cAAc,MAAM;AAC7B,MAAI,CAAC,SACH,OAAM,IAAI,MAAM,6CAA6C;AAG/D,SAAO;GACL,gBAAgB,IAAI,IAAI,eAAe,OAAO,GAAG,GAAG;GACpD,YAAY,CAAC,OAAO,IAAI;GACxB;GACD;;AAEH,KAAI,SAGF,QAAO;EACL,gBAAgB,KAAK,IAAI,oBAAoB,IAAI,GAAG,GAAG,SAAS,IAAI,WAAW,OAAO,GAAG,GAAG,WAAW,IAAI;EAC3G,YAAY;GAAC,OAAO;GAAW,OAAO;GAAW,OAAO;GAAI;EAC5D;EACD;AAEH,QAAO;EACL,gBAAgB,IAAI,IAAI,GAAG,GAAG,SAAS,IAAI,WAAW,OAAO,GAAG,GAAG;EACnE,YAAY;GAAC,OAAO;GAAW,OAAO;GAAW,OAAO;GAAI;EAC5D;EACD;;;;;;AAOH,SAAgB,sBACd,GACA,GACA,OACQ;AAER,KAAI,MAAM,QAAQ,MAAM,KAAM,QAAO;AACrC,KAAI,MAAM,KAAM,QAAO;AACvB,KAAI,MAAM,KAAM,QAAO;CACvB,IAAI;AACJ,KAAI,OAAO,MAAM,YAAY,OAAO,MAAM,SACxC,OAAM,MAAM,IAAI,IAAI,IAAI,IAAI,KAAK;MAC5B;EACL,MAAM,KAAK,OAAO,EAAE;EACpB,MAAM,KAAK,OAAO,EAAE;AACpB,QAAM,OAAO,KAAK,IAAI,KAAK,KAAK,KAAK;;AAEvC,QAAO,UAAU,SAAS,CAAC,MAAM;;;AAInC,SAAgB,YACd,SACA,QACA,QACA,OACS;CACT,MAAM,WAAW,UAAU,SAAS,SAAS,OAAO,MAAM,SAAS,OAAO;AAC1E,KAAI,OAAO,cAAc,KAEvB,QAAO,YAAY,QAAQ;AAE7B,KAAI,YAAY,KAAM,QAAO;CAC7B,MAAM,MAAM,sBAAsB,SAAS,OAAO,WAAW,MAAM;AACnE,KAAI,QAAQ,EAAG,QAAO,MAAM;AAC5B,QAAO;;;;;;;AAuJT,eAAsB,oBACpB,OACA,SACA,KACgC;CAChC,MAAM,UAAiC,EAAE;AACzC,MAAK,MAAM,EAAE,MAAM,OAAO,UAAU,SAAS;EAC3C,MAAM,WAAW,KAAK,SAAS,IAAI,KAAK,GACnC,IAAI,QAAQ,IAAI,KAAK,IAAI,OAC1B,MAAM,MAAM,IAAI,KAAK;AACzB,MAAI,KAAK,YAAY,WAAc,CAAC,YAAY,SAAS,QAAQ,KAAK,UAAU;AAC9E,WAAQ,KAAK;IACX;IACA,UAAU;KACR,YAAY,UAAU,OAAO;KAC7B,cAAc,UAAU,SAAS;KACjC,iBAAiB;KAClB;IACF,CAAC;AACF;;AAEF,MACE,YACA,SAAS,QAAQ,MAAM,OACvB,SAAS,aAAa,MAAM,YAC5B,uBAAuB,SAAS,UAAU,MAAM,SAAS,EACzD;GAQA,MAAM,gBAAgB,MAAM,iBAAiB,MAAM,MAAM,eAAe,MAAM,GAAG;AACjF,OAAI,kBAAkB,WAAc,SAAS,WAAW,UAAU,eAAe;AAC/E,YAAQ,KAAK;KAAE;KAAM,QAAQ,SAAS;KAAK,SAAS;KAAM,CAAC;AAC3D;;;AAIJ,MAAI;GACF,MAAM,EAAE,WAAW,MAAM,MAAM,IAAI,MAAM,OAAO,EAAE,UAAU,KAAK,UAAU,CAAC;GAC5E,MAAM,UAAW,MAAM,MAAM,qBAAqB,KAAK,IAAK;AAC5D,WAAQ,KAAK;IACX;IACA,GAAI,WAAW,SAAY,EAAE,QAAQ,GAAG,EAAE;IAC1C,GAAI,OAAO,YAAY,YAAY,UAAU,IAAI,EAAE,SAAS,GAAG,EAAE;IAClE,CAAC;WACK,KAAK;GAIZ,MAAM,OAAQ,KAA4B;AAC1C,WAAQ,KAAK;IACX;IACA,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;IACvD,GAAI,OAAO,SAAS,WAAW,EAAE,MAAM,GAAG,EAAE;IAC7C,CAAC;;;AAGN,QAAO;;;AAIT,eAAsB,uBACpB,OACA,SACA,KACmC;CACnC,MAAM,UAAoC,EAAE;AAC5C,MAAK,MAAM,EAAE,MAAM,UAAU,SAAS;EACpC,MAAM,WAAW,KAAK,SAAS,IAAI,KAAK,GACnC,IAAI,QAAQ,IAAI,KAAK,IAAI,OAC1B,MAAM,MAAM,IAAI,KAAK;AACzB,MAAI,KAAK,YAAY,WAAc,CAAC,YAAY,SAAS,QAAQ,KAAK,UAAU;AAC9E,WAAQ,KAAK;IACX;IACA,UAAU;KACR,YAAY,UAAU,OAAO;KAC7B,cAAc,UAAU,SAAS;KACjC,iBAAiB;KAClB;IACF,CAAC;AACF;;AAEF,MAAI,CAAC,UAAU;AACb,WAAQ,KAAK;IAAE;IAAM,UAAU;IAAM,CAAC;AACtC;;AAEF,MAAI;AACF,SAAM,MAAM,OAAO,MAAM;IACvB,UAAU,KAAK;IACf,GAAI,KAAK,UAAU,SAAY,EAAE,OAAO,KAAK,OAAO,GAAG,EAAE;IAC1D,CAAC;GACF,MAAM,UAAW,MAAM,MAAM,qBAAqB,KAAK,IAAK;AAC5D,WAAQ,KAAK;IACX;IACA,YAAY,SAAS;IACrB,GAAI,OAAO,YAAY,YAAY,UAAU,IAAI,EAAE,SAAS,GAAG,EAAE;IAClE,CAAC;WACK,KAAK;AACZ,WAAQ,KAAK;IAAE;IAAM,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;IAAE,CAAC;;;AAGnF,QAAO"}