@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,297 @@
1
+ import { SYNC_STATE_INDEX_SQL, SYNC_STATE_TABLE_SQL } from "./changelog-schema.mjs";
2
+
3
+ //#region src/sync-state.ts
4
+ /**
5
+ * SyncStateStore — Phase 3 Task 3.5 (G7)
6
+ *
7
+ * Typed key-value façade over the `sync_state` table introduced by
8
+ * Task 3.4. Where {@link LocalChangelog} owns the bare minimum of the
9
+ * `{scope}:local_seq` and `{scope}:cloud_seq` keys for changelog GC,
10
+ * SyncStateStore exposes the FULL set of progress fields the local
11
+ * SyncEngine needs:
12
+ *
13
+ * - device_id (per-user, NOT scope-prefixed)
14
+ * - {scope}:local_seq
15
+ * - {scope}:cloud_seq
16
+ * - {scope}:last_sync_at
17
+ * - {scope}:sync_status
18
+ *
19
+ * The class is intentionally a sibling of LocalChangelog rather than a
20
+ * refactor of it. Both operate on the same SQLite `sync_state` table and
21
+ * share monotonic semantics for `local_seq` / `cloud_seq`; cross-class
22
+ * parity is asserted by tests so the SyncEngine can mix the two without
23
+ * surprises. Refactoring LocalChangelog into a thin wrapper around
24
+ * SyncStateStore is a future optimisation that Task 3.6 (SyncEngine) can
25
+ * decide to make if it becomes ergonomic.
26
+ *
27
+ * Platform notes:
28
+ * - TypeScript / desktop: device_id is stored ONLY in this SQLite table.
29
+ * There is no Keychain equivalent on desktop, so the file IS the SoT.
30
+ * - Swift / iOS: Keychain is the SoT; this SQLite cache is rebuilt
31
+ * on startup.
32
+ * - Kotlin / Android: AndroidKeystore + EncryptedSharedPreferences is
33
+ * the SoT; SQLite is again the cache.
34
+ *
35
+ * The two mobile runtimes implement their own SyncStateStore + DeviceIDStore
36
+ * pair under `swift/Sources/AFSDIDSpace/` and
37
+ * `kotlin/afs-didspace/.../io/aigne/afs/didspace/`. The TypeScript class
38
+ * here matches their public surface as closely as the language allows.
39
+ */
40
+ /** Default `AppState` returned by load() when nothing is persisted yet. */
41
+ function defaultAppState() {
42
+ return {
43
+ deviceId: "",
44
+ cloudSeq: 0,
45
+ localSeq: 0,
46
+ lastSyncAt: 0,
47
+ syncStatus: { kind: "idle" }
48
+ };
49
+ }
50
+ /**
51
+ * Registry of the raw `sync_state` keys this class manages. Exported so
52
+ * tests and the eventual SyncEngine can introspect the wire format without
53
+ * hard-coding strings.
54
+ *
55
+ * Keys marked `scoped` are written as `{scope}:{name}`. The `device_id`
56
+ * key is unique among the registry — it is per-user and intentionally NOT
57
+ * scope-prefixed so a single device sharing two DID Space scopes still
58
+ * presents the same identity to both.
59
+ */
60
+ const SYNC_STATE_KEYS = {
61
+ deviceId: {
62
+ name: "device_id",
63
+ scoped: false
64
+ },
65
+ localSeq: {
66
+ name: "local_seq",
67
+ scoped: true
68
+ },
69
+ cloudSeq: {
70
+ name: "cloud_seq",
71
+ scoped: true
72
+ },
73
+ lastSyncAt: {
74
+ name: "last_sync_at",
75
+ scoped: true
76
+ },
77
+ syncStatus: {
78
+ name: "sync_status",
79
+ scoped: true
80
+ }
81
+ };
82
+ /**
83
+ * Serialise a {@link SyncStatus} to its on-disk string form.
84
+ *
85
+ * Format (matches the spec acceptance criteria):
86
+ * - `idle` → `"idle"`
87
+ * - `syncing` → `"syncing"`
88
+ * - `active` → `"active"`
89
+ * - `error:{message}` → `"error:{message}"`
90
+ *
91
+ * The `error` branch preserves the full message verbatim. Newlines and
92
+ * colons inside the message are tolerated; parsing is greedy on the prefix
93
+ * `error:` so the remainder of the string is the message regardless of
94
+ * what punctuation it contains.
95
+ */
96
+ function serializeSyncStatus(status) {
97
+ switch (status.kind) {
98
+ case "idle": return "idle";
99
+ case "syncing": return "syncing";
100
+ case "active": return "active";
101
+ case "error": return `error:${status.message}`;
102
+ }
103
+ }
104
+ /**
105
+ * Parse a `sync_status` string back into a {@link SyncStatus}. Throws on
106
+ * unrecognised forms — this matches LocalChangelog's "corrupt value =
107
+ * surface the bug" stance from Task 3.4.
108
+ */
109
+ function parseSyncStatus(raw) {
110
+ if (raw === "idle") return { kind: "idle" };
111
+ if (raw === "syncing") return { kind: "syncing" };
112
+ if (raw === "active") return { kind: "active" };
113
+ if (raw.startsWith("error:")) return {
114
+ kind: "error",
115
+ message: raw.slice(6)
116
+ };
117
+ throw new Error(`SyncStateStore: corrupt sync_status value: ${raw}`);
118
+ }
119
+ /**
120
+ * Strict device_id format regex. Must match the server-side validator in
121
+ * `services/arc-computer/src/handlers/validate-headers.ts` so locally
122
+ * generated identifiers round-trip through the cloud sync endpoints
123
+ * unchanged. Format: `<class>:<platform>:<suffix>`.
124
+ *
125
+ * Duplicated locally so this module has zero cross-package imports — the
126
+ * source of truth lives in `validate-headers.ts` and any change there must
127
+ * be mirrored here AND in the Swift/Kotlin equivalents.
128
+ */
129
+ const DEVICE_ID_FORMAT_RE = /^(aside|desktop|server):[a-z0-9-]+:[a-z0-9-]+$/;
130
+ /** Validate a device_id string. Returns true on match. */
131
+ function isValidDeviceId(deviceId) {
132
+ return DEVICE_ID_FORMAT_RE.test(deviceId);
133
+ }
134
+ /**
135
+ * Generate a fresh device_id for the current TypeScript desktop runtime.
136
+ * Format: `desktop:ts:<uuid v4>`. Tests can pass an injected UUID to
137
+ * keep generation deterministic.
138
+ */
139
+ function generateDesktopDeviceId(uuid) {
140
+ return `desktop:ts:${uuid ?? crypto.randomUUID()}`;
141
+ }
142
+ var SyncStateStore = class {
143
+ client;
144
+ scope;
145
+ constructor(opts) {
146
+ if (typeof opts.scope !== "string" || opts.scope.length === 0) throw new Error("SyncStateStore: scope must be a non-empty string");
147
+ this.client = opts.client;
148
+ this.scope = opts.scope;
149
+ }
150
+ /**
151
+ * Idempotently create the `sync_state` table. MetadataDB.open() and the
152
+ * Swift/Kotlin SQLiteTreeIndex.migrate() already create it; this static
153
+ * helper exists for test fixtures that build a SyncStateStore on a raw
154
+ * libsql client. Reuses the canonical DDL from `changelog-schema.ts` so
155
+ * three-runtime parity is preserved.
156
+ */
157
+ static async ensureTable(client) {
158
+ await client.execute(SYNC_STATE_TABLE_SQL);
159
+ for (const sql of SYNC_STATE_INDEX_SQL) await client.execute(sql);
160
+ }
161
+ /**
162
+ * Build the on-disk key for a given AppState field. Per-scope fields
163
+ * are prefixed with `{scope}:`; the per-user `device_id` field is the
164
+ * raw bare name. Exposed as a private helper (rather than inlined) so
165
+ * the parity tests can introspect the rule via SYNC_STATE_KEYS.
166
+ */
167
+ keyFor(field) {
168
+ const meta = SYNC_STATE_KEYS[field];
169
+ return meta.scoped ? `${this.scope}:${meta.name}` : meta.name;
170
+ }
171
+ /**
172
+ * Read the full {@link AppState} for this scope. Missing fields fall
173
+ * back to the defaults from {@link defaultAppState}.
174
+ *
175
+ * If `device_id` is absent, this method generates a fresh one using
176
+ * {@link generateDesktopDeviceId} and persists it back to the same
177
+ * SQLite cache before returning. Subsequent loads will see the same
178
+ * value (this is the "TS desktop has no Keychain — file IS the SoT"
179
+ * branch from the spec).
180
+ *
181
+ * Corrupt values throw — corrupt `cloud_seq` / `local_seq` / `last_sync_at`
182
+ * non-numeric, corrupt `sync_status` not in the recognised set, corrupt
183
+ * `device_id` failing the strict regex.
184
+ */
185
+ async load() {
186
+ const state = defaultAppState();
187
+ const result = await this.client.execute({
188
+ sql: `SELECT key, value FROM ds_sync_state WHERE key IN (?, ?, ?, ?, ?)`,
189
+ args: [
190
+ this.keyFor("deviceId"),
191
+ this.keyFor("cloudSeq"),
192
+ this.keyFor("localSeq"),
193
+ this.keyFor("lastSyncAt"),
194
+ this.keyFor("syncStatus")
195
+ ]
196
+ });
197
+ const rows = /* @__PURE__ */ new Map();
198
+ for (const row of result.rows) {
199
+ const k = row[0];
200
+ const v = row[1];
201
+ if (typeof k === "string" && (typeof v === "string" || typeof v === "number")) rows.set(k, String(v));
202
+ }
203
+ const rawDeviceId = rows.get(this.keyFor("deviceId"));
204
+ if (rawDeviceId !== void 0) {
205
+ if (!isValidDeviceId(rawDeviceId)) throw new Error(`SyncStateStore: corrupt device_id value: ${rawDeviceId}`);
206
+ state.deviceId = rawDeviceId;
207
+ } else {
208
+ const fresh = generateDesktopDeviceId();
209
+ await this.writeKey(this.keyFor("deviceId"), fresh);
210
+ state.deviceId = fresh;
211
+ }
212
+ state.cloudSeq = parseSeqValue(rows.get(this.keyFor("cloudSeq")), "cloud_seq");
213
+ state.localSeq = parseSeqValue(rows.get(this.keyFor("localSeq")), "local_seq");
214
+ state.lastSyncAt = parseSeqValue(rows.get(this.keyFor("lastSyncAt")), "last_sync_at");
215
+ const rawStatus = rows.get(this.keyFor("syncStatus"));
216
+ if (rawStatus !== void 0) state.syncStatus = parseSyncStatus(rawStatus);
217
+ return state;
218
+ }
219
+ /**
220
+ * Persist a partial update to the {@link AppState}. Each field is
221
+ * written via INSERT OR REPLACE on its individual key; concurrent
222
+ * save() calls touching disjoint fields cannot lose data.
223
+ *
224
+ * Monotonic fields:
225
+ * - `cloudSeq` and `localSeq` use the same CASE expression as
226
+ * {@link LocalChangelog.markPushed}, so a save() with a lower value
227
+ * than the current watermark is silently ignored. This keeps a
228
+ * SyncEngine + LocalChangelog mixed-write path safe.
229
+ *
230
+ * Non-monotonic fields:
231
+ * - `lastSyncAt`, `syncStatus`, `deviceId` are written as-is. The
232
+ * state machine (`idle → syncing → active → idle` etc.) and the
233
+ * `last_sync_at` clock have valid backwards transitions in some
234
+ * edge cases (test reset, manual rollback) and we must not block
235
+ * them.
236
+ *
237
+ * Unknown fields are caught at compile time by `Partial<AppState>`.
238
+ * Defense-in-depth: at runtime we iterate the explicit list of fields
239
+ * we know how to serialise rather than `Object.keys(update)`.
240
+ */
241
+ async save(update) {
242
+ if (update.deviceId !== void 0) {
243
+ if (!isValidDeviceId(update.deviceId)) throw new Error(`SyncStateStore: refusing to save invalid device_id: ${update.deviceId}`);
244
+ await this.writeKey(this.keyFor("deviceId"), update.deviceId);
245
+ }
246
+ if (update.cloudSeq !== void 0) await this.writeKeyMonotonic(this.keyFor("cloudSeq"), update.cloudSeq);
247
+ if (update.localSeq !== void 0) await this.writeKeyMonotonic(this.keyFor("localSeq"), update.localSeq);
248
+ if (update.lastSyncAt !== void 0) {
249
+ const safe = Math.max(0, Math.floor(Number.isFinite(update.lastSyncAt) ? update.lastSyncAt : 0));
250
+ await this.writeKey(this.keyFor("lastSyncAt"), String(safe));
251
+ }
252
+ if (update.syncStatus !== void 0) await this.writeKey(this.keyFor("syncStatus"), serializeSyncStatus(update.syncStatus));
253
+ }
254
+ /** Plain INSERT OR REPLACE for non-monotonic keys. */
255
+ async writeKey(key, value) {
256
+ await this.client.execute({
257
+ sql: `INSERT INTO ds_sync_state (key, value) VALUES (?, ?)
258
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value`,
259
+ args: [key, value]
260
+ });
261
+ }
262
+ /**
263
+ * Monotonic INSERT OR REPLACE for integer keys. Mirror of the helper in
264
+ * {@link LocalChangelog.writeSeqKeyMonotonic} so the two classes converge
265
+ * on the same on-disk representation regardless of which one performs
266
+ * the write.
267
+ */
268
+ async writeKeyMonotonic(key, seq) {
269
+ const safe = Math.max(0, Math.floor(Number.isFinite(seq) ? seq : 0));
270
+ await this.client.execute({
271
+ sql: `INSERT INTO ds_sync_state (key, value) VALUES (?, ?)
272
+ ON CONFLICT(key) DO UPDATE SET
273
+ value = CASE
274
+ WHEN CAST(excluded.value AS INTEGER) > CAST(ds_sync_state.value AS INTEGER)
275
+ THEN excluded.value
276
+ ELSE ds_sync_state.value
277
+ END`,
278
+ args: [key, String(safe)]
279
+ });
280
+ }
281
+ };
282
+ /**
283
+ * Parse a stored numeric value (cloud_seq / local_seq / last_sync_at).
284
+ *
285
+ * Missing → 0 (the "nothing recorded yet" sentinel, matches LocalChangelog).
286
+ * Non-integer / non-numeric → throws to surface corruption to the caller.
287
+ */
288
+ function parseSeqValue(raw, label) {
289
+ if (raw === void 0) return 0;
290
+ const parsed = Number(raw);
291
+ if (!Number.isFinite(parsed) || !Number.isInteger(parsed)) throw new Error(`SyncStateStore: corrupt ${label} value: ${raw}`);
292
+ return parsed;
293
+ }
294
+
295
+ //#endregion
296
+ export { DEVICE_ID_FORMAT_RE, SYNC_STATE_KEYS, SyncStateStore, defaultAppState, generateDesktopDeviceId, isValidDeviceId, parseSyncStatus, serializeSyncStatus };
297
+ //# sourceMappingURL=sync-state.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-state.mjs","names":[],"sources":["../src/sync-state.ts"],"sourcesContent":["/**\n * SyncStateStore — Phase 3 Task 3.5 (G7)\n *\n * Typed key-value façade over the `sync_state` table introduced by\n * Task 3.4. Where {@link LocalChangelog} owns the bare minimum of the\n * `{scope}:local_seq` and `{scope}:cloud_seq` keys for changelog GC,\n * SyncStateStore exposes the FULL set of progress fields the local\n * SyncEngine needs:\n *\n * - device_id (per-user, NOT scope-prefixed)\n * - {scope}:local_seq\n * - {scope}:cloud_seq\n * - {scope}:last_sync_at\n * - {scope}:sync_status\n *\n * The class is intentionally a sibling of LocalChangelog rather than a\n * refactor of it. Both operate on the same SQLite `sync_state` table and\n * share monotonic semantics for `local_seq` / `cloud_seq`; cross-class\n * parity is asserted by tests so the SyncEngine can mix the two without\n * surprises. Refactoring LocalChangelog into a thin wrapper around\n * SyncStateStore is a future optimisation that Task 3.6 (SyncEngine) can\n * decide to make if it becomes ergonomic.\n *\n * Platform notes:\n * - TypeScript / desktop: device_id is stored ONLY in this SQLite table.\n * There is no Keychain equivalent on desktop, so the file IS the SoT.\n * - Swift / iOS: Keychain is the SoT; this SQLite cache is rebuilt\n * on startup.\n * - Kotlin / Android: AndroidKeystore + EncryptedSharedPreferences is\n * the SoT; SQLite is again the cache.\n *\n * The two mobile runtimes implement their own SyncStateStore + DeviceIDStore\n * pair under `swift/Sources/AFSDIDSpace/` and\n * `kotlin/afs-didspace/.../io/aigne/afs/didspace/`. The TypeScript class\n * here matches their public surface as closely as the language allows.\n */\n\nimport { SYNC_STATE_INDEX_SQL, SYNC_STATE_TABLE_SQL } from \"./changelog-schema.js\";\n\n/** Discriminated union shape of `sync_status`. */\nexport type SyncStatus =\n | { kind: \"idle\" }\n | { kind: \"syncing\" }\n | { kind: \"active\" }\n | { kind: \"error\"; message: string };\n\n/**\n * Full snapshot of the per-scope sync state. Returned by {@link SyncStateStore.load}.\n *\n * `deviceId` is per-USER-DID (stable across all scopes for the same install)\n * and is therefore stored under the bare key `device_id` — NOT scope-prefixed.\n * Every other field is scope-prefixed internally so multiple scopes sharing\n * the same SQLite file stay isolated.\n */\nexport interface AppState {\n /** Stable device identifier. Generated on first load() if missing. */\n deviceId: string;\n /** Highest changelog.seq pulled+applied from other devices. */\n cloudSeq: number;\n /** Highest changelog.seq this device has pushed to cloud. */\n localSeq: number;\n /** Unix millis of the last successful sync round (any leg). */\n lastSyncAt: number;\n /** Current sync state machine value. */\n syncStatus: SyncStatus;\n}\n\n/** Default `AppState` returned by load() when nothing is persisted yet. */\nexport function defaultAppState(): AppState {\n return {\n deviceId: \"\",\n cloudSeq: 0,\n localSeq: 0,\n lastSyncAt: 0,\n syncStatus: { kind: \"idle\" },\n };\n}\n\n/**\n * Registry of the raw `sync_state` keys this class manages. Exported so\n * tests and the eventual SyncEngine can introspect the wire format without\n * hard-coding strings.\n *\n * Keys marked `scoped` are written as `{scope}:{name}`. The `device_id`\n * key is unique among the registry — it is per-user and intentionally NOT\n * scope-prefixed so a single device sharing two DID Space scopes still\n * presents the same identity to both.\n */\nexport const SYNC_STATE_KEYS = {\n deviceId: { name: \"device_id\", scoped: false },\n localSeq: { name: \"local_seq\", scoped: true },\n cloudSeq: { name: \"cloud_seq\", scoped: true },\n lastSyncAt: { name: \"last_sync_at\", scoped: true },\n syncStatus: { name: \"sync_status\", scoped: true },\n} as const;\n\nexport interface SyncStateStoreOptions {\n /**\n * Already-opened libsql client sharing the same DB file as the TreeIndex.\n * SyncStateStore does NOT own the client lifecycle — callers (typically\n * `DIDSpaceProvider` construction) remain responsible for close().\n */\n client: import(\"@libsql/client\").Client;\n /** Scope string used to prefix per-scope keys. Must be non-empty. */\n scope: string;\n}\n\n/**\n * Serialise a {@link SyncStatus} to its on-disk string form.\n *\n * Format (matches the spec acceptance criteria):\n * - `idle` → `\"idle\"`\n * - `syncing` → `\"syncing\"`\n * - `active` → `\"active\"`\n * - `error:{message}` → `\"error:{message}\"`\n *\n * The `error` branch preserves the full message verbatim. Newlines and\n * colons inside the message are tolerated; parsing is greedy on the prefix\n * `error:` so the remainder of the string is the message regardless of\n * what punctuation it contains.\n */\nexport function serializeSyncStatus(status: SyncStatus): string {\n switch (status.kind) {\n case \"idle\":\n return \"idle\";\n case \"syncing\":\n return \"syncing\";\n case \"active\":\n return \"active\";\n case \"error\":\n return `error:${status.message}`;\n }\n}\n\n/**\n * Parse a `sync_status` string back into a {@link SyncStatus}. Throws on\n * unrecognised forms — this matches LocalChangelog's \"corrupt value =\n * surface the bug\" stance from Task 3.4.\n */\nexport function parseSyncStatus(raw: string): SyncStatus {\n if (raw === \"idle\") return { kind: \"idle\" };\n if (raw === \"syncing\") return { kind: \"syncing\" };\n if (raw === \"active\") return { kind: \"active\" };\n if (raw.startsWith(\"error:\")) {\n return { kind: \"error\", message: raw.slice(\"error:\".length) };\n }\n throw new Error(`SyncStateStore: corrupt sync_status value: ${raw}`);\n}\n\n/**\n * Strict device_id format regex. Must match the server-side validator in\n * `services/arc-computer/src/handlers/validate-headers.ts` so locally\n * generated identifiers round-trip through the cloud sync endpoints\n * unchanged. Format: `<class>:<platform>:<suffix>`.\n *\n * Duplicated locally so this module has zero cross-package imports — the\n * source of truth lives in `validate-headers.ts` and any change there must\n * be mirrored here AND in the Swift/Kotlin equivalents.\n */\nexport const DEVICE_ID_FORMAT_RE = /^(aside|desktop|server):[a-z0-9-]+:[a-z0-9-]+$/;\n\n/** Validate a device_id string. Returns true on match. */\nexport function isValidDeviceId(deviceId: string): boolean {\n return DEVICE_ID_FORMAT_RE.test(deviceId);\n}\n\n/**\n * Generate a fresh device_id for the current TypeScript desktop runtime.\n * Format: `desktop:ts:<uuid v4>`. Tests can pass an injected UUID to\n * keep generation deterministic.\n */\nexport function generateDesktopDeviceId(uuid?: string): string {\n const id = uuid ?? crypto.randomUUID();\n return `desktop:ts:${id}`;\n}\n\nexport class SyncStateStore {\n private readonly client: import(\"@libsql/client\").Client;\n private readonly scope: string;\n\n constructor(opts: SyncStateStoreOptions) {\n if (typeof opts.scope !== \"string\" || opts.scope.length === 0) {\n throw new Error(\"SyncStateStore: scope must be a non-empty string\");\n }\n this.client = opts.client;\n this.scope = opts.scope;\n }\n\n /**\n * Idempotently create the `sync_state` table. MetadataDB.open() and the\n * Swift/Kotlin SQLiteTreeIndex.migrate() already create it; this static\n * helper exists for test fixtures that build a SyncStateStore on a raw\n * libsql client. Reuses the canonical DDL from `changelog-schema.ts` so\n * three-runtime parity is preserved.\n */\n static async ensureTable(client: import(\"@libsql/client\").Client): Promise<void> {\n await client.execute(SYNC_STATE_TABLE_SQL);\n for (const sql of SYNC_STATE_INDEX_SQL) {\n await client.execute(sql);\n }\n }\n\n // ── key helpers ───────────────────────────────────────────────\n\n /**\n * Build the on-disk key for a given AppState field. Per-scope fields\n * are prefixed with `{scope}:`; the per-user `device_id` field is the\n * raw bare name. Exposed as a private helper (rather than inlined) so\n * the parity tests can introspect the rule via SYNC_STATE_KEYS.\n */\n private keyFor(field: keyof typeof SYNC_STATE_KEYS): string {\n const meta = SYNC_STATE_KEYS[field];\n return meta.scoped ? `${this.scope}:${meta.name}` : meta.name;\n }\n\n // ── load ──────────────────────────────────────────────────────\n\n /**\n * Read the full {@link AppState} for this scope. Missing fields fall\n * back to the defaults from {@link defaultAppState}.\n *\n * If `device_id` is absent, this method generates a fresh one using\n * {@link generateDesktopDeviceId} and persists it back to the same\n * SQLite cache before returning. Subsequent loads will see the same\n * value (this is the \"TS desktop has no Keychain — file IS the SoT\"\n * branch from the spec).\n *\n * Corrupt values throw — corrupt `cloud_seq` / `local_seq` / `last_sync_at`\n * non-numeric, corrupt `sync_status` not in the recognised set, corrupt\n * `device_id` failing the strict regex.\n */\n async load(): Promise<AppState> {\n const state = defaultAppState();\n\n const result = await this.client.execute({\n sql: `SELECT key, value FROM ds_sync_state WHERE key IN (?, ?, ?, ?, ?)`,\n args: [\n this.keyFor(\"deviceId\"),\n this.keyFor(\"cloudSeq\"),\n this.keyFor(\"localSeq\"),\n this.keyFor(\"lastSyncAt\"),\n this.keyFor(\"syncStatus\"),\n ],\n });\n\n const rows = new Map<string, string>();\n for (const row of result.rows) {\n const k = row[0];\n const v = row[1];\n if (typeof k === \"string\" && (typeof v === \"string\" || typeof v === \"number\")) {\n rows.set(k, String(v));\n }\n }\n\n const rawDeviceId = rows.get(this.keyFor(\"deviceId\"));\n if (rawDeviceId !== undefined) {\n if (!isValidDeviceId(rawDeviceId)) {\n throw new Error(`SyncStateStore: corrupt device_id value: ${rawDeviceId}`);\n }\n state.deviceId = rawDeviceId;\n } else {\n // Bootstrap branch: TS desktop has no Keychain, so we generate the\n // device_id here and persist it. Mobile runtimes use their native\n // store as the SoT and only fall through to this branch if the\n // native store is empty.\n const fresh = generateDesktopDeviceId();\n await this.writeKey(this.keyFor(\"deviceId\"), fresh);\n state.deviceId = fresh;\n }\n\n state.cloudSeq = parseSeqValue(rows.get(this.keyFor(\"cloudSeq\")), \"cloud_seq\");\n state.localSeq = parseSeqValue(rows.get(this.keyFor(\"localSeq\")), \"local_seq\");\n state.lastSyncAt = parseSeqValue(rows.get(this.keyFor(\"lastSyncAt\")), \"last_sync_at\");\n\n const rawStatus = rows.get(this.keyFor(\"syncStatus\"));\n if (rawStatus !== undefined) {\n state.syncStatus = parseSyncStatus(rawStatus);\n }\n\n return state;\n }\n\n // ── save ──────────────────────────────────────────────────────\n\n /**\n * Persist a partial update to the {@link AppState}. Each field is\n * written via INSERT OR REPLACE on its individual key; concurrent\n * save() calls touching disjoint fields cannot lose data.\n *\n * Monotonic fields:\n * - `cloudSeq` and `localSeq` use the same CASE expression as\n * {@link LocalChangelog.markPushed}, so a save() with a lower value\n * than the current watermark is silently ignored. This keeps a\n * SyncEngine + LocalChangelog mixed-write path safe.\n *\n * Non-monotonic fields:\n * - `lastSyncAt`, `syncStatus`, `deviceId` are written as-is. The\n * state machine (`idle → syncing → active → idle` etc.) and the\n * `last_sync_at` clock have valid backwards transitions in some\n * edge cases (test reset, manual rollback) and we must not block\n * them.\n *\n * Unknown fields are caught at compile time by `Partial<AppState>`.\n * Defense-in-depth: at runtime we iterate the explicit list of fields\n * we know how to serialise rather than `Object.keys(update)`.\n */\n async save(update: Partial<AppState>): Promise<void> {\n if (update.deviceId !== undefined) {\n if (!isValidDeviceId(update.deviceId)) {\n throw new Error(`SyncStateStore: refusing to save invalid device_id: ${update.deviceId}`);\n }\n await this.writeKey(this.keyFor(\"deviceId\"), update.deviceId);\n }\n if (update.cloudSeq !== undefined) {\n await this.writeKeyMonotonic(this.keyFor(\"cloudSeq\"), update.cloudSeq);\n }\n if (update.localSeq !== undefined) {\n await this.writeKeyMonotonic(this.keyFor(\"localSeq\"), update.localSeq);\n }\n if (update.lastSyncAt !== undefined) {\n const safe = Math.max(\n 0,\n Math.floor(Number.isFinite(update.lastSyncAt) ? update.lastSyncAt : 0),\n );\n await this.writeKey(this.keyFor(\"lastSyncAt\"), String(safe));\n }\n if (update.syncStatus !== undefined) {\n await this.writeKey(this.keyFor(\"syncStatus\"), serializeSyncStatus(update.syncStatus));\n }\n }\n\n // ── private SQL helpers ───────────────────────────────────────\n\n /** Plain INSERT OR REPLACE for non-monotonic keys. */\n private async writeKey(key: string, value: string): Promise<void> {\n await this.client.execute({\n sql: `INSERT INTO ds_sync_state (key, value) VALUES (?, ?)\n ON CONFLICT(key) DO UPDATE SET value = excluded.value`,\n args: [key, value],\n });\n }\n\n /**\n * Monotonic INSERT OR REPLACE for integer keys. Mirror of the helper in\n * {@link LocalChangelog.writeSeqKeyMonotonic} so the two classes converge\n * on the same on-disk representation regardless of which one performs\n * the write.\n */\n private async writeKeyMonotonic(key: string, seq: number): Promise<void> {\n const safe = Math.max(0, Math.floor(Number.isFinite(seq) ? seq : 0));\n await this.client.execute({\n sql: `INSERT INTO ds_sync_state (key, value) VALUES (?, ?)\n ON CONFLICT(key) DO UPDATE SET\n value = CASE\n WHEN CAST(excluded.value AS INTEGER) > CAST(ds_sync_state.value AS INTEGER)\n THEN excluded.value\n ELSE ds_sync_state.value\n END`,\n args: [key, String(safe)],\n });\n }\n}\n\n/**\n * Parse a stored numeric value (cloud_seq / local_seq / last_sync_at).\n *\n * Missing → 0 (the \"nothing recorded yet\" sentinel, matches LocalChangelog).\n * Non-integer / non-numeric → throws to surface corruption to the caller.\n */\nfunction parseSeqValue(raw: string | undefined, label: string): number {\n if (raw === undefined) return 0;\n const parsed = Number(raw);\n if (!Number.isFinite(parsed) || !Number.isInteger(parsed)) {\n throw new Error(`SyncStateStore: corrupt ${label} value: ${raw}`);\n }\n return parsed;\n}\n"],"mappings":"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"}
@@ -0,0 +1,92 @@
1
+
2
+ //#region src/timestamp.ts
3
+ /**
4
+ * Timestamp normalization for the createdAt → ctime feed-sort fix
5
+ * (planning/did-integration/feed-sort-createdat, design §4).
6
+ *
7
+ * The `entries.ctime` / `entries.mtime` columns are INTEGER unix-ms; the
8
+ * application `createdAt` / `updatedAt` are ISO-8601 strings produced by
9
+ * `generate.timeiso` (`YYYY-MM-DDTHH:MM:SS.sssZ`, data-contract §2). These
10
+ * pure helpers bridge the two so a write/backfill can stamp `ctime` from the
11
+ * application timestamp.
12
+ *
13
+ * STRICT INVARIANTS (design §4, tri-runtime contract — TS `Date.parse`,
14
+ * Swift `ISO8601DateFormatter`, Kotlin `Instant.parse` MUST agree on the
15
+ * canonical format AND on the fallback behavior for malformed input):
16
+ * - The functions are PURE: no clock read (except the explicit `fallbackMs`
17
+ * argument), no env, no I/O, no SQL — they never throw and never log the
18
+ * input.
19
+ * - `normalizeTs` returns a finite epoch-ms `number > 0`, or `null`. It NEVER
20
+ * returns `NaN`, `0`, a negative, or a non-finite value, so a caller that
21
+ * does `normalizeTs(x) ?? fallback` can never persist `NaN`/`NULL` into a
22
+ * ctime/mtime column.
23
+ * - `resolveCtime` consumes ONLY explicit `metadata` — it does not touch
24
+ * bytes/path and does not call `readApplicationMetadata` (design §5 intro).
25
+ */
26
+ /**
27
+ * Parse an application timestamp value into epoch-ms.
28
+ *
29
+ * @param v - candidate value (typically `metadata.createdAt`, an ISO string;
30
+ * a raw epoch-ms number is also accepted for forward-compat).
31
+ * @returns finite epoch-ms `> 0`, or `null` when the input is missing, the
32
+ * wrong type, unparseable, or non-positive (`≤ 0`, which includes the 1970
33
+ * epoch and pre-epoch dates — those fall back rather than sink to ctime 0).
34
+ *
35
+ * Contract (design §4 / tasks 0-1):
36
+ * - string → `Date.parse(v)`; keep only when finite and `> 0`.
37
+ * - number → keep only when finite and `> 0` (already epoch-ms).
38
+ * - other → `null`.
39
+ */
40
+ function normalizeTs(v) {
41
+ let ms;
42
+ if (typeof v === "number") ms = v;
43
+ else if (typeof v === "string") ms = Date.parse(v);
44
+ else return null;
45
+ return Number.isFinite(ms) && ms > 0 ? ms : null;
46
+ }
47
+ /**
48
+ * Resolve the `ctime` to stamp for a write, from explicit application
49
+ * metadata, falling back to a caller-supplied epoch-ms.
50
+ *
51
+ * `= normalizeTs(metadata?.createdAt) ?? fallbackMs`. Because `fallbackMs` is
52
+ * a `number` (not `number | null`) and `normalizeTs` never returns `0`, the
53
+ * result is ALWAYS a finite `number` — callers can stamp it into a ctime
54
+ * column without a NaN/NULL guard.
55
+ *
56
+ * Only `metadata.createdAt` is read; bytes/path are never touched (design
57
+ * §5 intro). The caller is responsible for having already lifted the
58
+ * application metadata into `metadata` (cloud: `payload.meta`; local-apply:
59
+ * `SyncEngine` via `readAppMeta(bytes)`).
60
+ */
61
+ function resolveCtime(metadata, fallbackMs) {
62
+ return normalizeTs(metadata?.createdAt) ?? fallbackMs;
63
+ }
64
+ /**
65
+ * Resolve the direct-parent DIR row stamp for Rule R2 (design §3 / §5.2).
66
+ *
67
+ * @returns `{ ctime, mtime }` to UPSERT onto the written entry's DIRECT parent
68
+ * directory row, or `null` when the metadata carries no usable `createdAt`
69
+ * (the caller then keeps `INSERT OR IGNORE` — the dir row is NOT disturbed).
70
+ *
71
+ * - `ctime = normalizeTs(createdAt)` (the gate: null `createdAt` → whole stamp
72
+ * is `null`, so a write without an application createdAt never overwrites a
73
+ * previously stamped DIR ctime).
74
+ * - `mtime = normalizeTs(updatedAt) ?? ctime` (updatedAt falls back to
75
+ * createdAt). DIR rows never enter the changelog (design §1 fact 3) so this
76
+ * mtime carries NO LWW meaning — unlike file-row mtime, which is the LWW
77
+ * arbiter and is NEVER touched by Rule R.
78
+ *
79
+ * Only the direct parent is stamped; ancestors are never propagated to.
80
+ */
81
+ function resolveParentDirStamp(metadata) {
82
+ const ctime = normalizeTs(metadata?.createdAt);
83
+ if (ctime === null) return null;
84
+ return {
85
+ ctime,
86
+ mtime: normalizeTs(metadata?.updatedAt) ?? ctime
87
+ };
88
+ }
89
+
90
+ //#endregion
91
+ exports.resolveCtime = resolveCtime;
92
+ exports.resolveParentDirStamp = resolveParentDirStamp;
@@ -0,0 +1,91 @@
1
+ //#region src/timestamp.ts
2
+ /**
3
+ * Timestamp normalization for the createdAt → ctime feed-sort fix
4
+ * (planning/did-integration/feed-sort-createdat, design §4).
5
+ *
6
+ * The `entries.ctime` / `entries.mtime` columns are INTEGER unix-ms; the
7
+ * application `createdAt` / `updatedAt` are ISO-8601 strings produced by
8
+ * `generate.timeiso` (`YYYY-MM-DDTHH:MM:SS.sssZ`, data-contract §2). These
9
+ * pure helpers bridge the two so a write/backfill can stamp `ctime` from the
10
+ * application timestamp.
11
+ *
12
+ * STRICT INVARIANTS (design §4, tri-runtime contract — TS `Date.parse`,
13
+ * Swift `ISO8601DateFormatter`, Kotlin `Instant.parse` MUST agree on the
14
+ * canonical format AND on the fallback behavior for malformed input):
15
+ * - The functions are PURE: no clock read (except the explicit `fallbackMs`
16
+ * argument), no env, no I/O, no SQL — they never throw and never log the
17
+ * input.
18
+ * - `normalizeTs` returns a finite epoch-ms `number > 0`, or `null`. It NEVER
19
+ * returns `NaN`, `0`, a negative, or a non-finite value, so a caller that
20
+ * does `normalizeTs(x) ?? fallback` can never persist `NaN`/`NULL` into a
21
+ * ctime/mtime column.
22
+ * - `resolveCtime` consumes ONLY explicit `metadata` — it does not touch
23
+ * bytes/path and does not call `readApplicationMetadata` (design §5 intro).
24
+ */
25
+ /**
26
+ * Parse an application timestamp value into epoch-ms.
27
+ *
28
+ * @param v - candidate value (typically `metadata.createdAt`, an ISO string;
29
+ * a raw epoch-ms number is also accepted for forward-compat).
30
+ * @returns finite epoch-ms `> 0`, or `null` when the input is missing, the
31
+ * wrong type, unparseable, or non-positive (`≤ 0`, which includes the 1970
32
+ * epoch and pre-epoch dates — those fall back rather than sink to ctime 0).
33
+ *
34
+ * Contract (design §4 / tasks 0-1):
35
+ * - string → `Date.parse(v)`; keep only when finite and `> 0`.
36
+ * - number → keep only when finite and `> 0` (already epoch-ms).
37
+ * - other → `null`.
38
+ */
39
+ function normalizeTs(v) {
40
+ let ms;
41
+ if (typeof v === "number") ms = v;
42
+ else if (typeof v === "string") ms = Date.parse(v);
43
+ else return null;
44
+ return Number.isFinite(ms) && ms > 0 ? ms : null;
45
+ }
46
+ /**
47
+ * Resolve the `ctime` to stamp for a write, from explicit application
48
+ * metadata, falling back to a caller-supplied epoch-ms.
49
+ *
50
+ * `= normalizeTs(metadata?.createdAt) ?? fallbackMs`. Because `fallbackMs` is
51
+ * a `number` (not `number | null`) and `normalizeTs` never returns `0`, the
52
+ * result is ALWAYS a finite `number` — callers can stamp it into a ctime
53
+ * column without a NaN/NULL guard.
54
+ *
55
+ * Only `metadata.createdAt` is read; bytes/path are never touched (design
56
+ * §5 intro). The caller is responsible for having already lifted the
57
+ * application metadata into `metadata` (cloud: `payload.meta`; local-apply:
58
+ * `SyncEngine` via `readAppMeta(bytes)`).
59
+ */
60
+ function resolveCtime(metadata, fallbackMs) {
61
+ return normalizeTs(metadata?.createdAt) ?? fallbackMs;
62
+ }
63
+ /**
64
+ * Resolve the direct-parent DIR row stamp for Rule R2 (design §3 / §5.2).
65
+ *
66
+ * @returns `{ ctime, mtime }` to UPSERT onto the written entry's DIRECT parent
67
+ * directory row, or `null` when the metadata carries no usable `createdAt`
68
+ * (the caller then keeps `INSERT OR IGNORE` — the dir row is NOT disturbed).
69
+ *
70
+ * - `ctime = normalizeTs(createdAt)` (the gate: null `createdAt` → whole stamp
71
+ * is `null`, so a write without an application createdAt never overwrites a
72
+ * previously stamped DIR ctime).
73
+ * - `mtime = normalizeTs(updatedAt) ?? ctime` (updatedAt falls back to
74
+ * createdAt). DIR rows never enter the changelog (design §1 fact 3) so this
75
+ * mtime carries NO LWW meaning — unlike file-row mtime, which is the LWW
76
+ * arbiter and is NEVER touched by Rule R.
77
+ *
78
+ * Only the direct parent is stamped; ancestors are never propagated to.
79
+ */
80
+ function resolveParentDirStamp(metadata) {
81
+ const ctime = normalizeTs(metadata?.createdAt);
82
+ if (ctime === null) return null;
83
+ return {
84
+ ctime,
85
+ mtime: normalizeTs(metadata?.updatedAt) ?? ctime
86
+ };
87
+ }
88
+
89
+ //#endregion
90
+ export { resolveCtime, resolveParentDirStamp };
91
+ //# sourceMappingURL=timestamp.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timestamp.mjs","names":[],"sources":["../src/timestamp.ts"],"sourcesContent":["/**\n * Timestamp normalization for the createdAt → ctime feed-sort fix\n * (planning/did-integration/feed-sort-createdat, design §4).\n *\n * The `entries.ctime` / `entries.mtime` columns are INTEGER unix-ms; the\n * application `createdAt` / `updatedAt` are ISO-8601 strings produced by\n * `generate.timeiso` (`YYYY-MM-DDTHH:MM:SS.sssZ`, data-contract §2). These\n * pure helpers bridge the two so a write/backfill can stamp `ctime` from the\n * application timestamp.\n *\n * STRICT INVARIANTS (design §4, tri-runtime contract — TS `Date.parse`,\n * Swift `ISO8601DateFormatter`, Kotlin `Instant.parse` MUST agree on the\n * canonical format AND on the fallback behavior for malformed input):\n * - The functions are PURE: no clock read (except the explicit `fallbackMs`\n * argument), no env, no I/O, no SQL — they never throw and never log the\n * input.\n * - `normalizeTs` returns a finite epoch-ms `number > 0`, or `null`. It NEVER\n * returns `NaN`, `0`, a negative, or a non-finite value, so a caller that\n * does `normalizeTs(x) ?? fallback` can never persist `NaN`/`NULL` into a\n * ctime/mtime column.\n * - `resolveCtime` consumes ONLY explicit `metadata` — it does not touch\n * bytes/path and does not call `readApplicationMetadata` (design §5 intro).\n */\n\n/**\n * Parse an application timestamp value into epoch-ms.\n *\n * @param v - candidate value (typically `metadata.createdAt`, an ISO string;\n * a raw epoch-ms number is also accepted for forward-compat).\n * @returns finite epoch-ms `> 0`, or `null` when the input is missing, the\n * wrong type, unparseable, or non-positive (`≤ 0`, which includes the 1970\n * epoch and pre-epoch dates — those fall back rather than sink to ctime 0).\n *\n * Contract (design §4 / tasks 0-1):\n * - string → `Date.parse(v)`; keep only when finite and `> 0`.\n * - number → keep only when finite and `> 0` (already epoch-ms).\n * - other → `null`.\n */\nexport function normalizeTs(v: unknown): number | null {\n let ms: number;\n if (typeof v === \"number\") {\n ms = v;\n } else if (typeof v === \"string\") {\n // Date.parse returns NaN for unparseable input (empty string, garbage,\n // injection payloads) — Number.isFinite then rejects it below.\n ms = Date.parse(v);\n } else {\n return null;\n }\n return Number.isFinite(ms) && ms > 0 ? ms : null;\n}\n\n/**\n * Resolve the `ctime` to stamp for a write, from explicit application\n * metadata, falling back to a caller-supplied epoch-ms.\n *\n * `= normalizeTs(metadata?.createdAt) ?? fallbackMs`. Because `fallbackMs` is\n * a `number` (not `number | null`) and `normalizeTs` never returns `0`, the\n * result is ALWAYS a finite `number` — callers can stamp it into a ctime\n * column without a NaN/NULL guard.\n *\n * Only `metadata.createdAt` is read; bytes/path are never touched (design\n * §5 intro). The caller is responsible for having already lifted the\n * application metadata into `metadata` (cloud: `payload.meta`; local-apply:\n * `SyncEngine` via `readAppMeta(bytes)`).\n */\nexport function resolveCtime(\n metadata: Record<string, unknown> | null | undefined,\n fallbackMs: number,\n): number {\n return normalizeTs(metadata?.createdAt) ?? fallbackMs;\n}\n\n/**\n * Resolve the direct-parent DIR row stamp for Rule R2 (design §3 / §5.2).\n *\n * @returns `{ ctime, mtime }` to UPSERT onto the written entry's DIRECT parent\n * directory row, or `null` when the metadata carries no usable `createdAt`\n * (the caller then keeps `INSERT OR IGNORE` — the dir row is NOT disturbed).\n *\n * - `ctime = normalizeTs(createdAt)` (the gate: null `createdAt` → whole stamp\n * is `null`, so a write without an application createdAt never overwrites a\n * previously stamped DIR ctime).\n * - `mtime = normalizeTs(updatedAt) ?? ctime` (updatedAt falls back to\n * createdAt). DIR rows never enter the changelog (design §1 fact 3) so this\n * mtime carries NO LWW meaning — unlike file-row mtime, which is the LWW\n * arbiter and is NEVER touched by Rule R.\n *\n * Only the direct parent is stamped; ancestors are never propagated to.\n */\nexport function resolveParentDirStamp(\n metadata: Record<string, unknown> | null | undefined,\n): { ctime: number; mtime: number } | null {\n const ctime = normalizeTs(metadata?.createdAt);\n if (ctime === null) return null;\n const mtime = normalizeTs(metadata?.updatedAt) ?? ctime;\n return { ctime, mtime };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCA,SAAgB,YAAY,GAA2B;CACrD,IAAI;AACJ,KAAI,OAAO,MAAM,SACf,MAAK;UACI,OAAO,MAAM,SAGtB,MAAK,KAAK,MAAM,EAAE;KAElB,QAAO;AAET,QAAO,OAAO,SAAS,GAAG,IAAI,KAAK,IAAI,KAAK;;;;;;;;;;;;;;;;AAiB9C,SAAgB,aACd,UACA,YACQ;AACR,QAAO,YAAY,UAAU,UAAU,IAAI;;;;;;;;;;;;;;;;;;;AAoB7C,SAAgB,sBACd,UACyC;CACzC,MAAM,QAAQ,YAAY,UAAU,UAAU;AAC9C,KAAI,UAAU,KAAM,QAAO;AAE3B,QAAO;EAAE;EAAO,OADF,YAAY,UAAU,UAAU,IAAI;EAC3B"}
@@ -0,0 +1,54 @@
1
+ const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs');
2
+ const require_remote_afs = require('../remote-afs.cjs');
3
+ let _aigne_afs = require("@aigne/afs");
4
+
5
+ //#region src/transports/http.ts
6
+ const ALLOWED_SCHEMES = new Set(["http:", "https:"]);
7
+ function assertUrlScheme(rawUrl) {
8
+ let parsed;
9
+ try {
10
+ parsed = new URL(rawUrl);
11
+ } catch {
12
+ throw new Error(`HttpTransport: invalid URL: ${rawUrl}`);
13
+ }
14
+ if (!ALLOWED_SCHEMES.has(parsed.protocol)) throw new Error(`HttpTransport: only http(s) is supported, got ${parsed.protocol} (URL: ${rawUrl})`);
15
+ return parsed;
16
+ }
17
+ var HttpTransport = class {
18
+ kind = "http";
19
+ remote;
20
+ constructor(options) {
21
+ assertUrlScheme(options.url);
22
+ this.remote = new require_remote_afs.RemoteUserSpaceAFS({
23
+ baseUrl: options.url,
24
+ token: options.token
25
+ });
26
+ }
27
+ list(path, options) {
28
+ return this.remote.list(path, options);
29
+ }
30
+ read(path, _options) {
31
+ return this.remote.read(path);
32
+ }
33
+ write(path, payload, _options) {
34
+ return this.remote.write(path, payload);
35
+ }
36
+ delete(path, options) {
37
+ return this.remote.delete(path, options);
38
+ }
39
+ async stat(_path, _options) {
40
+ throw new _aigne_afs.AFSUnsupportedError("stat", "HttpTransport does not expose stat (REST has no /op=stat); use the WS transport for stat.");
41
+ }
42
+ async search(_path, _query, _options) {
43
+ throw new _aigne_afs.AFSUnsupportedError("search", "HttpTransport does not expose search (REST has no /op=search); use the WS transport for search.");
44
+ }
45
+ async exec(_path, _args, _options) {
46
+ throw new _aigne_afs.AFSUnsupportedError("exec", "HttpTransport does not expose exec; use the WS transport for action execution.");
47
+ }
48
+ async explain(_path, _options) {
49
+ throw new _aigne_afs.AFSUnsupportedError("explain", "HttpTransport does not expose explain; use the WS transport for explain.");
50
+ }
51
+ };
52
+
53
+ //#endregion
54
+ exports.HttpTransport = HttpTransport;
@@ -0,0 +1,26 @@
1
+ import { Transport } from "./types.cjs";
2
+ import { AFSDeleteOptions, AFSDeleteResult, AFSExecOptions, AFSExecResult, AFSExplainOptions, AFSExplainResult, AFSListOptions, AFSListResult, AFSReadOptions, AFSReadResult, AFSSearchOptions, AFSSearchResult, AFSStatOptions, AFSStatResult, AFSWriteEntryPayload, AFSWriteOptions, AFSWriteResult } from "@aigne/afs";
3
+
4
+ //#region src/transports/http.d.ts
5
+ interface HttpTransportOptions {
6
+ /** Base URL of the DID Space REST server, e.g. `https://computer.afsd.dev`. */
7
+ url: string;
8
+ /** Bearer token for authentication. */
9
+ token: string;
10
+ }
11
+ declare class HttpTransport implements Transport {
12
+ readonly kind: "http";
13
+ private readonly remote;
14
+ constructor(options: HttpTransportOptions);
15
+ list(path: string, options?: AFSListOptions): Promise<AFSListResult>;
16
+ read(path: string, _options?: AFSReadOptions): Promise<AFSReadResult>;
17
+ write(path: string, payload: AFSWriteEntryPayload, _options?: AFSWriteOptions): Promise<AFSWriteResult>;
18
+ delete(path: string, options?: AFSDeleteOptions): Promise<AFSDeleteResult>;
19
+ stat(_path: string, _options?: AFSStatOptions): Promise<AFSStatResult>;
20
+ search(_path: string, _query: string, _options?: AFSSearchOptions): Promise<AFSSearchResult>;
21
+ exec(_path: string, _args: Record<string, unknown>, _options?: AFSExecOptions): Promise<AFSExecResult>;
22
+ explain(_path: string, _options?: AFSExplainOptions): Promise<AFSExplainResult>;
23
+ }
24
+ //#endregion
25
+ export { HttpTransport, HttpTransportOptions };
26
+ //# sourceMappingURL=http.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.d.cts","names":[],"sources":["../../src/transports/http.ts"],"mappings":";;;;UA4CiB,oBAAA;EAmDgB;EAjD/B,GAAA;EAiDkD;EA/ClD,KAAA;AAAA;AAAA,cAoBW,aAAA,YAAyB,SAAA;EAAA,SAC3B,IAAA;EAAA,iBACQ,MAAA;cAEL,OAAA,EAAS,oBAAA;EAOrB,IAAA,CAAK,IAAA,UAAc,OAAA,GAAU,cAAA,GAAiB,OAAA,CAAQ,aAAA;EAItD,IAAA,CAAK,IAAA,UAAc,QAAA,GAAW,cAAA,GAAiB,OAAA,CAAQ,aAAA;EAIvD,KAAA,CACE,IAAA,UACA,OAAA,EAAS,oBAAA,EACT,QAAA,GAAW,eAAA,GACV,OAAA,CAAQ,cAAA;EAIX,MAAA,CAAO,IAAA,UAAc,OAAA,GAAU,gBAAA,GAAmB,OAAA,CAAQ,eAAA;EAIpD,IAAA,CAAK,KAAA,UAAe,QAAA,GAAW,cAAA,GAAiB,OAAA,CAAQ,aAAA;EAOxD,MAAA,CACJ,KAAA,UACA,MAAA,UACA,QAAA,GAAW,gBAAA,GACV,OAAA,CAAQ,eAAA;EAOL,IAAA,CACJ,KAAA,UACA,KAAA,EAAO,MAAA,mBACP,QAAA,GAAW,cAAA,GACV,OAAA,CAAQ,aAAA;EAOL,OAAA,CAAQ,KAAA,UAAe,QAAA,GAAW,iBAAA,GAAoB,OAAA,CAAQ,gBAAA;AAAA"}
@@ -0,0 +1,26 @@
1
+ import { Transport } from "./types.mjs";
2
+ import { AFSDeleteOptions, AFSDeleteResult, AFSExecOptions, AFSExecResult, AFSExplainOptions, AFSExplainResult, AFSListOptions, AFSListResult, AFSReadOptions, AFSReadResult, AFSSearchOptions, AFSSearchResult, AFSStatOptions, AFSStatResult, AFSWriteEntryPayload, AFSWriteOptions, AFSWriteResult } from "@aigne/afs";
3
+
4
+ //#region src/transports/http.d.ts
5
+ interface HttpTransportOptions {
6
+ /** Base URL of the DID Space REST server, e.g. `https://computer.afsd.dev`. */
7
+ url: string;
8
+ /** Bearer token for authentication. */
9
+ token: string;
10
+ }
11
+ declare class HttpTransport implements Transport {
12
+ readonly kind: "http";
13
+ private readonly remote;
14
+ constructor(options: HttpTransportOptions);
15
+ list(path: string, options?: AFSListOptions): Promise<AFSListResult>;
16
+ read(path: string, _options?: AFSReadOptions): Promise<AFSReadResult>;
17
+ write(path: string, payload: AFSWriteEntryPayload, _options?: AFSWriteOptions): Promise<AFSWriteResult>;
18
+ delete(path: string, options?: AFSDeleteOptions): Promise<AFSDeleteResult>;
19
+ stat(_path: string, _options?: AFSStatOptions): Promise<AFSStatResult>;
20
+ search(_path: string, _query: string, _options?: AFSSearchOptions): Promise<AFSSearchResult>;
21
+ exec(_path: string, _args: Record<string, unknown>, _options?: AFSExecOptions): Promise<AFSExecResult>;
22
+ explain(_path: string, _options?: AFSExplainOptions): Promise<AFSExplainResult>;
23
+ }
24
+ //#endregion
25
+ export { HttpTransport, HttpTransportOptions };
26
+ //# sourceMappingURL=http.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.d.mts","names":[],"sources":["../../src/transports/http.ts"],"mappings":";;;;UA4CiB,oBAAA;EAmDgB;EAjD/B,GAAA;EAiDkD;EA/ClD,KAAA;AAAA;AAAA,cAoBW,aAAA,YAAyB,SAAA;EAAA,SAC3B,IAAA;EAAA,iBACQ,MAAA;cAEL,OAAA,EAAS,oBAAA;EAOrB,IAAA,CAAK,IAAA,UAAc,OAAA,GAAU,cAAA,GAAiB,OAAA,CAAQ,aAAA;EAItD,IAAA,CAAK,IAAA,UAAc,QAAA,GAAW,cAAA,GAAiB,OAAA,CAAQ,aAAA;EAIvD,KAAA,CACE,IAAA,UACA,OAAA,EAAS,oBAAA,EACT,QAAA,GAAW,eAAA,GACV,OAAA,CAAQ,cAAA;EAIX,MAAA,CAAO,IAAA,UAAc,OAAA,GAAU,gBAAA,GAAmB,OAAA,CAAQ,eAAA;EAIpD,IAAA,CAAK,KAAA,UAAe,QAAA,GAAW,cAAA,GAAiB,OAAA,CAAQ,aAAA;EAOxD,MAAA,CACJ,KAAA,UACA,MAAA,UACA,QAAA,GAAW,gBAAA,GACV,OAAA,CAAQ,eAAA;EAOL,IAAA,CACJ,KAAA,UACA,KAAA,EAAO,MAAA,mBACP,QAAA,GAAW,cAAA,GACV,OAAA,CAAQ,aAAA;EAOL,OAAA,CAAQ,KAAA,UAAe,QAAA,GAAW,iBAAA,GAAoB,OAAA,CAAQ,gBAAA;AAAA"}