Skip to content

refactor(subsonic): split routes/subsonic.ts into endpoint modules with a query layer (#243) - #247

Merged
benders merged 4 commits into
mainfrom
feature/243-split-subsonic
Jul 11, 2026
Merged

refactor(subsonic): split routes/subsonic.ts into endpoint modules with a query layer (#243)#247
benders merged 4 commits into
mainfrom
feature/243-split-subsonic

Conversation

@benders

@benders benders commented Jul 8, 2026

Copy link
Copy Markdown
Owner

Closes #243.

Pure refactor, zero behavior change: the 1,819-line hub/src/routes/subsonic.ts monolith is now endpoint-family modules with a prepared-statement query layer. Four commits, one per phase:

Commit Phase
834bc46 1 — builders: buildSong, buildAlbum, annotateStarred, annotatePlays, contentTypeForFormat, audioSourceFields, share-ID pickers + row types → routes/subsonic/{builders,types}.ts
b4d1a11 2 — query layer: 26 static prepared statements → createSubsonicQueries() in hub/src/db/queries/subsonic-queries.ts, all prepared eagerly at plugin init (extends the #130 hoisting convention). Explicit SubsonicQueries interface so statement-name typos fail typecheck. New init-time smoke test catches SQL/schema drift at boot.
9570b23 3 — module split: monolith deleted; handlers moved verbatim into index.ts (99), browsing.ts (573), search.ts (84), playlists.ts (34), annotations.ts (210), stream.ts (358), composed via a shared SubsonicRouteContext. Only external import change is server.ts.
e8672cd 4 — docs sweep: 5 docs updated to point at the new module paths.

Deliberately unchanged

  • Endpoint registration set, preHandlers, and the castToken path-gating (requireSubsonicAuthBinary) — no auth files touched.
  • getAlbumList2's dynamically-assembled SQL stays inline in browsing.ts (it can't be eagerly prepared).
  • XML-default response format (Subsonic responses default to JSON; spec says XML #164) and all response shapes.
  • BEST_SOURCE_SUBQUERY re-exported byte-for-byte even though it appears to be dead code (flagged on the issue for a follow-up — deleting dead code is out of scope for a pure refactor).

Verification

  • pnpm verify green: 55 hub + 16 frontend test files (676+ tests), typecheck clean, lint:boundary zero output.
  • pnpm lint zero findings in both hub/ and frontend/ (the root-level combined runner OOMs locally — pre-existing env quirk, unrelated; CI lints per-package).
  • Federation compat suite green: 84/84 passed against the Docker hub-a/b/c stack — the strongest proof of zero Subsonic behavior change.
  • All Subsonic behavior suites (subsonic-routes, subsonic, stream, star-routes, play-events, subsonic-internal-auth, cast-token-stream.integration) pass unmodified.

Notes

  • Acceptance criteria from refactor(subsonic): split routes/subsonic.ts into endpoint modules with a query layer #243 met: monolith deleted; largest module is browsing.ts at 573 lines (23 over the ~550 allowance noted in the issue, because getAlbumList2's dynamic SQL stays inline); no db.prepare inside request handlers in the new modules.
  • Registration order: every route is a distinct static path with its own preHandler, so the slight interleaving of registration calls (documented in index.ts) has no effect on Fastify routing.

🤖 Generated with Claude Code

https://claude.ai/code/session_011Q3riZ78G26ohMgPDh8F7u

claude added 4 commits July 8, 2026 11:14
…ilders.ts (refs #243)

Phase 1 of 4: move buildSong/buildAlbum/annotateStarred/annotatePlays,
contentTypeForFormat, audioSourceFields, and the share-ID pickers plus
their row types (TrackRow, ReleaseGroupRow) out of the subsonic route
monolith. Pure move — no logic, signature, or behavior changes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Q3riZ78G26ohMgPDh8F7u
…y (refs #243)

Phase 2 of 4: 26 prepared statements relocate from routes/subsonic.ts
into hub/src/db/queries/subsonic-queries.ts, all prepared eagerly at
plugin init (extends the #130 hoisting convention to every static
query). Dynamic-SQL queries (getAlbumList2 assembly) stay inline by
design. SQL moved byte-for-byte; explicit SubsonicQueries interface so
statement-name typos fail typecheck. New init-time smoke test catches
SQL/schema drift at boot instead of first request.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Q3riZ78G26ohMgPDh8F7u
… (refs #243)

Phase 3 of 4: routes/subsonic.ts (1,250 lines remaining) deleted;
handlers move verbatim into routes/subsonic/{index,browsing,search,
playlists,annotations,stream}.ts, each registering through a shared
SubsonicRouteContext (app, queries, route/publicRoute/binaryRoute).
Registration set, preHandlers, and the castToken path-gating are
unchanged; getAlbumList2's dynamic SQL stays inline in browsing.ts.
Only import-path change outside the new directory is server.ts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Q3riZ78G26ohMgPDh8F7u
…efs #243)

Phase 4 of 4: opensubsonic.md, opensubsonic-extensions-survey.md,
hub-internals.md, authentication.md, and pitfalls.md now point at the
endpoint-family modules (builders.ts, stream.ts, index.ts) instead of
the deleted routes/subsonic.ts monolith.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Q3riZ78G26ohMgPDh8F7u
@benders
benders marked this pull request as ready for review July 11, 2026 05:30
@benders
benders merged commit 147cddb into main Jul 11, 2026
2 checks passed
@benders
benders deleted the feature/243-split-subsonic branch July 11, 2026 05:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

refactor(subsonic): split routes/subsonic.ts into endpoint modules with a query layer

2 participants