refactor: centralize duplicate code into shared utilities and services#543
refactor: centralize duplicate code into shared utilities and services#543electather wants to merge 8 commits into
Conversation
…tems Add the @ent-mcp/shared/library subpath (lens/watched/quality enums, facet and collection types, lens/collections query schemas), an additive optional collection field on the plugin-sdk media item, collection membership on the shared CanonicalMetadata type, CompactMediaItem.section for grouped lenses, and the library lens source ids. Includes the library backend design doc.
Map a movie's belongs_to_collection into the canonical metadata: tmdb mapMovie emits a stringified collection (TV maps to null), the catalog mapper persists collectionId/collectionName, and writeMetadata keeps them on a conflict update so a re-fetch refreshes franchise data. Adds the two canonical_metadata columns.
Add the library-owned library_items (composite (user_id, id) primary key so the same title can be owned by many users) and user_library_seed tables, the 0004 migration (also adding the canonical_metadata collection columns), and the server-mod-library / -internal / server-schema-library fallow zones plus the client-feat-library allows for the shared media and virtualized grid.
…ets, collections New library module: membership sync from collection@v1 (tombstone, no-resurrect, no wipe on a partial/empty feed), denormalized hydrate from catalog + availability + progress, the az/timeline/server/quality lens sources wired into the unified media REGISTRY, and the /api/library facets + collections endpoints. Adds MediaService.getCollectionFeed and getAvailabilityQuality. Registers the library.sync (6h) and library.hydrate (hourly) jobs. Tests cover sync diff, keyset stability, json_each fan-out, facets, hydrate, and multi-user ownership.
Replace the mock library data layer with the real endpoints (look preserved): the four item lenses reuse the shared media-rows infinite query against /api/media/sources/library-<lens>, collections and facets via /api/library. Section headers insert on group-key change over the flat stream (A-Z letter, timeline decade, server/quality section), rows key by id+section, infinite scroll via the shared virtual grid. Quality chips render from item tags; A-Z and decade rails are facet-driven. Drops the fixtures, fetch-all stub, and client-side grouping/filtering.
Code Review — PR #543: centralize duplicate codeDesign-doc syncSKIPPED — pure internal refactor, no public API or end-user behaviour changes. No design-doc link required. Fallow baselinePASSES — Correctness: crypto rename is right, but "byte-for-byte" claim is inaccurateThe PR description says the removed
if (!iv || !data) return null;
const plain = await decrypt(`${iv}:${data}`, env.ENCRYPTION_KEY);
try { return JSON.parse(plain); } catch { return plain; }
const combined = joinCiphertext(iv, data); // same null-guard, same concat
if (!combined) return null;
const plain = await decrypt(combined, env.ENCRYPTION_KEY);
try { return JSON.parse(plain); } catch { return plain; }Functionally identical — same null-guard, same Note also: Minor issues (non-blocking)
if (meta.year != null && meta.year > new Date().getUTCFullYear())Called during enrichment, not a hot path, but a module-level
return row as PluginRow;Pre-existing pattern from No unit tests for new shared helpers. What's clean
Approved. No blocking issues. |
Summary
Centralizes genuinely-duplicated code into single shared helpers/services, removing 22 baselined
fallowclone groups. Pure internal refactor — behaviour is preserved exactly at every call site; no public API or end-user behaviour changes.This came out of analysing the
.fallowbaselines and inlinefallow-ignoremarkers. Of the 161 baselined duplicate clone groups, only the genuine duplications — where the copies share one intent and would drift into bugs if edited independently — were centralized. The remaining ~136 are coincidental shape matches (distinct zod schemas / TS types), repeated data (theme palettes), or trivial fragments; forcing those into shared abstractions would couple unrelated domains and hurt maintainability, so they were deliberately left.What was centralized (14 clusters / 22 sites)
notifications/internal/deliver-handler.ts→recordDeliveryFailuremedia/repo/internal.ts→selectRowByKeymedia/internal/facets.ts→buildFacetscrypto/helpers.tsencryptJson/decryptFieldconnections/auth.tshelperconnections/auth.tshelperclient/shared/lib/query/optimistic.tsmcp/composite-tools/_shared.ts→buildAvailabilityMapMediaService.aggregateFeeddb/queries.ts→markConnectionExhaustednotifications/repo/inbox.tsmedia/repo/reads.tshelperplugin-runtime/internal/shared-credentials.tsThe crypto cluster also removed a hidden duplicate:
connections/helpers.ts's localdecryptJsonwas byte-for-bytecrypto/helpers.ts'sdecryptField. Both local copies are gone and callers now import the canonical crypto helpers directly (no re-export shim, per the shared-package rules). This clears the onlyduplicate_exportsthe work would otherwise have introduced.Deliberately deferred (out of scope for a dedup PR)
media-item-fieldsdefaults (catalog↔preferences): the only legal shared home sits on the catalog↔preferences cycle. Todaycatalog → preferencesis a compile-erased type-only edge; adding a runtime value-import would turn it into a real runtime cycle and a newcircular-dependenciesviolation. It belongs with the type-relocation that fixes that cycle.sweepExpiredhelper: low-confidence generic-over-table wrapper for an 8-line delete — net-negative indirection.MediaItemFields/CandidateFeatures/RawMediaItemto@ent-mcp/shared+ invert themanual-rebuild→ catalog edge), not duplicate code. Recommended as its own PR.Heads-up: stale
.fallowbaselinesThe committed
.fallow/*-baseline.jsonfiles predate the entirelibrary/**feature (they contain zerolibraryentries and line numbers have drifted repo-wide). A wholesale regen here would absorb unrelated library tech-debt and grow the dead-code baseline with a pre-existinglibrary/internal/reads.ts ↔ service.tscycle, so it was intentionally avoided. Only the 22 clone groups this PR actually eliminated were removed fromdupes-baseline.json;dead-code-baseline.jsonandhealth-baseline.jsonare untouched (this PR adds no dead code, cycles, or duplicate-exports — verified). Recommendation: refresh all three baselines as a separate chore once the library branch lands on main.Type of change
Scope
@ent-mcp/client@ent-mcp/serverTest plan
vp checkpasses locally (format, lint, type-check — 1557 files clean)vp testpasses locally (363 files, 2674 tests)Two plugin-runtime test mocks were updated to expose the new
requirePluginRowexport; the auth concurrent-completion test now mockscrypto/helpersinstead of the removedconnections/helperscrypto copies.Checklist