mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 05:26:47 +00:00
414d967223
Records the fold and corrects two long-carried errors: there is no admin MCP surface (the '11 MCP consumers' figure predated the Phase-0 addons extraction) and systemNotices/conditions.ts was never a consumer. The memories/admin edge is documented in the right direction for the first time. journeyService and oauthService now head the frontier.
44 lines
17 KiB
Markdown
44 lines
17 KiB
Markdown
Recommended order
|
|
|
|
Wave 1 — pilots (establish the template on easy wins)
|
|
|
|
1. tagService (26 lines, zero deps, only consumers: TagsModule + 1 MCP tool) — the ideal first migration. Small enough that the whole pattern fits in one PR: move the SQL into TagsService with injected DatabaseService, retire the
|
|
legacy file, convert its tests to overrideProvider(DATABASE_CONNECTION).
|
|
2. categoryService (31 lines, same shape) — immediate repeat to confirm the template.
|
|
3. todoService (120 lines, classic CRUD with transactions + broadcast) — first "full-shaped" domain: prepared statements, db.transaction, WebSocket broadcast, 3 MCP tools. After this one, the recipe is settled for everything else.
|
|
|
|
The pilots deliberately include MCP consumers, because they force the one open design decision early: MCP tools run outside the Nest container, so either the migrated module keeps thin function exports delegating to a shared instance,
|
|
or the MCP layer gets a handle to the Nest app (app.get(TodoService)). Better to settle that on a 26-line service than on authService.
|
|
|
|
Wave 2 — cross-cutting leverage (parallel to wave 3, high payoff)
|
|
|
|
4. permissions (149 lines, zero deps, 17 Nest consumers) → an injectable PermissionsService (done 2026-07 — every module wrapper, the airtrail-import controller and PluginHostDepsFactory now inject it; the cache stays module-scoped so the DI singleton and permissions.bridge — kept for mcp/_shared.ts + adminService/backupService — share one invalidation; the authService and collectionsService consumers fell away with their own folds). Every module wrapper already calls checkPermission; this converts the single most-imported seam in one move.
|
|
5. auditLog (171 lines, 13 Nest consumers) → AuditService (done 2026-07 — writeAudit is the injectable; getClientIp and the log*/LOG_LEVEL logger stay plain modules in nest/audit/ (client-ip.ts, audit-log.logger.ts with its frozen-at-import level + mkdir as a documented parity exception); audit.bridge covers mcp/index, oauthProvider and the legacy airtrail/immich/oauth services).
|
|
6. tripAccess (9 lines) — don't migrate, delete: it's a wrapper around canAccessTrip, which DatabaseService now exposes. Absorb its 11 call sites opportunistically.
|
|
|
|
Wave 3 — domain services with existing modules, low fan-in
|
|
|
|
7. dayNoteService (done 2026-07), tripInviteService (done 2026-07), assignmentService (done 2026-07), shareService (done 2026-07), settingsService (done 2026-07), fileService (done 2026-07), collabService (done 2026-07), packingService (done 2026-07) — each maps 1:1 to a Nest module that's already a thin delegate; none is imported by more than one
|
|
other legacy service.
|
|
|
|
Wave 4 — the coupled cluster (order matters here)
|
|
|
|
8. dayService → budgetService → reservationService in that order: reservations depend on both (budget-item linking, day resolution), so migrating the dependencies first lets ReservationsService inject the finished versions instead of
|
|
importing legacy functions. — **Correction (migration-graph.md, borne out by the migration): the claimed ordering constraint doesn't exist at the service layer.** reservationService (done 2026-07) imported neither budgetService nor
|
|
dayService; the budget/day coupling lives in the Nest wrapper's budget-sync seam and the MCP registrars, which keep their legacy imports until those domains migrate. Reservations went first as the frontier residue fold; dayService
|
|
(done 2026-07 — the 592-line service folded into the wrapper `DaysService`, the accommodations seam in `nest/reservations/` now injects it, and the hand-rolled reorder/insert transactions became `db.transaction()`) followed;
|
|
budgetService followed (done 2026-07 — the 755-line money core folded into the wrapper `BudgetService` (settlement maths, FX freeze/rebase, items/members/payers CRUD; the raw settlement writes became `insertSettlement`/`applySettlementUpdate` so the MCP paths keep skipping the freeze); the 11-tool mcp registrar + 3 budget resources moved to the decorator-driven `budget.mcp.ts`; TripsService/ReservationsService/BookingImportService now inject `BudgetService`; a 4-export `budget.bridge.ts` serves tripService, userCleanupService and the legacy trips/transports registrars; `exchange-rates.bridge.ts` was deleted with its last consumers; and the controller adopted `budget.dto.ts` over the shared schemas, clearing all nine budget allow-list entries — **tripService is now unblocked**).
|
|
9. tripService (done 2026-07 — the 1121-line hub folded into the wrapper `TripsService`: generateDays, the updateTrip date-shift transaction, members/guests (#973/#1362), the ICS export, copyTripById and getTripSummary; its six bridge imports became injected services (CollabService/VacayService joined the constructor); the 10-tool trips registrar + 3 trip resources + the trip-summary prompt moved to the decorator-driven `trips.mcp.ts` (first `@Prompt`; the static-token deprecation notice rides the `registry.attach` ctx) and the 3 share-link tools to `share.mcp.ts` on the `canShareTrips` predicate; FeedsService and the plugin RPC host inject `TripsService`; a 3-export `trips.bridge.ts` serves the legacy prompts registrar and budget.mcp.ts's owner/member seam; todo/share/collab/vacay bridges deleted with their last consumers; the controller adopted `trips.dto.ts`, clearing all seven trip allow-list entries). placeService followed (done 2026-07 — the 1029-line place core folded into the wrapper `PlacesService` with its pure half in `places.helpers.ts`; the 10-tool registrar + the trip-places resource moved to the decorator-driven `places.mcp.ts` (`search_place` injects `MapsService`); TripsService/DaysMcp/BookingImportService/the plugin RPC host all inject it, so the domain needs **no bridge at all**; the sibling `placeEnrichment` fold absorbed the last `maps.bridge` consumer and deleted that bridge too, and the DTO ratchet cleared all seven `PlacesController` allow-list entries). transitItineraryService followed (done 2026-08 — the last step-4 link and the first pure-helpers relocation with no service fold: the 287-line module is 100% pure (no SQL, no DB, no broadcasts), so the schemas + endpoint/metadata builders moved byte-identical to `nest/transit/transit-itinerary.helpers.ts` (the schemas must stay module-level plain exports — `transit.mcp.ts` consumes them inside `@Tool` decorators, which evaluate before the container exists), its sole consumer `transit.mcp.ts` was a one-import repoint, and a new 21-case `TRANSIT-ITIN-*` characterization suite pins the previously untested superRefine error strings and builder output — **step 4 is complete**). notificationService followed (done 2026-08 — the notifications fan-in, step 3 of the dependency-honest order, deferred while step 4 ran: the `send()` dispatcher **and** the `inAppNotifications.ts` store folded together into the existing wrapper `NotificationsService` over DatabaseService + RealtimeService; the prefs matrix, smtp/webhook/ntfy transports, channel registry and `inAppNotificationActions` stay plain infra modules (their registry⇄prefs cycle included); a 1-export `notifications.bridge.ts` (`send`) covers scheduler's two cron `require`s, legacy adminService + memories/{unified,synology}, and the six deliberately-lazy fire-and-forget `import()` sends in migrated Nest services (path-only repoints); `AdminController` and the plugin RPC host (24th constructor dep) inject instead; the 5-tool registrar + the `notifications-in-app` resource moved to the decorator-driven `notifications.mcp.ts`; the DTO ratchet cleared all five `NotificationsController` allow-list entries (`testNtfyRequestSchema` gained `.nullable()` server/token — the client sends null to mean "use the saved value") — **adminService is now unblocked and heads the frontier**).
|
|
|
|
Wave 5 — the heavyweights, last
|
|
|
|
10. adminService (done 2026-08 — the last Wave-5 god file: the **"11 MCP consumers" figure below was stale**, `src/mcp/tools/admin.ts` has never existed in the repo's history and the plugin host never imported the domain either, so recipe steps 3 and 4 were both no-ops. Ahead of the fold the 11 packing-template functions moved to `PackingService`, which already owned all three template tables — resolving the `admin-2` residual with no bridge, since `packing.mcp.ts` already injects that service. The 851-line module then folded into the wrapper `AdminService` over `DatabaseService` + injected Settings/Addons/Passkey/Packing/Auth/Permissions/Notifications (the auth, notifications and permissions bridge imports all became injections; `PERMISSION_ACTIONS` stayed a plain const and the `mcp/sessionManager` deep import kept its anti-cycle comment), with the pure + module-scoped half — `compareVersions`, `utcSuffix`, the import-time `isDocker` probe and the 5-minute version cache — moving to `admin.helpers.ts`; the cache stays module-scoped so the bridge instance and the container singleton share one GitHub fetch. A 1-export `admin.bridge.ts` (`checkAndNotifyVersion`) serves `scheduler.ts`'s cron, the only out-of-container consumer. The DTO ratchet cleared all **twenty** `AdminController` allow-list entries, the largest single block, and the module e2e went DI-native (6 cases → 15 over real SQL) — **journeyService and oauthService now head the frontier**), journeyService/atlasService (done 2026-08 — pulled forward as a zero-dependency frontier member opening the Wave-5 auth chain: the 1612-line core split places-style — the DB half (stats aggregation with its two divergent return shapes, visited countries/regions with the #1490 tombstones + cascade, bucket-list CRUD) folded into the wrapper `AtlasService` over `DatabaseService` alone, while the ~750-line pure-geo half (admin0/admin1 boundary stores with the #1576 streaming builders, poly/box indexes, Nominatim geocoding with its shared throttle, the geocode/region caches incl. the import-time unref'd cleanup interval, the `geocodingInFlight` dedup set) moved verbatim to the plain module `nest/atlas/atlas-geo.ts` so the multi-MB caches stay process-global across the container + bridge instances; the 10-tool registrar `mcp/tools/atlas.ts` **plus all four atlas resources in `mcp/resources.ts`** moved to the decorator-driven `atlas.mcp.ts` (the `when:` atlas-addon gate is parity here — legacy tools/resources gated, REST deliberately doesn't; `resources.test.ts` retired with its last two cases, moved to the atlas suites); the plugin RPC host swapped its 9 atlas imports for the injected service (23rd constructor dep — journeys is the factory's last legacy domain import); a minimal 2-export `atlas.bridge.ts` served legacy `authService.getTravelStats` (the zero-bridge streak ends at five; the bridge died on schedule with the 2026-08 auth fold); the DTO ratchet cleared all three `AtlasController` allow-list entries)/vacayService (done 2026-07 — pulled forward as a zero-dependency frontier member, see migration-graph.md)/collectionsService (done 2026-08 — pulled forward as a frontier-ready member: the 1024-line saved-places core folded into the wrapper `CollectionsService` over DatabaseService + PermissionsService + RealtimeService (the `checkPermission` bridge import and `broadcastToUser` websocket import became injections; the `sendInvite` lazy notificationService `import()` stays, collab precedent; `deleteOldCollectionCover` re-anchored one directory deeper); the 25-tool registrar `mcp/tools/collections.ts` moved to the decorator-driven `collections.mcp.ts` — with NO `when:` addon gate, preserving the legacy registrar's addon-gate asymmetry — plus a new 23-case `tools-collections` characterization suite (the registrar was previously untested); the plugin RPC host swapped its 7 collections imports for the injected service (22nd constructor dep); **no bridge file anywhere**; the DTO ratchet cleared both collections allow-list entries (`reorder`, `delete-many`))/mapsService (done 2026-07 — pulled forward as step 4 of the dependency-honest order: the 1429-line geo core folded into the wrapper `MapsService`, pure helpers to `maps.helpers.ts`, the 3 geo MCP tools to the decorator-driven `maps.mcp.ts` (weather/airport tools stay in the legacy registrar), BookingImportService injects it, and a 3-export `maps.bridge.ts` serves placeEnrichment + the places registrar — transitService → placeService now unblocked)/transitService (done 2026-07 — pulled forward as step 5 of the dependency-honest order: the first fully SQL-free domain fold — the 333-line Transitous/MOTIS proxy became a dep-free `TransitService` (no `DatabaseService`); pure `deriveTransitStats` + mode whitelist + itinerary types moved to `transit.helpers.ts` so the downstream `transitItineraryService` needed no bridge; the whole 3-tool registrar moved to the decorator-driven `transit.mcp.ts`, its days/reservations bridge imports becoming injected `DaysService`/`ReservationsService` — zero bridge files; placeService → transitItineraryService followed, both done 2026-07/08), and the auth stack: authService (done 2026-08 — the 1497-line head of the auth → admin → oauth chain folded into the wrapper `AuthService` over DatabaseService + injected PermissionsService/AtlasService (retiring `atlas.bridge.ts` on schedule); the pure crypto half (backup-code hash/match/generate, `stripUserForClient`, key masking, the import-time `DUMMY_PASSWORD_HASH` timing equaliser + `avatarDir` mkdir, both documented parity exceptions) moved to the plain module `auth.helpers.ts`; `mfaSetupPending` + the per-email reset throttle (with its unref'd cleanup interval) stay module-scoped so the bridge and container instances share them; no MCP registrar existed and the plugin host never imported it, so neither surface changed — but the 15 in-container `*.mcp.ts` `isDemoUser` guards now inject `AuthService` (15 modules import AuthModule), with `atlas.mcp.ts` alone on the new 8-export `auth.bridge.ts` because the AuthModule→AtlasModule edge would otherwise close a cycle (places.mcp precedent); the bridge also serves `mcp/index.ts` token verification, the journey/notifications/transports registrars, and the still-legacy adminService/passkeyService; tests moved with IDs preserved (authService.test.ts → auth.helpers.test.ts, authServiceDb.test.ts → auth.service.test.ts + AUTH-BR-001…007 bridge cases), auth.e2e went DI-native (real bcrypt, real audit rows), oidc.e2e switched its dead path-mock for an instance spy). oidcService followed (done 2026-08 — the first frontier cash-in of the auth fold: the 508-line SSO module folded into the wrapper `OidcService` over DatabaseService + injected AuthService (the `resolveAuthToggles` `auth.bridge` import became the injection); no MCP surface, no plugin-host import, and no bridge at all — nothing outside the container consumes the domain, so the state/auth-code maps, their sweep intervals, and the discovery/JWKS caches became instance state with `onModuleDestroy` clearing the timers; `uuid`/`bcryptjs` lazy requires, the `invite_exhausted` sentinel transaction and every SQL string relocated verbatim; tests moved with OIDC-SVC IDs preserved, oidc.e2e and the integration suite switched their path mocks for `vi.spyOn(app.get(OidcService), …)` instance spies). passkeyService followed (done 2026-08 — the last frontier member of that pair: the 364-line WebAuthn module folded into a new `PasskeyService` in `nest/auth/` over DatabaseService + injected AuthService (the three `auth.bridge` imports resolved to the injection + plain `auth.helpers`/`avatarUrl` imports; `resolveWebauthnConfig` stays a plain helper import); no MCP surface, no plugin-host import, no bridge and no module state — the challenge store is DB-backed — so both in-container consumers just swapped to the injection (`PasskeyController`, and `AdminService.resetUserPasskeys` via `exports: [PasskeyService]` + an AdminModule→AuthModule import); the previously untested service gained a fresh PASSKEY-SVC-001…030 characterization suite with `@simplewebauthn/server` mocked at the verdict boundary), leaving oauthService — still an unblocked repoint-consumer of the bridge — plus adminService next in the chain. backupService also stays late: it owns closeDb/reinitialize lifecycle, which is deliberately
|
|
outside the DI wrapper.
|
|
|
|
Two special cases to keep in mind: airportService is lazily required by db/database.ts at boot (the flight-endpoint backfill) — whenever it migrates, that call should move into Nest's bootstrap instead, or you recreate the circular
|
|
import; and notifications/notificationService were imported by 6+ other services, so they had to migrate before their dependents — **cashed in 2026-08**: the fan-in fold above landed exactly that way, `send` shimmed 1:1 through `notifications.bridge.ts` for the remaining legacy dependents (adminService, memories) until their own folds.
|
|
|
|
The through-line: each finished migration lets that domain's tests drop vi.mock('db/database') for overrideProvider(DATABASE_CONNECTION), so the mock seam shrinks wave by wave and disappears with the auth stack. Want me to start on
|
|
tagService as the pilot?
|
|
|