A system for building static webapps
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: add sync server

+338 -13
+4 -1
deno.json
··· 1 1 { 2 2 "workspace": [ 3 + "./apps/sync", 3 4 "./packages/cli", 4 5 "./packages/store", 5 6 "./packages/sync", ··· 9 10 "compilerOptions": { 10 11 "lib": [ 11 12 "deno.ns", 13 + "deno.unstable", 12 14 "dom", 13 15 "dom.iterable", 14 16 "dom.asynciterable", ··· 37 39 }, 38 40 "tasks": { 39 41 "cli": "deno install packages/cli/main.ts -Afg --name=civ --config ./deno.json", 40 - "test": "deno fmt && deno lint && deno test -A --coverage" 42 + "test": "deno fmt && deno lint && deno test packages/ -A --coverage --quiet" 41 43 }, 42 44 "imports": { 43 45 "@astral/astral": "jsr:@astral/astral@^0.5.5", 44 46 "@b-fuze/deno-dom": "jsr:@b-fuze/deno-dom@^0.1.56", 45 47 "@std/assert": "jsr:@std/assert@^1.0.19", 48 + "@std/dotenv": "jsr:@std/dotenv@^0.225.6", 46 49 "@std/testing": "jsr:@std/testing@^1.0.17" 47 50 } 48 51 }
+188 -10
deno.lock
··· 12 12 "jsr:@deno/cache-dir@0.22.2": "0.22.2", 13 13 "jsr:@deno/graph@0.86": "0.86.9", 14 14 "jsr:@denosaurs/plug@1.1.0": "1.1.0", 15 + "jsr:@hono/hono@^4.12.5": "4.12.5", 16 + "jsr:@inro/simple-tools@0.5.2": "0.5.2", 15 17 "jsr:@luca/esbuild-deno-loader@~0.11.1": "0.11.1", 16 18 "jsr:@paulmillr/qr@~0.5.5": "0.5.5", 17 19 "jsr:@rodney/parsedown@^1.4.3": "1.4.3", 18 - "jsr:@std/assert@1": "1.0.19", 19 20 "jsr:@std/assert@^1.0.17": "1.0.19", 20 21 "jsr:@std/assert@^1.0.19": "1.0.19", 21 22 "jsr:@std/async@1": "1.2.0", ··· 23 24 "jsr:@std/bytes@^1.0.2": "1.0.6", 24 25 "jsr:@std/bytes@^1.0.6": "1.0.6", 25 26 "jsr:@std/cli@^1.0.28": "1.0.28", 27 + "jsr:@std/collections@^1.1.0": "1.1.6", 26 28 "jsr:@std/collections@^1.1.3": "1.1.6", 29 + "jsr:@std/crypto@^1.0.5": "1.0.5", 27 30 "jsr:@std/data-structures@^1.0.10": "1.0.10", 31 + "jsr:@std/dotenv@~0.225.6": "0.225.6", 32 + "jsr:@std/encoding@0.224.0": "0.224.0", 28 33 "jsr:@std/encoding@1": "1.0.10", 29 34 "jsr:@std/encoding@^1.0.10": "1.0.10", 30 35 "jsr:@std/encoding@^1.0.5": "1.0.10", ··· 34 39 "jsr:@std/fmt@^1.0.9": "1.0.9", 35 40 "jsr:@std/front-matter@^1.0.9": "1.0.9", 36 41 "jsr:@std/fs@1": "1.0.23", 42 + "jsr:@std/fs@^1.0.17": "1.0.23", 37 43 "jsr:@std/fs@^1.0.22": "1.0.23", 38 44 "jsr:@std/fs@^1.0.23": "1.0.23", 39 45 "jsr:@std/fs@^1.0.6": "1.0.23", ··· 54 60 "jsr:@std/testing@^1.0.17": "1.0.17", 55 61 "jsr:@std/text@^1.0.17": "1.0.17", 56 62 "jsr:@std/toml@^1.0.3": "1.0.11", 63 + "jsr:@std/uuid@^1.1.0": "1.1.0", 57 64 "jsr:@std/yaml@^1.0.5": "1.0.12", 65 + "jsr:@zaubrik/djwt@^3.0.2": "3.0.2", 58 66 "jsr:@zip-js/zip-js@^2.7.52": "2.8.21", 67 + "npm:@hono/zod-openapi@^1.2.2": "1.2.2_hono@4.12.5_zod@4.3.6", 68 + "npm:@oslojs/crypto@^1.0.1": "1.0.1", 69 + "npm:@oslojs/encoding@^1.1.0": "1.1.0", 70 + "npm:@scalar/hono-api-reference@0.10": "0.10.0_hono@4.12.5", 71 + "npm:@tauri-apps/plugin-store@^2.2.0": "2.4.2", 59 72 "npm:autoprefixer@^10.4.27": "10.4.27_postcss@8.5.6", 60 73 "npm:cheerio@^1.2.0": "1.2.0", 61 74 "npm:create-vite@*": "7.1.1", ··· 63 76 "npm:lit@^3.3.2": "3.3.2", 64 77 "npm:native-file-system-adapter@^3.0.1": "3.0.1", 65 78 "npm:postcss-import@^16.1.1": "16.1.1_postcss@8.5.6", 66 - "npm:postcss@^8.5.6": "8.5.6" 79 + "npm:postcss@^8.5.6": "8.5.6", 80 + "npm:ts-fsrs@5": "5.2.3", 81 + "npm:zod@^4.3.6": "4.3.6" 67 82 }, 68 83 "jsr": { 69 84 "@astral/astral@0.5.5": { ··· 149 164 "jsr:@std/path@1" 150 165 ] 151 166 }, 167 + "@hono/hono@4.12.5": { 168 + "integrity": "16e6ac6d6ab65c3d08fa4c019c3d9236389044074f3932bf0922fcb582e530c6" 169 + }, 170 + "@inro/simple-tools@0.5.2": { 171 + "integrity": "cc34cd0914b9e0576d9bed9a66a91994123b73f3fd87a4e8db76880181731ee5", 172 + "dependencies": [ 173 + "jsr:@std/collections@^1.1.0", 174 + "jsr:@std/fs@^1.0.17", 175 + "npm:@tauri-apps/plugin-store", 176 + "npm:ts-fsrs" 177 + ] 178 + }, 152 179 "@luca/esbuild-deno-loader@0.11.1": { 153 180 "integrity": "dc020d16d75b591f679f6b9288b10f38bdb4f24345edb2f5732affa1d9885267", 154 181 "dependencies": [ ··· 184 211 "@std/collections@1.1.6": { 185 212 "integrity": "b458160ce65ea5ad35da05d0a5cbee4b583677c8b443a10d7beb0c4ac63f2baa" 186 213 }, 214 + "@std/crypto@1.0.5": { 215 + "integrity": "0dcfbb319fe0bba1bd3af904ceb4f948cde1b92979ec1614528380ed308a3b40" 216 + }, 187 217 "@std/data-structures@1.0.10": { 188 218 "integrity": "f574f86b0e07c69b9edc555fcc814b57d29258bad39fd5a34ba8a80ecf033cfe" 189 219 }, 220 + "@std/dotenv@0.225.6": { 221 + "integrity": "1d6f9db72f565bd26790fa034c26e45ecb260b5245417be76c2279e5734c421b" 222 + }, 223 + "@std/encoding@0.224.0": { 224 + "integrity": "efb6dca97d3e9c31392bd5c8cfd9f9fc9decf5a1f4d1f78af7900a493bcf89b5" 225 + }, 190 226 "@std/encoding@1.0.10": { 191 227 "integrity": "8783c6384a2d13abd5e9e87a7ae0520a30e9f56aeeaa3bdf910a3eaaf5c811a1" 192 228 }, ··· 234 270 "integrity": "972a634fd5bc34b242024402972cd5143eac68d8dffaca5eaa4dba30ce17b027" 235 271 }, 236 272 "@std/io@0.225.0": { 237 - "integrity": "c1db7c5e5a231629b32d64b9a53139445b2ca640d828c26bf23e1c55f8c079b3" 273 + "integrity": "c1db7c5e5a231629b32d64b9a53139445b2ca640d828c26bf23e1c55f8c079b3", 274 + "dependencies": [ 275 + "jsr:@std/bytes@^1.0.2" 276 + ] 238 277 }, 239 278 "@std/io@0.225.3": { 240 279 "integrity": "27b07b591384d12d7b568f39e61dff966b8230559122df1e9fd11cc068f7ddd1", ··· 274 313 "@std/text@1.0.17": { 275 314 "integrity": "4b2c4ef67ae5b6c1dfd447c81c83a43718f52e3c7e748d8b33f694aba9895f95" 276 315 }, 277 - "@std/toml@1.0.10": { 278 - "integrity": "87b2b7ff95afe7209a868732eb013a2707be29a15229f5b57bb13eededff4655", 316 + "@std/toml@1.0.11": { 317 + "integrity": "e084988b872ca4bad6aedfb7350f6eeed0e8ba88e9ee5e1590621c5b5bb8f715", 279 318 "dependencies": [ 280 - "jsr:@std/collections" 319 + "jsr:@std/collections@^1.1.3" 281 320 ] 282 321 }, 283 - "@std/toml@1.0.11": { 284 - "integrity": "e084988b872ca4bad6aedfb7350f6eeed0e8ba88e9ee5e1590621c5b5bb8f715", 322 + "@std/uuid@1.1.0": { 323 + "integrity": "6268db2ccf172849c9be80763354ca305d49ef4af41fe995623d44fcc3f7457c", 285 324 "dependencies": [ 286 - "jsr:@std/collections" 325 + "jsr:@std/bytes@^1.0.6", 326 + "jsr:@std/crypto" 287 327 ] 288 328 }, 289 329 "@std/yaml@1.0.9": { ··· 295 335 "@std/yaml@1.0.12": { 296 336 "integrity": "7deabca4545bcedd07c5f69ea53acea71b8b4c67562f224e17b90d75944cb20c" 297 337 }, 338 + "@zaubrik/djwt@3.0.2": { 339 + "integrity": "8070adaa49cd9e5d2b8ae82fd461132c966ef2f8ff8378db4a4da8df4f17c664", 340 + "dependencies": [ 341 + "jsr:@std/encoding@0.224.0" 342 + ] 343 + }, 298 344 "@zip-js/zip-js@2.8.21": { 299 345 "integrity": "0787af769e7ed64f0728beba0dd7b52a57d26f202c08c54f0200a48d1c38845d" 300 346 } 301 347 }, 302 348 "npm": { 349 + "@asteasolutions/zod-to-openapi@8.4.3_zod@4.3.6": { 350 + "integrity": "sha512-lwfMTN7kDbFDwMniYZUebiGGHxVGBw9ZSI4IBYjm6Ey22Kd5z/fsQb2k+Okr8WMbCCC553vi/ZM9utl5/XcvuQ==", 351 + "dependencies": [ 352 + "openapi3-ts", 353 + "zod" 354 + ] 355 + }, 303 356 "@esbuild/aix-ppc64@0.27.3": { 304 357 "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", 305 358 "os": ["aix"], ··· 430 483 "os": ["win32"], 431 484 "cpu": ["x64"] 432 485 }, 486 + "@hono/zod-openapi@1.2.2_hono@4.12.5_zod@4.3.6": { 487 + "integrity": "sha512-va6vsL23wCJ1d0Vd+vGL1XOt+wPwItxirYafuhlW9iC2MstYr2FvsI7mctb45eBTjZfkqB/3LYDJEppPjOEiHw==", 488 + "dependencies": [ 489 + "@asteasolutions/zod-to-openapi", 490 + "@hono/zod-validator", 491 + "hono", 492 + "openapi3-ts", 493 + "zod" 494 + ] 495 + }, 496 + "@hono/zod-validator@0.7.6_hono@4.12.5_zod@4.3.6": { 497 + "integrity": "sha512-Io1B6d011Gj1KknV4rXYz4le5+5EubcWEU/speUjuw9XMMIaP3n78yXLhjd2A3PXaXaUwEAluOiAyLqhBEJgsw==", 498 + "dependencies": [ 499 + "hono", 500 + "zod" 501 + ] 502 + }, 433 503 "@lit-labs/ssr-dom-shim@1.5.1": { 434 504 "integrity": "sha512-Aou5UdlSpr5whQe8AA/bZG0jMj96CoJIWbGfZ91qieWu5AWUMKw8VR/pAkQkJYvBNhmCcWnZlyyk5oze8JIqYA==" 435 505 }, ··· 439 509 "@lit-labs/ssr-dom-shim" 440 510 ] 441 511 }, 512 + "@oslojs/asn1@1.0.0": { 513 + "integrity": "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA==", 514 + "dependencies": [ 515 + "@oslojs/binary" 516 + ] 517 + }, 518 + "@oslojs/binary@1.0.0": { 519 + "integrity": "sha512-9RCU6OwXU6p67H4NODbuxv2S3eenuQ4/WFLrsq+K/k682xrznH5EVWA7N4VFk9VYVcbFtKqur5YQQZc0ySGhsQ==" 520 + }, 521 + "@oslojs/crypto@1.0.1": { 522 + "integrity": "sha512-7n08G8nWjAr/Yu3vu9zzrd0L9XnrJfpMioQcvCMxBIiF5orECHe5/3J0jmXRVvgfqMm/+4oxlQ+Sq39COYLcNQ==", 523 + "dependencies": [ 524 + "@oslojs/asn1", 525 + "@oslojs/binary" 526 + ] 527 + }, 528 + "@oslojs/encoding@1.1.0": { 529 + "integrity": "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==" 530 + }, 531 + "@scalar/core@0.4.0": { 532 + "integrity": "sha512-Zcl+V8oBxb0S7vR+Nro8J53GD/w/kSjuyX0UoT3r1sn0bUM3Buf4Ob44n3CSfluQzxmiMuZxfOROHqa9F+ckbg==", 533 + "dependencies": [ 534 + "@scalar/types" 535 + ] 536 + }, 537 + "@scalar/helpers@0.3.0": { 538 + "integrity": "sha512-lhQdehgighJC+PiSTJbbggM/SM3UydcRQil6Cfp/M4l539qklIh35pt4eh1+H+5Esa03gHnJwhTHF3TwglSOJw==" 539 + }, 540 + "@scalar/hono-api-reference@0.10.0_hono@4.12.5": { 541 + "integrity": "sha512-5QGNilMAnLRzSufMhh0Ni8DepWzL2UOJm+RQI+e/slrhhfhO+pSV2bcLE8dhBz6k+V70El6Pvl0m2cPaDvP4sw==", 542 + "dependencies": [ 543 + "@scalar/core", 544 + "hono" 545 + ] 546 + }, 547 + "@scalar/types@0.7.0": { 548 + "integrity": "sha512-IkG62M4ztmqkYNVhLpcswBojlQctbXLdkDa3UFsY8FfT7yfZ2LppjptycW9tWjD09ZQb4QAZ070FAUHmFRIS7w==", 549 + "dependencies": [ 550 + "@scalar/helpers", 551 + "nanoid@5.1.6", 552 + "type-fest", 553 + "zod" 554 + ] 555 + }, 556 + "@tauri-apps/api@2.10.1": { 557 + "integrity": "sha512-hKL/jWf293UDSUN09rR69hrToyIXBb8CjGaWC7gfinvnQrBVvnLr08FeFi38gxtugAVyVcTa5/FD/Xnkb1siBw==" 558 + }, 559 + "@tauri-apps/plugin-store@2.4.2": { 560 + "integrity": "sha512-0ClHS50Oq9HEvLPhNzTNFxbWVOqoAp3dRvtewQBeqfIQ0z5m3JRnOISIn2ZVPCrQC0MyGyhTS9DWhHjpigQE7A==", 561 + "dependencies": [ 562 + "@tauri-apps/api" 563 + ] 564 + }, 442 565 "@types/trusted-types@2.0.7": { 443 566 "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==" 444 567 }, ··· 618 741 "function-bind" 619 742 ] 620 743 }, 744 + "hono@4.12.5": { 745 + "integrity": "sha512-3qq+FUBtlTHhtYxbxheZgY8NIFnkkC/MR8u5TTsr7YZ3wixryQ3cCwn3iZbg8p8B88iDBBAYSfZDS75t8MN7Vg==" 746 + }, 621 747 "htmlparser2@10.1.0": { 622 748 "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", 623 749 "dependencies": [ ··· 665 791 "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", 666 792 "bin": true 667 793 }, 794 + "nanoid@5.1.6": { 795 + "integrity": "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==", 796 + "bin": true 797 + }, 668 798 "native-file-system-adapter@3.0.1": { 669 799 "integrity": "sha512-ocuhsYk2SY0906LPc3QIMW+rCV3MdhqGiy7wV5Bf0e8/5TsMjDdyIwhNiVPiKxzTJLDrLT6h8BoV9ERfJscKhw==", 670 800 "optionalDependencies": [ ··· 684 814 "boolbase" 685 815 ] 686 816 }, 817 + "openapi3-ts@4.5.0": { 818 + "integrity": "sha512-jaL+HgTq2Gj5jRcfdutgRGLosCy/hT8sQf6VOy+P+g36cZOjI1iukdPnijC+4CmeRzg/jEllJUboEic2FhxhtQ==", 819 + "dependencies": [ 820 + "yaml" 821 + ] 822 + }, 687 823 "parse5-htmlparser2-tree-adapter@7.1.0": { 688 824 "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", 689 825 "dependencies": [ ··· 727 863 "postcss@8.5.6": { 728 864 "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", 729 865 "dependencies": [ 730 - "nanoid", 866 + "nanoid@3.3.11", 731 867 "picocolors", 732 868 "source-map-js" 733 869 ] ··· 756 892 "supports-preserve-symlinks-flag@1.0.0": { 757 893 "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" 758 894 }, 895 + "tagged-tag@1.0.0": { 896 + "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==" 897 + }, 898 + "ts-fsrs@5.2.3": { 899 + "integrity": "sha512-R3IjceC9WfnvUin6Nx+DwqEzh3Qil6Gg2yEHqvocUcC7Nbi+xDrFg/1fKaYBT0tJedDnDAguXMSX0hijhi859w==" 900 + }, 901 + "type-fest@5.4.4": { 902 + "integrity": "sha512-JnTrzGu+zPV3aXIUhnyWJj4z/wigMsdYajGLIYakqyOW1nPllzXEJee0QQbHj+CTIQtXGlAjuK0UY+2xTyjVAw==", 903 + "dependencies": [ 904 + "tagged-tag" 905 + ] 906 + }, 759 907 "undici@7.22.0": { 760 908 "integrity": "sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==" 761 909 }, ··· 780 928 }, 781 929 "whatwg-mimetype@4.0.0": { 782 930 "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==" 931 + }, 932 + "yaml@2.8.2": { 933 + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", 934 + "bin": true 935 + }, 936 + "zod@4.3.6": { 937 + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==" 783 938 } 784 939 }, 785 940 "workspace": { ··· 787 942 "jsr:@astral/astral@~0.5.5", 788 943 "jsr:@b-fuze/deno-dom@~0.1.56", 789 944 "jsr:@std/assert@^1.0.19", 945 + "jsr:@std/dotenv@~0.225.6", 790 946 "jsr:@std/testing@^1.0.17" 791 947 ], 792 948 "members": { 949 + "apps/sync": { 950 + "dependencies": [ 951 + "jsr:@bpev/sync-link@^0.0.21", 952 + "jsr:@civility/store@0.3", 953 + "jsr:@hono/hono@^4.12.5", 954 + "jsr:@inro/simple-tools@0.5.2", 955 + "jsr:@luca/esbuild-deno-loader@~0.11.1", 956 + "jsr:@std/crypto@^1.0.5", 957 + "jsr:@std/dotenv@~0.225.6", 958 + "jsr:@std/encoding@^1.0.10", 959 + "jsr:@std/path@^1.1.4", 960 + "jsr:@std/uuid@^1.1.0", 961 + "jsr:@zaubrik/djwt@^3.0.2", 962 + "npm:@hono/zod-openapi@^1.2.2", 963 + "npm:@oslojs/crypto@^1.0.1", 964 + "npm:@oslojs/encoding@^1.1.0", 965 + "npm:@scalar/hono-api-reference@0.10", 966 + "npm:esbuild@~0.27.3", 967 + "npm:native-file-system-adapter@^3.0.1", 968 + "npm:zod@^4.3.6" 969 + ] 970 + }, 793 971 "packages/cli": { 794 972 "dependencies": [ 795 973 "jsr:@astral/astral@~0.5.5",
+1 -1
packages/store/__tests__/migrations.test.ts
··· 59 59 60 60 const storage = new LocalStorage<V1>({ 61 61 name: testKey, 62 - defaultValue: { count: 0, version: '1.0.0' }, 62 + defaultValue: { count: 0, version: '0.0.0' }, 63 63 deserialize: (str) => JSON.parse(str), 64 64 serialize: (data) => JSON.stringify(data), 65 65 verify: verifyV1,
+45
packages/sync/__mocks__/native-file-system-adapter.ts
··· 1 + /** 2 + * Mock implementation of native-file-system-adapter for testing 3 + */ 4 + 5 + // Minimal file-like object for testing 6 + interface MockFile { 7 + arrayBuffer: () => Promise<ArrayBuffer> 8 + } 9 + 10 + // File handle interface matching what tests need 11 + interface FileHandle { 12 + name: string 13 + getFile: () => Promise<MockFile> 14 + } 15 + 16 + // Mock state for file picker 17 + let mockFilePickerImpl: (() => Promise<FileHandle[]>) | null = null 18 + 19 + /** 20 + * Mock showOpenFilePicker function 21 + */ 22 + export async function showOpenFilePicker( 23 + _options?: unknown, 24 + ): Promise<FileHandle[]> { 25 + if (mockFilePickerImpl) { 26 + return await mockFilePickerImpl() 27 + } 28 + throw new Error('No mock file picker implementation set') 29 + } 30 + 31 + /** 32 + * Test utility to set the mock implementation 33 + */ 34 + export function _setMockFilePicker( 35 + impl: () => Promise<FileHandle[]>, 36 + ): void { 37 + mockFilePickerImpl = impl 38 + } 39 + 40 + /** 41 + * Test utility to reset the mock 42 + */ 43 + export function _resetMockFilePicker(): void { 44 + mockFilePickerImpl = null 45 + }
+97
packages/sync/__tests__/stores.test.ts
··· 1 + /** 2 + * Client Tests 3 + * 4 + * Focuses on offline workflow and core features. Network sync and E2E workflows 5 + * are covered in integration tests. 6 + */ 7 + import { assert, assertEquals } from '@std/assert' 8 + import SyncLink from '../mod.ts' 9 + import useText from '../stores/text.ts' 10 + import useJSON from '../stores/json.ts' 11 + 12 + // deno-lint-ignore no-explicit-any 13 + let client: SyncLink<any> | undefined 14 + const name = 'sync-link' 15 + 16 + Deno.test.beforeEach(() => { 17 + globalThis.localStorage.clear() 18 + }) 19 + 20 + Deno.test.afterEach(() => { 21 + if (client) client.dispose() 22 + client = undefined 23 + globalThis.localStorage.clear() 24 + }) 25 + 26 + Deno.test('Text Store', async () => { 27 + const store = useText(name, 'initial') 28 + client = new SyncLink(store) 29 + 30 + assert(await client.get(), 'initial') 31 + 32 + await client.set('updated') 33 + assertEquals(await client.get(), 'updated') 34 + assertEquals(await client.has(), true) 35 + 36 + const saved = JSON.parse(globalThis.localStorage.getItem(name) || '{}') 37 + assertEquals(saved.data, 'updated', 'should save to local storage') 38 + }) 39 + 40 + Deno.test('JSON Store', async () => { 41 + type AppState = { 42 + todos: Array<{ 43 + id: number 44 + text: string 45 + completed: boolean 46 + }> 47 + filter: 'all' | 'active' | 'completed' 48 + } 49 + 50 + const defaultState: AppState = { todos: [], filter: 'all' } 51 + const updatedState: AppState = { 52 + todos: [ 53 + { id: 1, text: 'Buy milk', completed: false }, 54 + { id: 2, text: 'Walk dog', completed: true }, 55 + ], 56 + filter: 'active', 57 + } 58 + 59 + const storage = useJSON<AppState>(name, defaultState) 60 + client = new SyncLink<AppState>(storage) 61 + 62 + assertEquals(await client.get(), defaultState) 63 + await client.set(updatedState) 64 + assertEquals(await client.get(), updatedState) 65 + 66 + const savedData = JSON.parse(globalThis.localStorage.getItem(name) || '{}') 67 + const savedJSON = JSON.parse(savedData.data || '{}') 68 + assertEquals(savedJSON, updatedState, 'should save to local storage') 69 + }) 70 + 71 + Deno.test('reset', async () => { 72 + const store = useText(name, 'initial') 73 + client = new SyncLink(store) 74 + 75 + await client.set('my test data') 76 + assertEquals(await client.get(), 'my test data', 'initial test data is set') 77 + 78 + const result = await client.resetData() 79 + // assertEquals(result.success, true, 'sync is successful') 80 + assertEquals(result.path, '', 'no path is given for a reset') 81 + assertEquals(await client.get(), 'initial', 'should reset to default value') 82 + }) 83 + 84 + Deno.test('loads existing localStorage data', async () => { 85 + globalThis.localStorage.setItem( 86 + name, 87 + JSON.stringify({ 88 + data: 'existing value', 89 + createdAt: new Date().toISOString(), 90 + updatedAt: new Date().toISOString(), 91 + }), 92 + ) 93 + 94 + client = new SyncLink(useText(name, 'default value')) 95 + 96 + assertEquals(await client.get(), 'existing value') 97 + })
+1 -1
packages/sync/deno.json
··· 1 1 { 2 2 "name": "@bpev/sync-link", 3 - "version": "0.0.20", 3 + "version": "0.0.21", 4 4 "license": "MIT", 5 5 "exports": { 6 6 ".": "./mod.ts",
+2
packages/sync/mod.ts
··· 1293 1293 return { path, content } 1294 1294 } 1295 1295 } 1296 + 1297 + export { createApi, SyncLinkApi }