chore: move transitItineraryService into nest/transit as pure helpers

This commit is contained in:
jubnl
2026-08-01 12:45:35 +02:00
parent 57cc676e8a
commit e80ae79b77
8 changed files with 468 additions and 35 deletions
+18 -4
View File
@@ -29,7 +29,7 @@ mount to Nest and leaves the sibling trip routes (days, places, ...) on Express.
- **DI-native services (legacy `src/services/*` deleted):** tags, categories,
todo, packing, day-notes, trip-invite, assignments, share, settings, files,
collab, vacay, reservations, day, permissions, audit, budget, trip, maps,
transit, place — see the migration recipe below.
transit, place, transit-itinerary — see the migration recipe below.
## Cross-cutting Foundation pieces
@@ -244,7 +244,8 @@ Transitous/MOTIS proxy became a dep-free `TransitService` (no
frozen-at-import `TRANSIT_API_BASE` and lazy User-Agent memo staying
module-scoped on purpose; the pure `deriveTransitStats` + mode whitelist +
itinerary types moved to `transit.helpers.ts` (maps.helpers precedent) so the
downstream legacy `transitItineraryService` needs no bridge; the whole 3-tool
downstream legacy `transitItineraryService` needed no bridge (since relocated
into the domain as `transit-itinerary.helpers.ts`); the whole 3-tool
`mcp/tools/transit.ts` registrar moved to `transit.mcp.ts` — the two geo
search tools on `access: { group: 'geo', mode: 'read' }` and
`create_transit_journey` on `reservations:write`, with its days/reservations
@@ -278,9 +279,22 @@ empty-list short-circuit stays reachable, and retired three bespoke 400
strings — 'Place name is required', 'ids must be an array of numbers' and
'URL is required' — in favour of the pipe envelope, accepting that a
malformed body now 400s ahead of the trip-access 404 (the todo/trips trade).
transitItineraryService followed (the first pure-helpers relocation with no
service fold at all — the 287-line module is 100% pure, so the recipe's SQL /
bridge / DTO / plugin-host steps were all no-ops: the Zod itinerary schemas +
endpoint/metadata builders moved byte-identical to
`transit-itinerary.helpers.ts`, next to `transit.helpers.ts`; the schemas
**must** stay module-level plain exports because `transit.mcp.ts` consumes
them inside `@Tool({ inputSchema })` decorators, which evaluate at module load
before any container exists; the sole consumer — the in-container
`transit.mcp.ts` — was a one-import repoint, closing step 4 of the
dependency-honest order; the legacy module had no direct suite, so a new
21-case `TRANSIT-ITIN-*` characterization suite now pins all 12 superRefine
error strings, the `??` time fallbacks, the coordinate tolerances and the
reservation endpoint/metadata builder).
Repeat these steps per
service (next up: **transitItineraryService** —
or the notifications fan-in — per the dependency-honest order in
service (next up: **the notifications fan-in** —
per the dependency-honest order in
`migration-graph.md`). This is a
**pure relocation** — byte-identical
SQL, statuses, bodies, and error strings. The plugin RPC host is **no longer a
@@ -1,7 +1,16 @@
import { haversineKm } from './distanceService';
import type { EndpointInput } from '../nest/reservations/reservations.bridge';
import { localParts, resolveTimeZone } from './timezoneService';
import { deriveTransitStats, type TransitItinerary, type TransitLeg } from '../nest/transit/transit.helpers';
/**
* Pure transit-itinerary helpers — the wire schemas for provider itineraries
* plus the endpoint/metadata builder for automated transit reservations.
* Moved 1:1 from the legacy services/transitItineraryService.ts (identical
* schemas, tolerances, error strings and output shapes). Kept as plain
* exports (the maps.helpers.ts precedent): the schemas are consumed inside
* transit.mcp.ts's @Tool inputSchema decorators, which evaluate at module
* load — before any DI container exists — so they must stay module-level.
*/
import { haversineKm } from '../../services/distanceService';
import type { EndpointInput } from '../reservations/reservations.service';
import { localParts, resolveTimeZone } from '../../services/timezoneService';
import { deriveTransitStats, type TransitItinerary, type TransitLeg } from './transit.helpers';
import { z } from 'zod';
+4 -5
View File
@@ -1,9 +1,8 @@
/**
* Pure transit helpers shared by TransitService and plain-module code outside
* the container (the legacy services/transitItineraryService imports
* deriveTransitStats + the itinerary types). Kept as plain functions/consts
* (the maps.helpers.ts / files.constants.ts precedent) so no bridge is needed —
* helpers never block a migration.
* Pure transit helpers shared by TransitService and the itinerary
* validation/reservation builders in transit-itinerary.helpers.ts. Kept as
* plain functions/consts (the maps.helpers.ts / files.constants.ts
* precedent) — helpers never block a migration.
*/
// Modes the client may request — a strict whitelist so the proxy can't be used
+2 -3
View File
@@ -12,7 +12,7 @@ import {
transitCoordinatesSchema,
transitItinerarySchema,
transitPlaceSchema,
} from '../../services/transitItineraryService';
} from './transit-itinerary.helpers';
import { hasTripPermission, noAccess, permissionDenied, safeBroadcast } from '../../mcp/tools/_shared';
import { RateLimitService } from '../auth/rate-limit.service';
import { DatabaseService } from '../database/database.service';
@@ -54,8 +54,7 @@ function rateLimit(userId: number, bucket: string, max: number) {
* scope group — there is no geo:write) and the `canWrite(scopes,
* 'reservations')` early return on create_transit_journey. The
* days/reservations bridge imports became injected services; the itinerary
* schemas/helpers still come from the legacy transitItineraryService until
* that domain migrates.
* schemas/helpers live in the colocated transit-itinerary.helpers.ts.
*/
@McpController()
export class TransitMcp {
+1 -1
View File
@@ -126,7 +126,7 @@ function mapStop(p: MotisPlaceRaw | undefined, kind: 'departure' | 'arrival'): T
// A missing coordinate becomes 0 (null island) rather than null on
// purpose: TransitLegStop.lat/lng are non-nullable across the wire
// contract (transitStopSchema, the client picker) — making them nullable
// would ripple through transitItineraryService's validation and drop
// would ripple through transit-itinerary.helpers' validation and drop
// otherwise-usable itineraries. MOTIS places carry coordinates in
// practice; endpoint stops are re-checked by transitCoordinatesMatch.
lat: typeof p?.lat === 'number' ? p.lat : 0,