diff --git a/.github/workflows/web.yml b/.github/workflows/web.yml index 5edbe93a..c88c66b8 100644 --- a/.github/workflows/web.yml +++ b/.github/workflows/web.yml @@ -34,8 +34,8 @@ jobs: # - name: Lint # run: deno task lint - # - name: Typecheck - # run: deno task check + - name: Typecheck + run: deno task check - name: Build run: deno task build @@ -46,11 +46,11 @@ jobs: # needs: test # steps: - - name: checkout repository - uses: actions/checkout@v6 + # - name: checkout repository + # uses: actions/checkout@v6 - - name: setup - uses: './.github/actions/shared_setup' + # - name: setup + # uses: './.github/actions/shared_setup' # - name: Build # run: deno task build diff --git a/deno.json b/deno.json index eee795aa..7332215a 100644 --- a/deno.json +++ b/deno.json @@ -15,7 +15,11 @@ "imports": { "@std/datetime": "jsr:@std/datetime@^0.225.3", "@std/yaml": "jsr:@std/yaml@^1.0.5", - "@std/fmt": "jsr:@std/fmt@0.225.6", + // NOTE mapped as an explicit subpath rather than a bare "@std/fmt" or an "@std/fmt/" + // prefix: `jsrDepsInNodeModules` rewrites those to their npm form + // ("npm:@jsr/std__fmt@0.225.6/"), which cannot resolve a subpath. Any other + // @std/fmt subpath we start importing needs its own entry here. + "@std/fmt/bytes": "jsr:@std/fmt@0.225.6/bytes", "@std/fs": "jsr:@std/fs@0.229.3", "@std/log": "jsr:@std/log@^0.224.6", "@std/media-types": "jsr:@std/media-types@0.224.0", @@ -31,5 +35,6 @@ "unstable": ["raw-imports"], "nodeModulesDir": "auto", + "jsrDepsInNodeModules": true, "allowScripts": ["npm:@sveltejs/kit@2.5.24", "npm:@tailwindcss/oxide@4.1.14", "npm:esbuild@0.25.10", "npm:fsevents@2.3.3"] } diff --git a/deno.lock b/deno.lock index 7058ed2c..e44c3f5d 100644 --- a/deno.lock +++ b/deno.lock @@ -65,15 +65,26 @@ "jsr:@std/yaml@*": "1.0.9", "jsr:@std/yaml@^1.0.5": "1.0.9", "jsr:@torm/sqlite@^1.9.7": "1.9.7", - "npm:@deno/vite-plugin@1.0.6": "1.0.6_vite@7.1.8__@types+node@24.2.0__picomatch@4.0.3_@types+node@24.2.0", + "npm:@deno/vite-plugin@1.0.6": "1.0.6_vite@7.1.8_@types+node@24.2.0", + "npm:@jsr/andykais__ts-rpc@~0.2.2": "0.2.4_svelte@5.56.4", "npm:@jsr/andykais__ts-rpc@~0.2.4": "0.2.4_svelte@5.56.4", + "npm:@jsr/cliffy__command@1.0.0-rc.8": "1.0.0-rc.8", + "npm:@jsr/std__datetime@~0.225.3": "0.225.7", + "npm:@jsr/std__fmt@0.225.6": "0.225.6", + "npm:@jsr/std__fs@0.229.3": "0.229.3", + "npm:@jsr/std__http@0.224.5": "0.224.5", + "npm:@jsr/std__log@~0.224.6": "0.224.14", + "npm:@jsr/std__media-types@0.224.0": "0.224.0", + "npm:@jsr/std__path@1.0.3": "1.0.3", + "npm:@jsr/std__yaml@^1.0.5": "1.3.0", "npm:@jsr/torm__sqlite@^1.9.7": "1.9.7", "npm:@opentelemetry/api@1.9.0": "1.9.0", "npm:@opentelemetry/api@^1.9.0": "1.9.0", - "npm:@sveltejs/kit@2.43.7": "2.43.7_@opentelemetry+api@1.9.0_@sveltejs+vite-plugin-svelte@6.2.1__svelte@5.56.4__vite@7.1.8___@types+node@24.2.0___picomatch@4.0.3_svelte@5.56.4_vite@7.1.8__@types+node@24.2.0__picomatch@4.0.3", - "npm:@sveltejs/kit@2.5.24": "2.5.24_@sveltejs+vite-plugin-svelte@6.2.1__svelte@5.56.4__vite@7.1.8___@types+node@24.2.0___picomatch@4.0.3_svelte@5.56.4_vite@7.1.8__@types+node@24.2.0__picomatch@4.0.3", - "npm:@sveltejs/vite-plugin-svelte@6.2.1": "6.2.1_svelte@5.56.4_vite@7.1.8__@types+node@24.2.0__picomatch@4.0.3", - "npm:@tailwindcss/vite@4.1.14": "4.1.14_vite@7.1.8__@types+node@24.2.0__picomatch@4.0.3_@types+node@24.2.0", + "npm:@sveltejs/kit@2.43.7": "2.43.7_@opentelemetry+api@1.9.0_@sveltejs+vite-plugin-svelte@6.2.1_svelte@5.56.4_vite@7.1.8", + "npm:@sveltejs/kit@2.5.24": "2.5.24_@sveltejs+vite-plugin-svelte@6.2.1_svelte@5.56.4_vite@7.1.8", + "npm:@sveltejs/vite-plugin-svelte@6.2.1": "6.2.1_svelte@5.56.4_vite@7.1.8", + "npm:@tailwindcss/vite@4.1.14": "4.1.14_vite@7.1.8_@types+node@24.2.0", + "npm:@types/deno@^2.5.0": "2.7.0", "npm:@types/node@*": "24.2.0", "npm:esbuild@*": "0.25.10", "npm:path-to-regexp@6.2.1": "6.2.1", @@ -353,7 +364,7 @@ } }, "npm": { - "@deno/vite-plugin@1.0.6_vite@7.1.8__@types+node@24.2.0__picomatch@4.0.3_@types+node@24.2.0": { + "@deno/vite-plugin@1.0.6_vite@7.1.8_@types+node@24.2.0": { "integrity": "sha512-Sh5XqvFuKAwjARTesi0n6xRpEXm1V0UeqKh+SxIrexCofxOaieNDMqXZD02RiZCg0mrJ43V8eCMuVrDfq6mLmg==", "dependencies": [ "vite" @@ -526,10 +537,42 @@ "integrity": "sha512-q8DSNIQLpPw/z+VcVcY+rrbRsmzZmw+bwopW4u95buXRLO/Bet8uYRddroAA8AI5zZkvuHJnxlNppwUmIFU5rA==", "dependencies": [ "@jsr/oak__oak", - "@sveltejs/kit@2.5.24_@sveltejs+vite-plugin-svelte@6.2.1__svelte@5.56.4__vite@7.1.8___@types+node@24.2.0___picomatch@4.0.3_svelte@5.56.4_vite@7.1.8__@types+node@24.2.0__picomatch@4.0.3" + "@sveltejs/kit@2.5.24_@sveltejs+vite-plugin-svelte@6.2.1_svelte@5.56.4_vite@7.1.8" ], "tarball": "https://npm.jsr.io/~/11/@jsr/andykais__ts-rpc/0.2.4.tgz" }, + "@jsr/cliffy__command@1.0.0-rc.8": { + "integrity": "sha512-ayYncuw/PTIiceKKMEZPyv9Qu39Pt6bNTBariKjZi0th5Je4am6RNC+FSXEAHPJBH+bAY3S0lwaFsEGC9uPEmg==", + "dependencies": [ + "@jsr/cliffy__flags", + "@jsr/cliffy__internal", + "@jsr/cliffy__table", + "@jsr/std__fmt@1.0.10", + "@jsr/std__text" + ], + "tarball": "https://npm.jsr.io/~/11/@jsr/cliffy__command/1.0.0-rc.8.tgz" + }, + "@jsr/cliffy__flags@1.0.0-rc.8": { + "integrity": "sha512-gbIPx2XIxkNlAQikraWm4h+C77tHmE8NQhwIX6Qn2rrU32uh/Vg4OXklXvlrkfrcs3wMbOd/SZ9e/wVqM0H8/w==", + "dependencies": [ + "@jsr/std__text" + ], + "tarball": "https://npm.jsr.io/~/11/@jsr/cliffy__flags/1.0.0-rc.8.tgz" + }, + "@jsr/cliffy__internal@1.0.0-rc.8": { + "integrity": "sha512-xKIMr2v5+R5qNw7CWqic2ulhZE3cXPKB40rb5mibsUrtJkC55Ha/3PGgTm1W9SWH1+IqA/FfC2ikpjzCXSKVBw==", + "dependencies": [ + "@jsr/std__fmt@1.0.10" + ], + "tarball": "https://npm.jsr.io/~/11/@jsr/cliffy__internal/1.0.0-rc.8.tgz" + }, + "@jsr/cliffy__table@1.0.0-rc.8": { + "integrity": "sha512-7r94r56TLd17raKyZQHV3/qLngy2Lusuy4rjA/5QMDNM0lDIwZw67lmRMxpyX6+qzAxzRAJ8dtTvFSvGwrSqgg==", + "dependencies": [ + "@jsr/std__fmt@1.0.10" + ], + "tarball": "https://npm.jsr.io/~/11/@jsr/cliffy__table/1.0.0-rc.8.tgz" + }, "@jsr/oak__commons@0.10.2": { "integrity": "sha512-E7aBj8qrfgPiAhmXCTpq/ZhErnlbMwsG6WO2cthuXrwEDl5pNf90nTi6Q/Jjw0ngC/9ElfueXmGEuWydhGYJBQ==", "dependencies": [ @@ -574,7 +617,7 @@ "@jsr/std__assert@0.226.0": { "integrity": "sha512-xCuUFDfHkIZd96glKgjZbnYFqu6blu8Y53SyvDMlFDJm1Y/j+/FcW6xq7TzGFIaF5B9QecIlDfamfhzA8ZdVbg==", "dependencies": [ - "@jsr/std__internal@1.0.10" + "@jsr/std__internal@1.0.14" ], "tarball": "https://npm.jsr.io/~/11/@jsr/std__assert/0.226.0.tgz" }, @@ -628,6 +671,10 @@ ], "tarball": "https://npm.jsr.io/~/11/@jsr/std__crypto/0.224.0.tgz" }, + "@jsr/std__datetime@0.225.7": { + "integrity": "sha512-z1N0Ompk9zzcrwExq2uMxGAvnXDjV9W9m5Lun+OE27/Y2F+jvDWdZ766a28h+U3XKKoDq0hByYVuhOZ7pzWuhw==", + "tarball": "https://npm.jsr.io/~/11/@jsr/std__datetime/0.225.7.tgz" + }, "@jsr/std__encoding@0.223.0": { "integrity": "sha512-auQ0wUZFsVGXU+6qfjGeonWe8utBhp+y43etR1lanCJXXUzL5W3yS+i73Fki+RLjvxUG4Pwd1CrGSR57kIvrIg==", "tarball": "https://npm.jsr.io/~/11/@jsr/std__encoding/0.223.0.tgz" @@ -652,6 +699,25 @@ "integrity": "sha512-5wll7CImyrJfen4fecziNSxZ5S6Fo3H1rQqKMQDewRGHAnFVqcHLe8wwCuDLK+HTbSPy6AkR3b136WpjVdOjTQ==", "tarball": "https://npm.jsr.io/~/11/@jsr/std__fmt/0.225.6.tgz" }, + "@jsr/std__fmt@1.0.10": { + "integrity": "sha512-Q2N8XDNkh/c1IKTi+sO4Vm64M5m4W0o47qTURmMX0vgHm9AJ6jL7jEQ2kw0M1T2rBZiwAY78eUbYzFyOYWaCqQ==", + "tarball": "https://npm.jsr.io/~/11/@jsr/std__fmt/1.0.10.tgz" + }, + "@jsr/std__fs@0.229.3": { + "integrity": "sha512-hXsLcP1dumz3VbRKVhPtzs7Wigxhqp2rtBzwzrF9Cz9IMqZkp7EoKHxeqcUiduf7yYxVV/vbnfhjqbOLvlljjA==", + "dependencies": [ + "@jsr/std__path@1.0.0-rc.1" + ], + "tarball": "https://npm.jsr.io/~/11/@jsr/std__fs/0.229.3.tgz" + }, + "@jsr/std__fs@1.0.24": { + "integrity": "sha512-UunW2Rg++9sA74NJnYMGTsMVQgoFRAwPP02wRRohL0PQBNorbyxHiyCMRaxYQCtLepTLkC4nUyMtcwrGKqTR6g==", + "dependencies": [ + "@jsr/std__internal@1.0.14", + "@jsr/std__path@1.1.6" + ], + "tarball": "https://npm.jsr.io/~/11/@jsr/std__fs/1.0.24.tgz" + }, "@jsr/std__http@0.223.0": { "integrity": "sha512-v60Hv8J1GXM1ExsJgBXVcmhbyt4Ip3RFaIdkVrycujbBL5qFuhNnvkmggAonP+nwXy5V0crU/tkLNWTyncoPXQ==", "dependencies": [ @@ -687,9 +753,9 @@ ], "tarball": "https://npm.jsr.io/~/11/@jsr/std__internal/0.224.0.tgz" }, - "@jsr/std__internal@1.0.10": { - "integrity": "sha512-fmD6yKep/sMnB2yPQU/REZG7Z4N9SZwcUBNnceo4QkXk67l3JEfxHoROQ/YHeVSOmq6x55Ra6nuMjz2ib3nj3g==", - "tarball": "https://npm.jsr.io/~/11/@jsr/std__internal/1.0.10.tgz" + "@jsr/std__internal@1.0.14": { + "integrity": "sha512-JT8b/t40WcR9q0GDwRZUooY7aXeXnFal8iidE/5TckdAFtvpVAsaJ71/Xgf1SfoChs41s6CZkuCYM8toaNgdvA==", + "tarball": "https://npm.jsr.io/~/11/@jsr/std__internal/1.0.14.tgz" }, "@jsr/std__io@0.223.0": { "integrity": "sha512-VUPpfHeLvhooQFyujLSj6/KDCqgF+joBpb6eO9dg+JJWvy5wHj1Nl2EMRQe6Pl8nNVUwjt+ZraydkGTdlDS1jw==", @@ -706,10 +772,30 @@ ], "tarball": "https://npm.jsr.io/~/11/@jsr/std__io/0.224.9.tgz" }, + "@jsr/std__io@0.225.3": { + "integrity": "sha512-IDXY253ipW6FV34CJVxO+3ubfvSEEzw9N2W303KnLe9K/Y9+v/ID1dQYf9VsCCOFMpFtCmOLqzIZsRqv6yQnWw==", + "dependencies": [ + "@jsr/std__bytes@1.0.6" + ], + "tarball": "https://npm.jsr.io/~/11/@jsr/std__io/0.225.3.tgz" + }, + "@jsr/std__log@0.224.14": { + "integrity": "sha512-EHT7E0plakyzk/gxMrwqUf3YGCCxN3Is25QrEh7toYA7qwj46R4qY7cIaDEKy8QqI5JHOFHwWXOClcPK6goIoQ==", + "dependencies": [ + "@jsr/std__fmt@1.0.10", + "@jsr/std__fs@1.0.24", + "@jsr/std__io@0.225.3" + ], + "tarball": "https://npm.jsr.io/~/11/@jsr/std__log/0.224.14.tgz" + }, "@jsr/std__media-types@0.223.0": { "integrity": "sha512-wJe0dRZiL1OqU8oGXReVuMK5stHERyGgpR/6yS4qW9+p74+XXSIP+OGubfzl8TjeKeULd82zLbk1QNo5FvwXNw==", "tarball": "https://npm.jsr.io/~/11/@jsr/std__media-types/0.223.0.tgz" }, + "@jsr/std__media-types@0.224.0": { + "integrity": "sha512-ua4EvwuGmFFB0KREfJGROd+EeVxGE6IwNJnfP4jHGyIuH7JLxX78r1cNbPxVjTrNso2GbjFdBQ2pKcyPQOOhUQ==", + "tarball": "https://npm.jsr.io/~/11/@jsr/std__media-types/0.224.0.tgz" + }, "@jsr/std__media-types@0.224.1": { "integrity": "sha512-L3kF5zNOA0KNibbdKwxmjaHUOpa7vWTb6BNwv41QZEjOtmS8GWPTRAXkglLEUfu2gl6pXlOoT3BmXv/naO5tKA==", "tarball": "https://npm.jsr.io/~/11/@jsr/std__media-types/0.224.1.tgz" @@ -729,10 +815,29 @@ ], "tarball": "https://npm.jsr.io/~/11/@jsr/std__path/0.223.0.tgz" }, + "@jsr/std__path@1.0.0-rc.1": { + "integrity": "sha512-bGolWCcaF8HGT0QVZr/heEaHm6MEgpOI7LyrV4JLuBYrLb81FAX7m3Li77JnF06RNhJViY8K7zvPpuVIiakyvA==", + "tarball": "https://npm.jsr.io/~/11/@jsr/std__path/1.0.0-rc.1.tgz" + }, "@jsr/std__path@1.0.0-rc.2": { "integrity": "sha512-qUHfVMqAtMGPFmdkOfmkCONqgpe4UFVAaD+wL6ZdlQxtvQiNxHQWsje+djri+0095s/kkoBOPH4CpYxBq+zh3A==", "tarball": "https://npm.jsr.io/~/11/@jsr/std__path/1.0.0-rc.2.tgz" }, + "@jsr/std__path@1.0.3": { + "integrity": "sha512-XH7cE38WHq4J3ICVKrVs0GvKf9EXfMxAzvXkVewHXJeyh7TW/bN47yIPArDQUybHlyLkTQvQaDW0hWDY3TP2xQ==", + "tarball": "https://npm.jsr.io/~/11/@jsr/std__path/1.0.3.tgz" + }, + "@jsr/std__path@1.1.6": { + "integrity": "sha512-H1Cmg6z8jFyIsQbVV+vZB4eOx4k6Qg8lyUM6l7nZysj5MZumg89kb0M1bsPBTGQis3fw6mJFkJELo5+AK6oCFA==", + "dependencies": [ + "@jsr/std__internal@1.0.14" + ], + "tarball": "https://npm.jsr.io/~/11/@jsr/std__path/1.1.6.tgz" + }, + "@jsr/std__regexp@1.0.2": { + "integrity": "sha512-wvJnzBWsPW4YLdAhCSggh4NyNia/qtzsuJr0kSYuqUBtvOU/RsI/WKB5lu7ehlzuZ5Sy+efr0ACyogCCZmw0YQ==", + "tarball": "https://npm.jsr.io/~/11/@jsr/std__regexp/1.0.2.tgz" + }, "@jsr/std__streams@0.223.0": { "integrity": "sha512-YfOfRTpnTocgC5h22aRMgu74ByBO6763PgwG2p7iFWYZFBRrDNW6WqYK1MFXcbU8VrwlgpUEvkS3Xk9dJnd+hw==", "dependencies": [ @@ -750,6 +855,17 @@ ], "tarball": "https://npm.jsr.io/~/11/@jsr/std__streams/0.224.5.tgz" }, + "@jsr/std__text@1.0.19": { + "integrity": "sha512-OIXUoD4gty+GH+ZOpe7LT1jVr+31ctOoIoxBzLN1m3fvAOtqWyEz6Ap3NwDiuEgHpAIMz/O5L2SKDr0F2bChCA==", + "dependencies": [ + "@jsr/std__regexp" + ], + "tarball": "https://npm.jsr.io/~/11/@jsr/std__text/1.0.19.tgz" + }, + "@jsr/std__yaml@1.3.0": { + "integrity": "sha512-TuFbXlSy3nRuQuR8co2t7yzhv53ObdYZZidn8M958Glj6vBp5b7nxg7wTN8VTyCwC3vpbr6Mfdx9auNe/BMAVA==", + "tarball": "https://npm.jsr.io/~/11/@jsr/std__yaml/1.3.0.tgz" + }, "@jsr/torm__sqlite@1.9.7": { "integrity": "sha512-naligACB12UtWD43fd0QtUCIgNotPoiib3r/qFHAMj0Hy2RxapfR24dSH2U5gHQM4DSBCt2/sqwcjojY2T3SMw==", "dependencies": [ @@ -883,7 +999,7 @@ "acorn" ] }, - "@sveltejs/kit@2.43.7_@opentelemetry+api@1.9.0_@sveltejs+vite-plugin-svelte@6.2.1__svelte@5.56.4__vite@7.1.8___@types+node@24.2.0___picomatch@4.0.3_svelte@5.56.4_vite@7.1.8__@types+node@24.2.0__picomatch@4.0.3": { + "@sveltejs/kit@2.43.7_@opentelemetry+api@1.9.0_@sveltejs+vite-plugin-svelte@6.2.1_svelte@5.56.4_vite@7.1.8": { "integrity": "sha512-6trpyltB9XZNkM8cfVHG9U2urAH4NPD7UeO0wiBvZjD8gHj6w9bVeWnBQgnO8LPNpzOhSlwnZDk355OOAa/9Zw==", "dependencies": [ "@opentelemetry/api", @@ -909,7 +1025,7 @@ ], "bin": true }, - "@sveltejs/kit@2.5.24_@sveltejs+vite-plugin-svelte@6.2.1__svelte@5.56.4__vite@7.1.8___@types+node@24.2.0___picomatch@4.0.3_svelte@5.56.4_vite@7.1.8__@types+node@24.2.0__picomatch@4.0.3": { + "@sveltejs/kit@2.5.24_@sveltejs+vite-plugin-svelte@6.2.1_svelte@5.56.4_vite@7.1.8": { "integrity": "sha512-Nr2oxsCsDfEkdS/zzQQQbsPYTbu692Qs3/iE3L7VHzCVjG2+WujF9oMUozWI7GuX98KxYSoPMlAsfmDLSg44hQ==", "dependencies": [ "@sveltejs/vite-plugin-svelte", @@ -934,7 +1050,7 @@ "@sveltejs/sv-utils@0.0.2": { "integrity": "sha512-vmH85dvUP5UtlSgu/dmlybSUT9ZGURkrKlLNzKJ5fO5ZmnV/kzlS3UlzlTrffqbVEkaZwEJV3RW45RK19z0vVg==" }, - "@sveltejs/vite-plugin-svelte-inspector@5.0.1_@sveltejs+vite-plugin-svelte@6.2.1__svelte@5.56.4__vite@7.1.8___@types+node@24.2.0___picomatch@4.0.3_svelte@5.56.4_vite@7.1.8__@types+node@24.2.0__picomatch@4.0.3": { + "@sveltejs/vite-plugin-svelte-inspector@5.0.1_@sveltejs+vite-plugin-svelte@6.2.1_svelte@5.56.4_vite@7.1.8": { "integrity": "sha512-ubWshlMk4bc8mkwWbg6vNvCeT7lGQojE3ijDh3QTR6Zr/R+GXxsGbyH4PExEPpiFmqPhYiVSVmHBjUcVc1JIrA==", "dependencies": [ "@sveltejs/vite-plugin-svelte", @@ -943,7 +1059,7 @@ "vite" ] }, - "@sveltejs/vite-plugin-svelte@6.2.1_svelte@5.56.4_vite@7.1.8__@types+node@24.2.0__picomatch@4.0.3": { + "@sveltejs/vite-plugin-svelte@6.2.1_svelte@5.56.4_vite@7.1.8": { "integrity": "sha512-YZs/OSKOQAQCnJvM/P+F1URotNnYNeU3P2s4oIpzm1uFaqUEqRxUB0g5ejMjEb5Gjb9/PiBI5Ktrq4rUUF8UVQ==", "dependencies": [ "@sveltejs/vite-plugin-svelte-inspector", @@ -1048,7 +1164,7 @@ ], "scripts": true }, - "@tailwindcss/vite@4.1.14_vite@7.1.8__@types+node@24.2.0__picomatch@4.0.3_@types+node@24.2.0": { + "@tailwindcss/vite@4.1.14_vite@7.1.8_@types+node@24.2.0": { "integrity": "sha512-BoFUoU0XqgCUS1UXWhmDJroKKhNXeDzD7/XwabjkDIAbMnc4ULn5e2FuEuBbhZ6ENZoSYzKlzvZ44Yr6EUDUSA==", "dependencies": [ "@tailwindcss/node", @@ -1060,6 +1176,9 @@ "@types/cookie@0.6.0": { "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==" }, + "@types/deno@2.7.0": { + "integrity": "sha512-Y6fWcV8KpYeO3Lik/RiYnUxtr/LWqoeACciU0CA+dr1U/45tGgaX3HWQQAPCNN53raN4Rr4Fht4y6qsUH57fDA==" + }, "@types/estree@1.0.8": { "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==" }, @@ -1527,7 +1646,7 @@ ], "bin": true }, - "vitefu@1.1.1_vite@7.1.8__@types+node@24.2.0__picomatch@4.0.3_@types+node@24.2.0": { + "vitefu@1.1.1_vite@7.1.8_@types+node@24.2.0": { "integrity": "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==", "dependencies": [ "vite" @@ -1573,11 +1692,11 @@ "dependencies": [ "npm:@deno/vite-plugin@1.0.6", "npm:@jsr/andykais__ts-rpc@~0.2.4", - "npm:@jsr/torm__sqlite@^1.9.7", "npm:@opentelemetry/api@1.9.0", "npm:@sveltejs/kit@2.43.7", "npm:@sveltejs/vite-plugin-svelte@6.2.1", "npm:@tailwindcss/vite@4.1.14", + "npm:@types/deno@^2.5.0", "npm:svelte-check@4.3.2", "npm:svelte@5.56.4", "npm:tailwindcss@4.1.14", diff --git a/packages/core/src/lib/file_processor.ts b/packages/core/src/lib/file_processor.ts index 10646220..642c651e 100644 --- a/packages/core/src/lib/file_processor.ts +++ b/packages/core/src/lib/file_processor.ts @@ -668,7 +668,11 @@ class FileProcessor { } static async * read_lines(stream: ReadableStream) { - const reader = stream.pipeThrough(new TextDecoderStream()).getReader() + // NOTE `deno check` passes without this cast, but @forager/web type-checks core's source + // under TypeScript's DOM lib, which types TextDecoderStream's writable side as + // WritableStream and rejects piping a Uint8Array stream into it. + const decoder = new TextDecoderStream() as ReadableWritablePair + const reader = stream.pipeThrough(decoder).getReader() let buffer = '' while (true) { const read_result = await reader.read() diff --git a/packages/web/package.json b/packages/web/package.json index dd554f94..d34594bf 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -13,14 +13,14 @@ }, "dependencies": { "@andykais/ts-rpc": "npm:@jsr/andykais__ts-rpc@^0.2.4", - "@opentelemetry/api": "1.9.0", - "@torm/sqlite": "npm:@jsr/torm__sqlite@^1.9.7" + "@opentelemetry/api": "1.9.0" }, "devDependencies": { "@deno/vite-plugin": "1.0.6", "@sveltejs/kit": "2.43.7", "@sveltejs/vite-plugin-svelte": "6.2.1", "@tailwindcss/vite": "4.1.14", + "@types/deno": "^2.5.0", "svelte": "5.56.4", "svelte-check": "4.3.2", "tailwindcss": "4.1.14", diff --git a/packages/web/src/app.d.ts b/packages/web/src/app.d.ts index da08e6da..591e5d15 100644 --- a/packages/web/src/app.d.ts +++ b/packages/web/src/app.d.ts @@ -1,9 +1,15 @@ // See https://svelte.dev/docs/kit/types#app.d.ts // for information about these interfaces +import type { Forager } from '@forager/core' +import type { Config } from '$lib/server/config.ts' + declare global { namespace App { // interface Error {} - // interface Locals {} + interface Locals { + forager: Forager + config: Config + } // interface PageData {} // interface PageState {} // interface Platform {} diff --git a/packages/web/src/hooks.server.ts b/packages/web/src/hooks.server.ts index d6b8e76f..787bc67a 100644 --- a/packages/web/src/hooks.server.ts +++ b/packages/web/src/hooks.server.ts @@ -29,8 +29,10 @@ if (!building) { forager.init() } else { if (env.FORAGER_INSTANCE) { - forager = env.FORAGER_INSTANCE - config = env.FORAGER_CONFIG + // In production the CLI injects a live Forager instance and parsed config + // through these env slots, so they are not the plain strings $env types them as. + forager = env.FORAGER_INSTANCE as unknown as Forager + config = env.FORAGER_CONFIG as unknown as Config } else { throw new Error(`FORAGER_INSTANCE must be passed to sveltekit hooks`) } diff --git a/packages/web/src/lib/actions/focusable.ts b/packages/web/src/lib/actions/focusable.ts index c4534a0d..c04dfab6 100644 --- a/packages/web/src/lib/actions/focusable.ts +++ b/packages/web/src/lib/actions/focusable.ts @@ -1,11 +1,15 @@ -export function focusable(node: HTMLElement) { +export function focusable(node: HTMLElement, focused: boolean) { + const apply = (should_focus: boolean) => { + if (should_focus) { + node.focus() + } else { + node.blur() + } + } + apply(focused) return { - update(focused: boolean) { - if (focused) { - node.focus() - } else { - node.blur() - } + update(should_focus: boolean) { + apply(should_focus) } } diff --git a/packages/web/src/lib/actions/scrollable.ts b/packages/web/src/lib/actions/scrollable.ts index ea361469..991ec967 100644 --- a/packages/web/src/lib/actions/scrollable.ts +++ b/packages/web/src/lib/actions/scrollable.ts @@ -1,4 +1,4 @@ -export function scrollable(node: HTMLElement) { +export function scrollable(node: HTMLElement, _focused: boolean) { let is_visible = false const observer = new IntersectionObserver((entries) => { is_visible = entries[0].isIntersecting diff --git a/packages/web/src/lib/api.ts b/packages/web/src/lib/api.ts index 039ddc95..b871b0d0 100644 --- a/packages/web/src/lib/api.ts +++ b/packages/web/src/lib/api.ts @@ -18,11 +18,37 @@ class ForagerTagApi extends rpc.ApiController { parent_delete = this.context.forager.tag.parent_delete } +class ForagerMediaApi extends rpc.ApiController { + create = this.context.forager.media.create + update = this.context.forager.media.update + upsert = this.context.forager.media.upsert + delete = this.context.forager.media.delete + search = this.context.forager.media.search + group = this.context.forager.media.group + get = this.context.forager.media.get + thumbnail = this.context.forager.media.thumbnail + reload = this.context.forager.media.reload +} + +class ForagerSeriesApi extends rpc.ApiController { + create = this.context.forager.series.create + update = this.context.forager.series.update + upsert = this.context.forager.series.upsert + add = this.context.forager.series.add + get = this.context.forager.series.get + search = this.context.forager.series.search +} + +class ForagerViewsApi extends rpc.ApiController { + start = this.context.forager.views.start + update = this.context.forager.views.update +} + class ForagerApi extends rpc.ApiController { - media = this.context.forager.media - series = this.context.forager.series + media = this.module(ForagerMediaApi) + series = this.module(ForagerSeriesApi) tag = this.module(ForagerTagApi) - views = this.context.forager.views + views = this.module(ForagerViewsApi) } diff --git a/packages/web/src/lib/base_controller.ts b/packages/web/src/lib/base_controller.ts index b221163e..9d0499b2 100644 --- a/packages/web/src/lib/base_controller.ts +++ b/packages/web/src/lib/base_controller.ts @@ -26,8 +26,6 @@ abstract class BaseController { if (this.#config) return this.#config else throw new Error(`Controller::config not initialized`) } - - abstract handlers: {} } export { BaseController } diff --git a/packages/web/src/lib/components/Header.svelte b/packages/web/src/lib/components/Header.svelte index 4ed3a0e7..18387468 100644 --- a/packages/web/src/lib/components/Header.svelte +++ b/packages/web/src/lib/components/Header.svelte @@ -8,6 +8,7 @@ ...props }: { title: string + height?: number children: SvelteHTMLElements['div']['children'] } = $props() diff --git a/packages/web/src/lib/components/SelectInput.svelte b/packages/web/src/lib/components/SelectInput.svelte index f8b7e702..428ec35c 100644 --- a/packages/web/src/lib/components/SelectInput.svelte +++ b/packages/web/src/lib/components/SelectInput.svelte @@ -4,10 +4,10 @@ value: string } interface Props { - label: string - options: SelectOption + label?: string + options: SelectOption[] value: string - onchange: () => void | undefined + onchange?: (e: Event) => void } let { label, diff --git a/packages/web/src/lib/components/Sidebar.svelte b/packages/web/src/lib/components/Sidebar.svelte index efc532b4..6ec88ba9 100644 --- a/packages/web/src/lib/components/Sidebar.svelte +++ b/packages/web/src/lib/components/Sidebar.svelte @@ -27,6 +27,8 @@ screen: 0, button: 0, sidebar_max: 0, + // only the initial width comes from props, dragging the sidebar owns it afterwards + // svelte-ignore state_referenced_locally sidebar: props.width, mouse_button_offset: 0, diff --git a/packages/web/src/lib/components/StarInput.svelte b/packages/web/src/lib/components/StarInput.svelte index b72f7bbf..53ae452e 100644 --- a/packages/web/src/lib/components/StarInput.svelte +++ b/packages/web/src/lib/components/StarInput.svelte @@ -5,16 +5,16 @@ let { star, - value = $bindable(), + value = $bindable(), onclick, }: { star: number - value: number + value: number | undefined onclick: () => void } = $props() let icon_color = $derived.by(() => { - if (value >= star) { + if ((value ?? 0) >= star) { // starred return theme.colors.gray[400] } else { diff --git a/packages/web/src/lib/components/Tag.svelte b/packages/web/src/lib/components/Tag.svelte index ba630442..ebbe7401 100644 --- a/packages/web/src/lib/components/Tag.svelte +++ b/packages/web/src/lib/components/Tag.svelte @@ -1,14 +1,14 @@ {#if !(props.hide_if_null && props.content === null)} diff --git a/packages/web/src/routes/browse/components/MediaDetails.svelte b/packages/web/src/routes/browse/components/MediaDetails.svelte index 9b526406..f508e5ad 100644 --- a/packages/web/src/routes/browse/components/MediaDetails.svelte +++ b/packages/web/src/routes/browse/components/MediaDetails.svelte @@ -1,6 +1,6 @@ diff --git a/packages/web/src/routes/tags/[slug]/+page.svelte b/packages/web/src/routes/tags/[slug]/+page.svelte index 62cf88cc..d1bd0f19 100644 --- a/packages/web/src/routes/tags/[slug]/+page.svelte +++ b/packages/web/src/routes/tags/[slug]/+page.svelte @@ -11,6 +11,8 @@ import Numeric from '$lib/components/Numeric.svelte'; let props = $props() + // the controller is created once per page, from the config the page was loaded with + // svelte-ignore state_referenced_locally const controller = contexts.controller.set(new TagDetailController(props.data.config)) let alias_input = $state('') @@ -18,7 +20,7 @@ let parent_input = $state('') $effect(() => { - const slug = decodeURIComponent(page.params.slug) + const slug = decodeURIComponent(page.params.slug ?? '') controller.load(slug) }) @@ -69,11 +71,11 @@ class="rounded-md py-1 px-3 text-slate-100 bg-gray-800 text-sm min-h-16" bind:value={controller.draft.description} > - + Media - + Unread - + Created
@@ -149,7 +151,7 @@ } }}>
- + Set this tag as an alias that hides the following tags: { public loading: boolean = $state(false) #has_more: boolean = false - get DEFAULTS() { return DEFAULTS } get URL_PARAM_MAP() { return URL_PARAM_MAP } constructor(client: BaseController['client']) { - super(client) + super(client, DEFAULTS) } #build_search_options(params: SearchParams, limit: number) { diff --git a/packages/web/svelte.config.js b/packages/web/svelte.config.js index 02299a17..ff7da84e 100644 --- a/packages/web/svelte.config.js +++ b/packages/web/svelte.config.js @@ -9,8 +9,14 @@ const config = { kit: { adapter: adapter(), - experimental: { - remoteFunctions: true, + // resolve @forager/core to its TypeScript source so svelte-check can infer types + // across the workspace. Declaring these here (rather than as `paths` in + // tsconfig.json) lets `svelte-kit sync` generate them into + // .svelte-kit/tsconfig.json alongside the $lib/$app aliases, so there is no + // hand-maintained copy to keep in sync. + alias: { + '@forager/core': '../core/src/mod.ts', + '~': '../core/src', }, prerender: { crawl: false, diff --git a/packages/web/tsconfig.json b/packages/web/tsconfig.json index 45be027a..27393233 100644 --- a/packages/web/tsconfig.json +++ b/packages/web/tsconfig.json @@ -11,17 +11,23 @@ "strict": true, "moduleResolution": "bundler", + // `@forager/core` is a Deno project whose source is pulled into the program + // through the `kit.alias` mapping. `verbatimModuleSyntax` is disabled so its + // (Deno-valid) value/type imports don't surface here, and `DOM.AsyncIterable` + // is added so async-iterable web streams (e.g. `for await (…of readable)`) + // type-check the same way they run under Deno. + "verbatimModuleSyntax": false, + "lib": ["esnext", "DOM", "DOM.Iterable", "DOM.AsyncIterable"], + "plugins": [{ "name": "typescript-svelte-plugin", "enabled": true, "assumeIsSvelteProject": true }], + // NOTE `paths` intentionally lives in svelte.config.js under `kit.alias`, which + // `svelte-kit sync` compiles into ./.svelte-kit/tsconfig.json for us. Declaring + // paths here instead would replace (not merge with) that generated mapping. "allowImportingTsExtensions": true } - // Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias - // except $lib which is handled by https://svelte.dev/docs/kit/configuration#files - // - // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes - // from the referenced tsconfig.json - TypeScript does not merge them in } diff --git a/packages/web/vite.config.ts b/packages/web/vite.config.ts index c7bd6c15..2c3ba88e 100644 --- a/packages/web/vite.config.ts +++ b/packages/web/vite.config.ts @@ -10,7 +10,7 @@ const monorepo_root = path.resolve(__dirname, '..', '..') * Note that npm depedencies like you may put into package.json like `npm:@jsr/torm__sqlite@^1.9.7` do _not_ work */ function resolve_workspace_imports(workspace_package: string) { const package_root = path.join(monorepo_root, 'packages', workspace_package) - const denojson = JSON.parse(new TextDecoder().decode(Deno.readFileSync(path.join(package_root, 'deno.json')))) + const denojson: { imports: Record } = JSON.parse(new TextDecoder().decode(Deno.readFileSync(path.join(package_root, 'deno.json')))) const resolved_imports: Record = {} for (const [package_alias, specifier] of Object.entries(denojson.imports)) {