Appearance
@libris/api-hono
0.29.0
Minor Changes
2376f08: Pull reading status from Hardcover into Libris on every Hardcover sync.
The
hardcover-syncworker is now bidirectional for status. In addition to pushing Libris-derived status to Hardcover (existing behavior), it also fetches each connected user'sme.user_booksfrom Hardcover and stores the status asreading_aggregate.external_status. This means a fresh Libris install with a large existing Hardcover library will instantly reflect the user's existing reading state across the library view — no need to re-mark anything by hand.Schema:
reading_aggregategains two columns:external_status(the Hardcover-pulled status) andexternal_status_synced_at. Hardcover's "Did Not Finish" (status_id 5) folds into Librispaused.Effective status precedence is now:
manual_status -- user override ?? local-computed (only if any local reading_progress for this book) ?? external_status -- Hardcover-pulled fallback ?? "unread"Local progress (KOReader / kosync) still wins over Hardcover-pulled status the moment any reading data lands in Libris. Manual overrides still beat both.
external_statusis read-only from the push side, so pulled values cannot loop back outward.
Patch Changes
bc775c9: Push
manual_statusoverrides to Hardcover.The Hardcover sync push side now respects
reading_aggregate.manual_status— if a user marks a book "finished" (or "reading", "paused") manually in Libris, that status is pushed to Hardcover on the next sync, even when no KOReader/kosync data exists for the book. Previously the push side only computed status from local progress and ignored manual overrides.Effective status pushed to Hardcover is
manual_status ?? computed. The existing "never push unread" guard still applies so we don't clobber Hardcover with no-data states.The change-detection CTE in
hardcover-sync.tswas extracted intolib/hardcover/sync-candidates.tsso it can be exercised directly by integration tests; 7 new tests cover the manual-only path (book selected with no progress; sync log diff vs. effective status; manual overrides progress-derived status in change detection; per-api-key scoping).
0.28.0
Minor Changes
19f7b77: Manual reading status override on the book detail page.
API:
reading_aggregategains five sticky-override columns (manual_status,manual_started_at,manual_finished_at,manual_paused_at,manual_set_at). Whenmanual_statusis set it wins over the computed status everywhere —GET /api/library/{id}(now also returnsprogress),GET /api/library/sync,GET /api/reading-status/counts, andGET /api/reading-status/{status}— until the user clears it. Underlyingreading_progresshistory and the autostarted_at/finished_atderivation from KoSync are untouched, so the override is reversible.Two new endpoints:
PATCH /api/library/{id}/reading-status— body{ status, startedAt?, finishedAt?, pausedAt? }. Per-status field policy:unreadclears all dates,readingkeepsstarted_at,finishedkeepsstarted_at+finished_at,pausedkeepsstarted_at+paused_at. Rejects future dates andfinishedAt < startedAt.DELETE /api/library/{id}/reading-status— clears the override; subsequent reads fall back to the computed status from KoSync.
The
ProgressAggregateschema gainspausedAt(manual-only) andmanuallySet: booleanso clients can render a Clear-override affordance.Web: New "Edit reading status" action on the book detail page action menu opens a modal with a status select and date pickers that appear conditionally per status. Pre-fills
finished_onto today andstarted_atfrom the existing aggregate. A "Clear override" button appears when an override is active. The Obsidian plugin remains read-only and consumes the effective (override-aware) status automatically via/api/library/sync.
Patch Changes
e05296d: Migrate the monorepo to Vite+ as the unified toolchain.
- Drop Turborepo. The root
vite.config.tsand per-packagevite.config.tsfiles now hold allfmt/lint/pack/testconfig. Workspace orchestration runs throughvp run -r <task>(with-Ffilters replacing--filter). - Drop standalone
oxlint,oxfmt, andoxlint-tsgolintdev deps —vp lintandvp fmtship the same binaries. Per-package.oxlintrc.jsonfiles are removed (rules now live in the rootvite.config.tslintblock). services/api-honobuilds viavp pack(still tsdown under the hood) instead of invokingtsdowndirectly.tsdown.config.tscontent is inlined into thepackblock ofservices/api-hono/vite.config.ts.apps/obsidian-pluginkeepsesbuildfor the production build (Obsidian-specific output) but usesvpfortest/lint/fmt/check.apps/webbranches its plugin set onprocess.env.VITESTinsidevite.config.tsso component tests renderUBadgeetc. as plain elements (matches the pre-migration test environment); the main path carries the full plugin set for dev/build. (Test-only config previously lived in a separatevitest.config.ts— removed in the follow-up cleanup.)- All
vitest/vitest/configimports rewritten tovite-plus/test. Catalogviteandvitestentries are aliased to@voidzero-dev/vite-plus-coreand@voidzero-dev/vite-plus-testso transitive peer-dep resolutions still deduplicate correctly. - Lefthook hooks call
vp lint --fix/vp fmt/vp run check -rinstead ofpnpm exec. - CI: switched to the official
voidzero-dev/setup-vp@v1action (handles Node + pnpm + cache in one step, reading.node-version). Removed.turbocache steps, the manualcorepackblocks, and the now-defunctgenerate-typesstep.pnpm run Xinvocations rewritten tovp run -r X/vp run -F <pkg> X;pnpm execinvocations rewritten tovp exec.--affectedflags are dropped (no direct Vite+ equivalent yet);check/testnow run across all workspace packages every time. Docs-deploy change-detection regex updated fromturbo.jsontovite.config.ts. - Commit hooks: lefthook's pre-commit now calls
vp staged(Vite+'s native staged-file runner). The per-extension command map moved fromlefthook.ymlglob commands to astagedblock in the rootvite.config.ts. Lefthook still owns the other hooks (prepare-commit-msg, pre-push, post-merge, post-checkout) for the beads integration. - AGENTS.md / CLAUDE.md: added the canonical
<!--VITE PLUS START--> ... <!--VITE PLUS END-->block at the top so generic agents pick up the toolchain context without reading the project-specific section below. - VitePress bumped to
2.0.0-alpha.17. Adds native rolldown-vite support and switches the bundler tooxc-minify— eliminates the "plugin assigns to bundle variable" warnings and thetransformWithEsbuild is deprecatedwarning we saw on every docs build, and roughly halves docs build time (14s → 7s). Required addingoxc-minify(peer dep of vitepress 2 when running on rolldown-vite) to@libris/docsdevDeps. - Type-aware lint enabled globally — both
typeAwareandtypeCheckset totrueinlint.options(matching thevp create/vp migratedefault). Per-package--type-awareflags removed from scripts since it's now the default. Surfaced 32no-floating-promisesviolations inapps/web:onSettledcallbacks: rewrote each as() => queryCache.invalidateQueries(...)(single) or() => Promise.all([...])(multiple). Returning the promise is more idiomatic thanvoid-ing it — pinia-colada (and any other consumer) can await invalidation if it cares.apps/web/src/main.ts:bootstrap()is the app entry point. Switched fromvoid bootstrap()to top-levelawait bootstrap()(supported by ourtarget: esnextbuild).apps/obsidian-plugin/src/main.ts:workspace.revealLeaf(leaf)properlyawaited inside the already-asyncactivateSyncStatusView.
MetadataFieldPicker.types.tsextracted fromMetadataFieldPicker.vue. tsgolint can lint the<script>block of a.vuefile but can't resolve types declared inside an SFC when they're imported from a.tsfile — types meant to cross the SFC boundary must live in a regular.tsmodule. The test now importsCandidatedirectly instead of relying on theInstanceType<typeof Component>["$props"]extraction trick.- Per-package
checkscripts collapsed to a singlevp checkinvocation. Previously each package ranvp fmt --check . && vp lint && tsc --noEmitas three serial subprocesses; withtypeCheck: trueenabled,vp checkis one cohesive run that does fmt + lint + tsgolint type-check.apps/webstill chains&& vue-tsc --buildbecause tsgolint can't see inside.vueSFCs (vue-tsc covers templates and Vue component types). Workspacechecknow executes 8 tasks instead of 13. - IDE integration (
vp createadds these by default;vp migratedoesn't):.vscode/extensions.jsonnow recommendsVoidZero.vite-plus-extension-pack, and.vscode/settings.jsonsetsnpm.scriptRunner: vpand the oxc formatter as the default + format-on-save + fix-on-save action. - CI gains a
node_modules/.vite/task-cacheactions/cachestep (per check / test / build / docs job), and the cacheable steps now usevp run --cache -r <task>. Verified locally: 11/13 cache hits and ~74s saved on a warm second run ofvp run --cache -r check. Tasks that mutate their own inputs (e.g.@libris/docs#check,@libris/api-hono#build:spec) are correctly identified as non-cacheable. fmt.sortPackageJson: trueadded to rootvite.config.ts. One-time reformat applied to everypackage.jsonin the workspace.lefthook.ymlnow carries an explanatory header noting why we don't runvp config(we already use lefthook for the broader hook lanes — pre-push, post-merge, post-checkout, prepare-commit-msg — which integrate beads).
Validation:
vp install,vp run -r check,vp run -r test,vp run -r buildall green locally.- Drop Turborepo. The root
e05296d: Finish the Vite+ migration: drop legacy script/task patterns and wire up the workspace task graph properly.
apps/web— collapsedvitest.config.tsinto thetestblock ofvite.config.ts(per Vite+ guidance: "We do not recommend usingvitest.config.tswith Vite+"). Plugin set is now branched onprocess.env.VITESTsoshallowMountkeeps seeing raw.u-badgeHTML instead of stubbed<u-badge-stub>components. Replaced thenpm-run-all2build (run-p type-check "build-only {@}") with a Vite Task graph:type-check(vue-tsc),build(vp build, dependsOn type-check),check(vp check, dependsOn type-check). Cross-packagedependsOn: ['@libris/api-hono#generate:version']declared ontype-checkso standalonevp run -F @libris/web buildtriggers the upstream codegen — workspace dep ordering only kicks in for-r. Excludeddist/,node_modules/.nuxt-ui/, and the auto-generated.d.tsfiles from the input fingerprint to stop self-poisoning the cache. Droppednpm-run-all2from the catalog.services/api-hono— replaced thepre*+pnpm runhook chains withvite.config.tstasks anddependsOn. Five scripts (predev,prebuild,prebuild:spec,precheck,pretest) all forwarded topnpm run generate:version, andbuilddidpnpm run build:spec && vp pack && cp -r migrations dist/migrations.pnpm runinside a script bypasses the Vite Task graph (separate process, no cache, no inlining). Now:generate:version,build:spec,build,check,test,dev,db:reset,db:studio,reset:bullmqare all tasks with properdependsOnandcachesettings. InlineGOMEMLIMIT=1GiB GOGC=50kept inside thechecktask's command (cleaner thanuntrackedEnvfor a fixed knob —untrackedEnvonly passes through values the caller has set). Input filters added to exclude self-written outputs (src/generated/,openapi.json,dist/).docs— added input filters to the vitepress tasks:.vitepress/dist/,.vitepress/cache/,.vitepress/.temp/,node_modules/.vite-temp/. With these excluded, the docs build is now 100% cache-hit on warm runs (~9s saved per invocation).Root
package.json+vite.config.ts— dropped ~15 thin alias scripts (dev:server,dev:web,dev:docs,build:docs,test:api,db:reset,db:studio,reset:bullmq,docs:generate:db, …) that were purevp run -Fwrappers. Promoted the three CI-style aggregators (check,test,build) and the two shell wrappers (bruno:import,test:e2e:docker) into root tasks invite.config.ts. Per-package work is now invoked directly viavp run -F @libris/<pkg> <task>. UpdatedREADME.md,docs/contributing.md,docs/database.md,docs/testing.md,docs/ci-cd.md,.github/pull_request_template.md, andAGENTS.md(CLAUDE.md is a symlink) to reflect the new invocation surface.Dockerfile — migrated to install
vite-plusglobally and usevp run -F @libris/<pkg> buildfor both stages. The previouspnpm run build/pnpm run generate:versioninvocations stopped working when those moved from package.json scripts to vite.config.ts tasks. Build-web stage now copiesservices/api-hono/vite.config.ts+scripts/so the cross-packagedependsOnresolves..forgejo/workflows/publish-images.yml— version job migrated from rawcorepack/pnpm install --frozen-lockfile/pnpm changeset versiontosetup-vp@v1+vp install --frozen-lockfile+vp exec changeset version, matching the rest of CI.Validation:
vp run check,vp run -F @libris/web test,vp run -F @libris/api-hono test,vp run -F @libris/obsidian-plugin test,vp run -F @libris/api-hono build,vp run -F @libris/web build,vp run -F @libris/docs buildall green. Recursivevp run checkwarm cache: 5/7 hits, 61s saved. Docker image build couldn't be validated locally (Docker not present in WSL) — flag for human verification on next image publish.
0.27.0
Minor Changes
c58d4f3: Add
GET /api/library/sync— a paginated bulk endpoint optimised for full-vault mirror clients (Obsidian plugin, future CLIs). EachBookSyncRecordbundles theBookSummaryfields plus the per-book progress aggregate (MAX(percentage)across devices + derived reading status), so a sync run is one paginated drain instead of N×3 requests against the catalog. Optional?since=<ISO>filters to records whose metadata or progress changed after that timestamp; the response'sserverTimeis the cursor for the next incremental run. Auth is the same bearer-token gate as the rest of/api/*. New schemas:ProgressAggregate,BookSyncRecord,BookSyncResponse.c58d4f3: Plugin bases pass + lifecycle dates from server.
API:
GET /api/library/syncnow returnsprogress.startedAtandprogress.finishedAtper book — read from the newreading_aggregatetable introduced in the previous changeset. Both fields are nullable ISO 8601 strings.Plugin: pre-release, breaking changes are accepted.
- Per-book frontmatter gains
started_atandfinished_at(allowlisted, written from the server response). - Series and author pages migrated from markdown bullet lists to
Series/<name>.baseandAuthors/<name>.base— cards view with covers, sorted byseries_index ASC/year ASC. Reading.mdreplaced withReading.base— four cover-grid views: Currently reading, Recently finished (last 30 days), Paused, Read this year. Uses the server-trackedfinished_atfor objective filtering.Library.base,My Reading.base, andReading.baseregenerate on every sync.My Reading.basedrops its "Read this year" view (now inReading.base) and themy_read_dateproperty entry.- Empty
my_rating:placeholder seeded into newly-created book notes for discoverability. Plugin never overwrites it. - Legacy markdown relation/dashboard pages (
Reading.md,Series/*.md,Authors/*.md) are moved to the system trash on each sync, identified by theirlibris_kindfrontmatter. - New
bases-format.escapeBaseStringhelper for safely interpolating series/author names into Bases filter expressions. - Ribbon tooltips drop the "Libris:" prefix; commands "Open current book in Libris" / "Open current book's series in Libris" renamed to "Open current book in browser" / "Open current series in browser" per Obsidian directory naming guidelines.
- Per-book frontmatter gains
0.26.0
Minor Changes
7119824: Track per-(user, book) reading lifecycle timestamps server-side.
- New
reading_aggregatetable keyed by(api_key_id, book_id)with nullablestarted_atandfinished_at. - KOReader sync (
PUT /syncs/progress) now upserts the aggregate after each progress write:started_atis set on the first non-zero progress;finished_aton the first reading at or aboveFINISHED_THRESHOLD(0.95). Neither field is overwritten once set —COALESCEpreserves the prior value on conflict. - One-shot
backfill-reading-aggregatemaintenance job (enqueued once on startup) derives both timestamps fromreading_progress_historyfor every existing(api_key_id, book_id)pair, so already-finished books populate immediately. - Aggregate writes are fire-and-forget so they never block the kosync response.
The new fields are not yet surfaced through any read API — that's a follow-up bundled with the Obsidian plugin update that consumes them.
- New
Patch Changes
9193223: Upgrade dependencies across the monorepo. Notable major bumps: TypeScript 5 → 6.0.3,
@hono/node-server1 → 2,@loglayer/transport-pretty-terminal5 → 6,@unhead/vue2 → 3,chokidar4 → 5,lefthook1 → 2,@electric-sql/pglite0.3 → 0.4.Two breakages fixed:
@loglayer/transport-pretty-terminalv6 makesdatabaserequired and no longer bundlesbetter-sqlite3. The dev logger now dynamically importsbetter-sqlite3alongside the transport and passes an in-memory DB; both stay out of the production bundle.lefthookv2 refuseslefthook installwhencore.hooksPathis set locally. The rootpreparescript now useslefthook install --forcesopnpm installworks on any clone.
Also bumps the
@opentelemetry/api-logspnpm override from0.214.0→0.215.0to match the upgraded@opentelemetry/sdk-logs/sdk-nodeand avoid a duplicated logger singleton.Sets
NO_COLOR=1on everybd hooks runcommand inlefthook.yml.bd1.0.2 emits OSC 11 and DA terminal-detection queries on stdout and exits before reading the responses, so with lefthook v2's verbose post-checkout output the unread responses leak into the interactive shell (visible as stray^[]11;rgb:...^G^[[?62;22;52cgarbage at the prompt aftergit switch).NO_COLOR=1makes bd skip the detection entirely; the env blocks can be removed once a fixed bd release ships.Bumps the CI
mcr.microsoft.com/playwrightcontainer image fromv1.58.2-jammy→v1.59.1-jammyin both the PR smoke-test and main full-suite E2E jobs so the bundled Playwright binary matches the upgraded@playwright/testcatalog pin (1.59.1).
0.25.0
Minor Changes
b8f7d26: BREAKING: drop
DATABASE_URLandREDIS_URLenv vars. The app now assembles both connection URLs from split vars, givingdocker-compose.dev.ymland.enva single source of truth.- Postgres:
POSTGRES_HOST,POSTGRES_PORT(default5432),POSTGRES_USER,POSTGRES_PASSWORD,POSTGRES_DB - Redis:
REDIS_HOST,REDIS_PORT(default6379),REDIS_USER(optional),REDIS_PASSWORD(optional),REDIS_TLS=1forrediss://
The assembly lives in
services/api-hono/src/lib/resolve-database-url.tsandresolve-redis-url.ts.docker-compose.dev.ymlreads the same split vars via${VAR:-default}interpolation, so editing.envis reflected on both sides.Migration: replace any
DATABASE_URL=postgresql://user:pass@host:5432/dbin your.env, deployment config, or CI secrets with the fivePOSTGRES_*vars; same forREDIS_URL→REDIS_*. See.env.examplefor the dev template anddocs/deployment.mdfor prod.- Postgres:
Patch Changes
b8f7d26: CI: cap tsgolint memory and raise vitest hook timeout
services/api-honocheckscript now setsGOMEMLIMIT=1GiB GOGC=50when invokingoxlint --type-aware. Oxlint's type-aware pass shells out to thetsgolintGo binary, which was getting SIGKILL'd by the OOM killer on the Forgejo docker runner. The soft memory limit makes Go GC more aggressively so the process stays under the container's memory ceiling.services/api-hono/vitest.config.tsaddshookTimeout: 60_000. The CI runner is ~3x slower than local; PGlite spin-up + migrations inbeforeAll/beforeEachwas exceeding the 10s default, causing cascading test failures (later tests saw stale state from the interrupted hook).
4c007d2: Fix publish-images build: generate
services/api-hono/src/generated/version.tsin thebuild-webDockerfile stage.Since the switch to
vue-tsc --build(which actually follows workspace imports),apps/web's type-check chases@libris/api-hono/client→services/api-hono/src/routes/index.ts→../generated/version.js. That file is produced by thegenerate:versionscript at api-hono build time and is gitignored, so the isolatedbuild-webstage had nothing to resolve to (TS2307).Fix: the
build-webstage now also copiesservices/api-hono/scripts/and runspnpm --filter @libris/api-hono run generate:versionbefore the web build, producing the version module next to the sources the stage already has. Stays independent ofbuild-apiso the two stages can still build in parallel.9da0f5d: fix: return cover of the first volume in a series
6aa46fe: Scan existing inbox files on startup
The inbox watcher now processes files present at startup instead of only watching for new additions. Files dropped into the inbox while the server is offline are picked up on the next boot; the
book-detectedworker dedupes by SHA-256 checksum so already-ingested files are cheap no-ops.b8f7d26: Declare
services/api-hono/src/generated/**as a turbo output of bothbuild:specandbuild.apps/webtransitively importsservices/api-hono/src/routes/index.tsvia@libris/api-hono/client+/types, which imports../generated/version.js(written by thegenerate:versionhook that runs before each ofprebuild,prebuild:spec,precheck,pretest). On a CI runner, turbo restores cached tasks by replacing only the declared outputs — so whenbuild:specorbuildwas a cache hit, the siblingversion.tswas left missing and downstreamvue-tsc --buildinapps/webfailed with TS2307 (seen in CI runs 529 →checkand 530 →build).Adding
src/generated/**to both task outputs captures the generated version module alongside each task's primary artifacts, so downstream packages resolve it whether the upstream api-hono task ran fresh or was a cache hit.
0.24.1
Patch Changes
79dc86f: Avoid noisy 404 cover requests for inbox items with no cover source
- Inbox list page now only requests cover extraction for items with EPUB files or an HTTP coverUrl; non-EPUB items without a cover show a placeholder immediately
- Inbox list cover cells gracefully fall back to the placeholder icon when image loading fails (e.g. corrupt EPUB with no extractable cover)
- Backend cover endpoint downgrades "no cover source" log from warn to debug since it's an expected condition
0.24.0
Minor Changes
29f1d25: Rename
LIBRARY_PATHtoLIBRIS_LIBRARY_PATHandINBOX_PATHtoLIBRIS_INBOX_PATHto avoid collisions with system env vars (Nix C linker). Enable devenv native dotenv integration, add dotenv-cli for non-devenv users, fix devenv postgres database name, and remove dead env vars.Breaking: Rename in your
.env/ docker-compose / CI config:LIBRARY_PATH->LIBRIS_LIBRARY_PATHINBOX_PATH->LIBRIS_INBOX_PATH
0.23.1
Patch Changes
d9a6eef: Catalog hygiene — consolidate direct version pins into the shared
pnpm-workspace.yamlcatalog and drop stale entries.Moved to catalog (were direct pins without special reason):
apps/web:@nuxt/ui,nuxt,tailwindcss,vuedocs:vitepress,vitepress-sidebar
Kept as direct pin (intentional exact-pin):
tests/e2e:@playwright/test(1.58.2, no caret) — E2E determinism.
Removed from catalog + dependents:
consola— no longer imported by any source file since the logging stack migrated from consola to LogLayer. The remainingvi.mock("consola", ...)stub inservices/api-hono/src/lib/metadata/clients/metadata-clients.test.tswas dead code and has been deleted (closes libris-detd).@types/bcryptjs— deprecated upstream; types are now bundled withbcryptjsitself.
No functional changes — resolved versions are unchanged after the lockfile refresh (verified via
pnpm listin each workspace).e3e9196: Update
drizzle-ormanddrizzle-kitfrom1.0.0-beta.18-7eb39f0to1.0.0-beta.21.Stays on the beta channel — the stable
latestdist-tag (0.45.x) would be a downgrade, since the codebase already uses v1-only APIs (defineRelations()insrc/db/relations.ts, imports fromdrizzle-orm/zod, and the new per-folder migration structure).Relevant changes between beta.18 and beta.21:
- beta.20 (2026-03-27) — security: escaping fix for
sql.identifier()andsql.as()(CWE-89, SQL injection). Libris does not use either function so we were not directly exposed, but defense-in-depth is cheap. - beta.19 (2026-03-23): SQL commenter support (
.comment({ ... }))- drizzle-kit bug fixes around migration schema handling, index operator classes, and RDS Data API schema pulling.
- beta.21 (2026-04-14): PostgreSQL enum migrations are no longer treated as commutative, and enum values added in different leaves now merge properly during migration generation.
- beta.20 (2026-03-27) — security: escaping fix for
0.23.0
Minor Changes
346a032: Rebuild the stats page with Apache ECharts and expand it to seven charts.
The reading-stats page previously shipped two hand-rolled DIV-based charts with inline-style height percentages and CSS flex tricks. They worked but had no axes, no real tooltips, no responsive redraw, and were fragile to extend. Replaced with Apache ECharts (Apache-2.0) via
nuxt-echarts+vue-echarts, and the page now tells a richer reading story:- Yearly pages-read heatmap (GitHub-contribution style, calendar year + year picker) — replaces the old 30-day bar.
- Genre distribution as a donut — replaces the stacked CSS bar.
- Books finished per month (current year, 12 bars).
- Reading velocity — 7-day moving average of pages per day over the last 90 days.
- Top 10 authors by organized-book count (horizontal bar).
- Days-to-finish distribution (fixed 6 buckets).
- Library growth — cumulative book count by month since the first book was added.
Backend:
/api/statsgainspagesHeatmap,finishedPerMonth,readingVelocity,topAuthors,daysToFinishBuckets, andlibraryGrowthfields plus an optional?year=YYYYquery param for the heatmap. The now-unuseddailyActivityfield is removed. All new aggregations are scoped per API key.Frontend: new
<StatChart>wrapper component (handles theme, autoresize, empty state, card shell) anduseChartThemecomposable (resolves an ECharts theme from Nuxt UI CSS custom properties — follows light/dark palette swaps automatically, no hardcoded hex values in chart components).Licensing note: explicitly chose Apache ECharts over ApexCharts, which moved to a dual-license freemium model (free only for orgs < $2M USD revenue; paid for commercial or OEM/redistribution). libris is OSS and self-hostable, so downstream forks and users at larger orgs benefit from the permissive license.
0.22.4
Patch Changes
- 90fac7d: Admin diagnostics now list every registered queue.
/api/jobs/status,/api/jobs/failed, and/api/settings/statuspreviously read only the ingestion pipeline queues, hiding failures and activity from thehardcover-sync,progress-history-cleanup, anddb-maintenancequeues. They now use the full queue registry so operational visibility matches reality. The home dashboard's ingestion pipeline indicator is unchanged. - 78cf951: The scheduled
cleanup-orphaned-filesmaintenance job now uses keyset pagination overbook_files.idinstead ofOFFSET. The previous loop deleted rows from the same table it was paginating through, shifting later rows and silently skipping eligible orphans on each pass. With keyset pagination, a single nightly run processes every row whosestorage_pathno longer exists on disk. The handler has also been extracted frombootstrap.tsinto its own worker module with regression test coverage. - 3c8b15b: Remove unsupported in-memory path overrides from the settings API.
PATCH /api/settingsno longer acceptslibraryPathorinboxPath— these always come from theLIBRARY_PATHandINBOX_PATHenvironment variables. The previous override was half-broken: it was visible to API read handlers but the inbox watcher andbook-organizeworker still read the env vars directly, leaving the system in a split-brain state when overrides were applied. The settings UI was already display-only, so user-facing behavior is unchanged.PATCH /api/settingsnow only accepts thehardcoverMetadataEnabledandhardcoverSyncEnabledflags; the response body shrinks to{ updated: string[] }.
0.22.3
Patch Changes
- f36f496: Show uploader attribution in library and inbox views, add library language and uploader filtering, and replace the crowded library header filters with a consolidated filter panel.
0.22.2
Patch Changes
- cde31d0: Avoid ambiguous external metadata lookups for corrupt or metadata-empty EPUBs by moving them into manual review, and show a clearer review message when no metadata sources are available.
- c74393b: Add a BullMQ reset utility and use it in local and E2E reset flows so queue diagnostics reflect a truly fresh dev state.
0.22.1
Patch Changes
- 664e492: Fix OpenTelemetry log export by aligning @opentelemetry/api-logs to a single version. The loglayer transport and SDK were using different versions, creating separate global registries so the SDK's LoggerProvider never reached the transport.
0.22.0
Minor Changes
- f8fbb02: Add OpenTelemetry SDK initialization. Set
OTEL_EXPORTER_OTLP_ENDPOINTto enable log and trace export via OTLP. Uses standard OTel environment variables -- no custom configuration needed. When unset, the SDK does not initialize and adds zero overhead.
0.21.1
Patch Changes
- 0652683: Fix production crash by excluding @loglayer/transport-pretty-terminal from bundle. The dev-only transport transitively imports better-sqlite3 (native module) which doesn't exist in the Docker runtime. Switched to dynamic import so it's only loaded in development.
0.21.0
Minor Changes
c6ff439: Replace consola with LogLayer for structured logging
- Dev: @loglayer/transport-pretty-terminal with moonlight theme
- Prod: @loglayer/transport-pino for JSON output
- OTel: opt-in via OTEL_ENABLED=1 with @loglayer/transport-opentelemetry and @loglayer/plugin-opentelemetry for trace_id/span_id correlation
- New env vars: LOG_LEVEL (trace|debug|info|warn|error|fatal), OTEL_ENABLED (0|1)
- Per-request child loggers with requestId and IP context
- All 22 source files migrated from consola.withTag() to getLogger()
0.20.1
Patch Changes
- 1df5cc2: Fix EPUB cover extraction for SVG-wrapped covers (Standard Ebooks)
- 6172de2: Fix Hardcover sync rate limit retry budget leaking across books
- 00ae7d1: Fix non-deterministic upload ownership for duplicate checksums
- f5f9867: Add $onUpdate for updatedAt columns, index on hardcoverBookId, and unique constraint on series+seriesIndex
- 83ddfa5: Wrap inbox rescan in transaction for atomic candidate delete and status reset
- b958a58: Restrict settings status diagnostics to admin users only
0.20.0
Minor Changes
28c4cc6: Migrate real-time events from SSE to WebSocket
Replace Server-Sent Events (streamSSE + useEventSource) with WebSocket (@hono/node-ws + useWebSocket) for all real-time event streaming between backend and frontend.
Backend:
- Add
@hono/node-wsadapter withcreateNodeWebSocketin app.ts - Replace SSE endpoint with WebSocket handler in events.ts using
upgradeWebSocket - Add
POST /__test/emit-eventendpoint for deterministic E2E event testing
Frontend:
- Replace
useEventSourcewith VueUseuseWebSocketin useServerEvents composable - Add exponential backoff reconnect (1s-30s) and heartbeat keep-alive
- Add
runtimeConfig.public.wsBaseUrlfor dev/prod WebSocket URL configuration
E2E:
- Add
livePagefixture that allows WebSocket connections through - Add 4 new tests verifying WebSocket-driven UI reactivity (connection, inbox refresh, badge update, settings events)
- Add
0.19.0
Minor Changes
ff4d612: Add multi-user authentication with API key identity
- API keys serve as user identity; first key is admin, subsequent keys are regular users
- Admin-only: key creation/deletion, global settings, job management
- Per-user: service credentials (OPDS, KoSync, Hardcover), reading progress, stats
- Book ownership: creator can edit, others read-only, admin can edit all
- OPDS and KoSync auth resolves per-user credentials by username
- Hardcover sync worker processes all users independently
- Frontend: admin-only UI sections, user label in sidebar, conditional edit controls
ff4d612: Add upload_registry table to track book ownership through the upload-to-detection pipeline
Patch Changes
- ff4d612: Export clearAuthCaches for use by future privilege-editing routes
- ff4d612: Add unit test coverage for multi-user auth access control
- ff4d612: Clean up dead code: remove unused safeCompare, deduplicate DUMMY_HASH, remove redundant deviceFilter, fix route-policy shadowing
- ff4d612: Skip caching when apiKeyId is missing to prevent cross-user cache sharing
- ff4d612: Add missing requireBookOwnership check on candidates route
- ff4d612: Fix Hardcover sync to retry books when edition pages unavailable and normalize Date usage
- ff4d612: Default isAdmin to false in auth middleware to prevent undefined at runtime
- ff4d612: Swap KoSync bcrypt comparison order to try MD5 first (common case)
- ff4d612: Prevent deletion of the last admin API key
- ff4d612: Fix service_credentials schema to use uniqueIndex instead of index
- ff4d612: Add multi-user integration and E2E test suite with dual-user fixtures
- ff4d612: Add test coverage for upload registry flow: checksum computation unit tests, upload route registry insert integration tests, and book-detected worker ownership attribution tests
- ff4d612: Add composite unique constraint on (checksum, api_key_id) to upload_registry table, replacing the non-unique index. Use onConflictDoNothing on insert to prevent duplicate rows from concurrent uploads.
0.18.2
Patch Changes
- dd3f003: Scope CSP header to API routes only so Nuxt SPA inline scripts are not blocked
0.18.1
Patch Changes
- ac06294: Fix auth middleware blocking SPA static file routes when served by Hono
0.18.0
Minor Changes
- 0633ad1: Serve SPA from Hono backend in production, consolidating two Docker images into one. Removes CORS, config.json runtime injection, nginx, and separate web Dockerfile. Adds static file serving with SPA fallback to Hono. CI/CD publishes a single unified image with composite version tag.
0.17.1
Patch Changes
- b44c3d3: Normalize OpenAPI tag casing for reading-status routes from 'Reading Status' to 'reading-status' for consistency with all other tags
- c6e752f: Add OpenAPI documentation to all OPDS catalog routes so they appear in the Scalar API docs at /_docs/scalar
0.17.0
Minor Changes
- 234cad0: Add job detail view, all-jobs browser, job logs via BullMQ job.log(), and queue management actions (pause/resume/drain/clean)
- b68c9bb: Consolidate Redis connections from ~20 to ~9 by sharing ioredis instances across BullMQ queues, replacing unstorage, and deduplicating queue instances
0.16.1
Patch Changes
- d34297c: Remove overly strict Redis TLS requirement in production env validation
0.16.0
Minor Changes
- 5d8f42b: Add OpenAPI documentation to inbox cover, inbox upload, library cover, and library download routes
Patch Changes
- 241b97e: Fix fallback DATABASE_URL in drizzle.config.ts to match devenv database name
- 22ba82e: Remove duplicate API test step from CI workflow
- bacaa4e: Remove unbounded .returning() from progress history cleanup DELETE
- 5a63107: Update tsdown build target from node22 to node24 to match runtime
- 953c40b: Escape ILIKE wildcard characters in series search query to prevent
%or_from matching all series - 96ba0c0: Fix parseRedisUrl to pass tls: true when the URL scheme is rediss://, preventing silent TLS downgrade in production
- 7c2e560: Replace sql.raw() with parameterized interval in reading-status queries
- 8ca620d: Remove redundant double type assertion in hardcover-sync worker
- 0c400ea: Fix fallbackExtract resolving promise twice in epub.ts metadata extractor
- d3fb616: Fix infinite retry loop on Hardcover API rate limits by adding a maximum retry count of 5 to all rate-limit retry logic in hardcover-sync and matching workers
- 040d7c5: Add onConflictDoUpdate to book-parse-file insert for retry safety
0.15.3
Patch Changes
- 2c20a70: Fix Docker builds and release pipeline: remove stale root tsconfig.json COPY from both Dockerfiles, restructure publish workflow so images are built before the version bump is committed to main.
0.15.2
Patch Changes
- 519bca5: Fix Docker build: remove stale
COPY tsconfig.jsonthat referenced a deleted root-level file. The service-level tsconfig is already included viaCOPY services/api-hono/.
0.15.1
Patch Changes
66c4b08: Security hardening for open-source release:
- Fix SQL injection via string interpolation in test DB setup (use parameterized query)
- Enforce
CORS_ORIGIN != '*'in production at startup (was warn-only, now throws) - Enforce
REDIS_URLmust userediss://(TLS) in production - Add security headers to nginx: X-Content-Type-Options, X-Frame-Options, Referrer-Policy, Permissions-Policy
- Add
.env.exampletemplate for contributors - Unignore
.env.exampleand.env.test.examplein.gitignore
125b165: Quality: replace raw SQL with Drizzle (inArray/ne/desc), Hono built-in bodyLimit, response compression, stats caching, graceful shutdown drain, typed normalized jsonb column
b417375: P3 quality and security improvements:
- Add X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset headers to all rate-limited responses
- Fix OPDS Basic auth timing side-channel: always run bcrypt.compare() regardless of username match
- Add keyboard shortcuts help modal (press ? to open) listing all navigation shortcuts
- Replace z.catch() with .optional().default() in API query schemas — enables static OpenAPI spec generation
- Generate openapi.json at build time; docs now import it statically instead of relying on runtime fetch
75ea45d: Security: protect OPDS downloads with auth, fix rate limit IP spoofing, cache bcrypt validation, fix book delete path bug, fix README broken link
0.15.0
Minor Changes
- e4184da: Show series name and position on library book cards and list view, and add a "Series order" sort option
0.14.1
Patch Changes
2a67fec: DB hardening: Drizzle relations, index audit, materialize book_id on reading_progress
- Add Drizzle relations for books → bookFiles and books → bookMetadataCandidates (j7u)
- Add missing indexes: book_files.checksum, books.isbn_13, books(status, created_at) composite, reading_progress_history(document, created_at) composite, hardcover_sync_log.last_synced_at (acf)
- Remove redundant indexes superseded by unique constraints: reading_progress_doc_device_idx, hardcover_sync_log_book_id_idx, books_status_idx, reading_progress_history_doc_idx (acf)
- Materialize book_id on reading_progress and reading_progress_history, eliminating OR-join bottleneck across reading-status, stats, and hardcover-sync queries (pnx)
- Populate book_id at write time in KoSync PUT endpoint via content hash lookup
- Backfill migration for existing reading progress records
8916fd2: Complete E2E test suite optimization and data-testid audit
data-testid coverage (tasks 6xjv, 9yh5, z0v9, 81kn):
- Added testids to library/index.vue: search, view toggle, book cards, filters, pagination
- Added testids to inbox/index.vue: upload btn, search, sort, pagination
- Added testids to home page: section containers, stat value cards (de-duplicated), book cards
- Added testids to inbox/[id].vue action buttons: rescan, delete, approve
- Added testids to library/[id].vue: cover image, download button
- Fixed 4 duplicate stat-value testids in stats.vue → specific names
- Added activity-chart testid to stats.vue chart container
- Added testids to ConfirmDialog.vue and ApiError.vue
Fragile selector replacement (task 6ugb):
- Replaced
locator("section").filter({hasText})with section testids in home/stats tests - Replaced CSS class selectors (.flex.items-end) with activity-chart testid
- Replaced img[alt='...'] with book-cover-img testid in library tests
- Replaced getByRole("button", { name: ... }) with testid selectors for action buttons
Test count reduction (tasks bxqe, jts5, z0oi):
- book-progress.spec.ts: 8 tests → 3 (consolidated visibility checks into one test)
- reading-status.spec.ts: 4 identical tab tests → 1 parametrized loop
- Removed 3 redundant navigation tests (home ×2, reading-status ×1)
- Total: 99 → 91 E2E tests
OPDS tests moved (task rxoq):
- Created services/api-hono/src/routes/opds.test.ts with 7 Vitest integration tests
- OPDS Playwright tests skipped (no browser needed for HTTP API tests)
CI sharding (task upf4):
- Full suite on main now runs as 2 parallel shards (each with own DB/Redis)
- PR smoke tests remain single job
- Estimated ~50% CI wall-time reduction on main push
b674b64: Reset migrations to single init snapshot, resolving Drizzle v0→v1 migration tracking incompatibility.
19397c8: Fix metadata upsert crash on retry and switch fileSize to number mode
- Add ON CONFLICT DO UPDATE to metadata candidate insert in book-fetch-metadata worker (retries no longer crash on unique constraint violation)
- Switch bookFiles.fileSize from bigint mode:'bigint' to mode:'number' (avoids BigInt serialization overhead; ebooks are well under 2^53 bytes)
0.14.0
Minor Changes
- 6fb5245: Add series support: group books by series with browsing, metadata extraction, and OPDS feeds
- Add
seriesandseries_indexcolumns to books table with partial index and search vector - Extract series from EPUB calibre:series meta tags and Hardcover API (Typesense + GraphQL)
- Embed calibre:series/calibre:series_index in EPUB files on export
- Write series data from Hardcover matching to books table
- Add GET /api/series and GET /api/series/:name API endpoints
- Add series filter to GET /api/library and series facet to GET /api/library/facets
- Add OPDS series navigation and acquisition feeds
- Add series listing page, series detail page, and sidebar navigation
- Add series/seriesIndex to MetadataFieldPicker, EditBookModal, and book detail page
- Add series filter dropdown to library page
- Add
Patch Changes
- bd1a814: Fix series UX and dev infrastructure
- Combine series name and position into a single grouped field in MetadataFieldPicker
- Add Nitro devProxy so API requests go through same origin in dev (fixes SSE/cookie issues)
- Use relative URLs for EventSource (SSE) connection
- Consolidate migrations into single drizzle-kit compatible migration with snapshot
0.13.1
Patch Changes
ed90947: Fix Hardcover sync to use edition page count instead of local page_count
The sync worker now fetches the page count from the matched Hardcover edition instead of using the local page_count (which may come from EPUB metadata for a different edition). Also adds a backfill step that updates local page_count from Hardcover edition data for all already-matched books.
0.13.0
Minor Changes
- 7b2b14d: Drop support for non-EPUB formats (PDF, MOBI, AZW3, CBZ, CBR)
- 7b2b14d: Remove Google Books metadata source, making Hardcover the sole external metadata provider
Patch Changes
- 7b2b14d: Auto-populate page_count from Hardcover edition during ISBN matching
0.12.2
Patch Changes
- 8c57241: Remove unsupported progress field from Hardcover API calls, only send progress_pages
0.12.1
Patch Changes
2470ae4: Fix orphaned files cleanup deleting all book_files records
The cleanup job used relative storagePath without prepending LIBRARY_PATH, causing every lstat check to fail. Added rebuild-book-files startup job to recover.
0.12.0
Minor Changes
- 08c02b2: Fix KoSync progress matching and improve Hardcover sync
- Use KoReader-compatible partial MD5 for content hashing (fixes progress not showing on dashboard)
- Store original content hash before EPUB metadata embedding for dual-hash matching
- Add startup backfill job to recompute existing content hashes
- Prevent Hardcover sync from overwriting existing statuses with "Want to Read"
- Send percentage and started_at to Hardcover API (not just progress_pages)
- Send reading progress for all statuses, not only "currently reading"
0.11.4
Patch Changes
- 4116e04: retrigger deploy
0.11.3
Patch Changes
db1eae3: Fix OPDS pagination returning stale cached data for all pages
unstorage's
normalizeKeystrips everything after?in keys, soroutes:/opds/books?page=1androutes:/opds/books?page=2resolved to the same cache key. Encode query params as a path segment instead.
0.11.2
Patch Changes
44be8f1: Fix OPDS feed links using http:// instead of https:// behind reverse proxies
OPDS feeds generated
http://links for sub-pages becausegetBaseUrlread the internal Hono URL (behind TLS-terminating proxy) instead of the external URL. KOReader correctly drops Authorization headers when following links that downgrade from HTTPS to HTTP, causing auth failures on all sub-page navigations (books, search, genres, etc).Now respects the
X-Forwarded-Protoheader set by Traefik/nginx to generate correcthttps://links.
0.11.1
Patch Changes
- b762556: Fix Hardcover sync failures caused by upstream GraphQL API type renames (Hasura snake_case → PascalCase)
0.11.0
Minor Changes
d308b84: Support OPDS auth via
?key=query parameter for clients like KOReader that don't send HTTP Basic auth headers29b5dc5: Simplify OPDS and KoSync authentication to username/password only
OPDS and KoSync now authenticate exclusively via service credentials (username + password) configured through the Settings UI. Removed API key fallback for OPDS,
?key=query parameter support, andKOSYNC_USERNAME/KOSYNC_PASSWORD_HASHenvironment variables.Breaking changes:
- OPDS no longer accepts API keys (Bearer, Basic with API key, or
?key=query param). Set OPDS credentials in Settings. - KoSync no longer falls back to
KOSYNC_USERNAME/KOSYNC_PASSWORD_HASHenv vars. Set KoSync credentials in Settings.
- OPDS no longer accepts API keys (Bearer, Basic with API key, or
0.10.0
Minor Changes
- c30505a: Fix KoSync authentication for KOReader: store
bcrypt(md5(password))since KOReader sends MD5-hashed passwords, and returnuserkeyin auth responses for KOReader compatibility
Patch Changes
- 24a7b3a: Fix Bruno import: use lightweight OpenAPI-only server that doesn't require DB/Redis
0.9.4
Patch Changes
- 1ff0518: Add temporary debug logging to KoSync and OPDS auth paths to diagnose KOReader authentication failures
0.9.3
Patch Changes
- 73aa619: Fix OPDS auth for HTTP clients (KOReader): return
WWW-Authenticate: Basicheader on 401 responses so clients know to send credentials
0.9.2
Patch Changes
- 00e7cdf: Fix KoSync login from KOReader: add GET handler for
/kosync/users/auththat reads credentials fromx-auth-user/x-auth-keyheaders
0.9.1
Patch Changes
7a7fefe: fix(api-hono): add filename heuristic fallback for EPUB cover extraction and embedding
EPUB cover extraction now falls back to scanning ZIP entries for files named cover.{jpg,jpeg,png,webp,gif} when the OPF metadata doesn't declare a cover via standard EPUB2/EPUB3 patterns. This fixes cover extraction for many real-world EPUBs that have embedded covers but don't reference them in the OPF.
The metadata embedding step also uses the same heuristic — when replacing a cover during organize, it now finds and replaces the existing cover.jpeg instead of adding a duplicate cover-embedded.jpg alongside it.
0.9.0
Minor Changes
e8ef69a: fix(api-hono,web): inbox cover endpoint falls back to coverUrl proxy, frontend uses coverUrl directly
The inbox cover endpoint previously only extracted covers from EPUB files and returned 404 for everything else. Now it tries EPUB extraction first, then falls back to proxying the book's coverUrl from external metadata sources (Google Books, Hardcover). Non-EPUB books with a coverUrl now get covers too.
The frontend inbox list now uses coverUrl directly from the API response when available (instant, no extraction round-trip), falling back to the server-side endpoint for EPUB extraction or coverUrl proxy.
Added diagnostic logging throughout the cover extraction pipeline to aid debugging.
0.8.1
Patch Changes
- 45bc6a4: Fix cover image caching and EPUB metadata extraction
- Stop EPUB extractor from putting internal file paths (e.g.
images/cover.jpg) intocoverUrl. This field is reserved for external HTTP URLs from metadata sources. The organize worker already extracts EPUB covers directly viaextractEpubCoverImage(). - Add ETag headers to library and OPDS cover endpoints for proper cache revalidation when covers change (e.g. after force re-download). Supports
If-None-Matchfor 304 responses. - Change library and inbox cover
Cache-Controlfrompublictoprivatesince these endpoints require authentication.
- Stop EPUB extractor from putting internal file paths (e.g.
0.8.0
Minor Changes
- 3e8e53e: Support updating cover images on organized books
- Add
forceRedownloadCoverflag to organize worker so it re-downloads covers when the URL changes - PATCH
/api/library/:idnow acceptscoverUrland enqueues an organize job to download the new cover - apply-metadata endpoint passes
forceRedownloadCoverwhen coverUrl is in the update set - EditBookModal now includes a Cover Image URL field
- Add
0.7.0
Minor Changes
d525462: feat: aggregate settings endpoint + Pinia Colada migration
Backend: Add
GET /api/settings/statusaggregate endpoint that returns health checks, job queue status, failed jobs, app settings, and all credential statuses in a singlePromise.all()call. Reduces settings page API calls from 9 to 1.Frontend: Replace Nuxt's
useAsyncDatawith Pinia Colada'suseQuery/useQueryCacheacross all pages. Add@pinia/nuxtand@pinia/colada-nuxtmodules. Create reusable query composables incomposables/queries/. Add skeleton loaders to the settings Connections tab.Migrated pages: settings, dashboard, library list, library detail, inbox list, inbox detail, reading status, stats, and layout sidebar.
0.6.0
Minor Changes
34aa0ea: Add independent toggles for Hardcover metadata fetching and reading progress sync
Users can now control Hardcover features independently from the Settings > Connections page:
- "Use as metadata source" — enable/disable Hardcover as a book metadata provider during ingestion
- "Sync reading progress" — enable/disable pushing reading status and progress to Hardcover
Both default to enabled when a Hardcover token is configured (backward compatible). Settings are persisted in a new
app_settingsdatabase table and survive server restarts.
Patch Changes
678ec8c: Fix OpenAPI spec generation failing on z.catch() schemas
Add explicit
.openapi({ type })metadata to all Zod schemas using.catch()so@hono/zod-openapican serialize them to the OpenAPI spec. Previously, the/_docs/openapi.jsonendpoint returned an "Unknown zod object type" error.
0.5.3
Patch Changes
- 72f3a5b: Align Hardcover and Google Books metadata search strategy: both now validate ISBN check digits and fall back to title+author when ISBN is invalid
- 6c51b40: Fix Google Books metadata test assertions to match ISBN-only search behavior
0.5.2
Patch Changes
c19d555: Fix Google Books search returning no results when ISBN is available
Two bugs in query construction: (1) ISBN was combined with
intitle:andinauthor:qualifiers, making queries too restrictive since Google's metadata often doesn't match exactly. Now uses ISBN alone when available. (2) Field qualifiers were joined with+which gets double-encoded by ofetch's URL encoding — switched to space separator.
0.5.1
Patch Changes
7f7da5f: Fix Docker build: use existing
nodeuser (UID 1000) instead of creatingappusernode:24-slimalready has anodeuser at UID 1000, causinguseraddto fail with "UID is not unique".
0.5.0
Minor Changes
95b0120: Complete Nitro-to-Hono cutover with SPA pivot.
API (
@libris/api-hono):- Export typed
hcclient via@libris/api-hono/client - Add cookie-based auth endpoints (login/logout/session) for SPA frontend
- Auth middleware supports httpOnly cookie fallback alongside Bearer tokens
- CORS supports credentials for cross-origin cookie auth
- Add
COOKIE_DOMAINenv var for subdomain cookie scoping - Fix
z.unknown()in approve endpoint — now validates value types - Add missing DB indexes (books.status, reading_progress.device, hardcover_sync_log.last_status)
- Add FK constraint on books.possibleDuplicateOf
- Configure DB connection pool (max 20, idle timeout, max lifetime) and graceful shutdown
- Await job scheduler registration on startup (no more silent failures)
- Batch orphaned file cleanup to avoid OOM on large datasets
- Delete DB record before file cleanup in book delete (prevents orphaned files)
- Sanitize filesystem paths from error messages
- Add error handling to fire-and-forget event publishing
- Standardize reading-status pagination response shape
- Read OpenAPI version from package.json
- Fix serviceCredentials.updatedAt to NOT NULL
Web (
@libris/web):- Switch to SPA mode (
ssr: false), drop Pinia - Delete entire BFF server layer (proxy, session, CSRF)
- Use
hctyped client directly in all pages viauseApiClient() - Replace
useAuthStorewithuseAuth()composable (useState-based) - Add
useUpload()composable for XHR file upload with progress - All pages use
useAsyncData+hc— fully typed API calls - Dockerfile serves static files via nginx (no Node.js runtime)
- Add search debounce (300ms) to library and inbox
- Add lazy loading to cover images in grid views
- Fix SSE race condition on component unmount
- Extract shared pagination constant
Removed:
services/api/(old Nitro backend),Dockerfile.api, BFF server, Pinia- Export typed
Patch Changes
dd9a3b6: Fix audit regressions from Hono migration
- Add
removeOnCompletewith TTL andremoveOnFailto all BullMQ pipeline queues - Configure
lockDuration,stalledInterval, andmaxStalledCounton all workers - Add
retryStrategyandreconnectOnErrorto event bus Redis connections - Add foreign key constraint on
books.possibleDuplicateOf(self-reference withON DELETE SET NULL) - Exclude
searchVectorfrom all book queries and response schemas (never sent to clients)
- Add
6dea8ff: fix(e2e): resolve auth template reactivity, CORS, and approve error handling
- Destructure
useAuth()soisAuthenticatedis a top-level ref that Vue auto-unwraps in templates (was a nested ComputedRef in a plain object, always truthy) - Add
CORS_ORIGINto CI and test-e2e.sh so the SPA (port 3100) can make credentialed requests to the API (port 3000) - Check
res.okin inbox approve/delete handlers — honohcclient doesn't throw on 4xx/5xx - Fix E2E assertions for cover/download URLs that now include the full API base URL in SPA mode
- Add
dist/to api-hono.gitignoreso oxfmt skips bundled output
- Destructure
0.3.0
Minor Changes
- 131dd0c: Fix Docker image: correct ESM entrypoint (.mjs), migrations path, and skip lifecycle scripts during build.