Skip to content

Commit 923ee22

Browse files
committed
refactor(framework): typed registrars for remaining domains + cleanup
- Replace untyped registerProvider(domain, unknown) with 8 typed registrars (weather, geocoding, routing, photos, reviews, poi-search, knowledge, gtfs-catalog); move each domain's provider interface into packages/integration-framework/src/contracts/. Rename KnowledgeSource to KnowledgeProvider. - Move motis-feed-proxy nginx renderer from packages/cli into the dedicated @openmapx/motis-feed-proxy-config package; CLI becomes a thin wrapper. - Rewrite NSW + UTMC parking providers against TfNSW Car Park API v2.3 and UTMC v2 specs (fixes API version 404, array-shape bugs, expands NSW KNOWN_FACILITIES from 11 to 44); add fixture-based tests. - Resolve DEDUP and contract-type TODOs with explanatory comments; delete unused feedTagFromSource helper; type TimetableEntry as Departure.
1 parent c2d22e1 commit 923ee22

66 files changed

Lines changed: 1722 additions & 1043 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/api/src/integration-host.ts

Lines changed: 98 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -914,19 +914,60 @@ export async function initIntegrations(
914914
getRequiredService(key: string) {
915915
return requiresMap.get(key) ?? null;
916916
},
917-
registerProvider(domain: string, provider: unknown) {
918-
const existing = providers.get(domain) ?? [];
919-
existing.push(provider);
920-
providers.set(domain, existing);
921-
},
922917
registerTransitProvider(provider) {
923-
this.registerProvider("transit", provider);
918+
const existing = providers.get("transit") ?? [];
919+
existing.push(provider);
920+
providers.set("transit", existing);
924921
},
925922
registerRealtimeProvider(provider) {
926-
this.registerProvider("live-transit", provider);
923+
const existing = providers.get("live-transit") ?? [];
924+
existing.push(provider);
925+
providers.set("live-transit", existing);
927926
},
928927
registerMobilityDataSource(provider) {
929-
this.registerProvider("data-source", provider);
928+
const existing = providers.get("data-source") ?? [];
929+
existing.push(provider);
930+
providers.set("data-source", existing);
931+
},
932+
registerWeatherProvider(provider) {
933+
const existing = providers.get("weather") ?? [];
934+
existing.push(provider);
935+
providers.set("weather", existing);
936+
},
937+
registerGeocodingProvider(provider) {
938+
const existing = providers.get("geocoding") ?? [];
939+
existing.push(provider);
940+
providers.set("geocoding", existing);
941+
},
942+
registerRoutingProvider(provider) {
943+
const existing = providers.get("routing") ?? [];
944+
existing.push(provider);
945+
providers.set("routing", existing);
946+
},
947+
registerPhotoProvider(provider) {
948+
const existing = providers.get("photos") ?? [];
949+
existing.push(provider);
950+
providers.set("photos", existing);
951+
},
952+
registerReviewProvider(provider) {
953+
const existing = providers.get("reviews") ?? [];
954+
existing.push(provider);
955+
providers.set("reviews", existing);
956+
},
957+
registerPoiSearchProvider(provider) {
958+
const existing = providers.get("poi-search") ?? [];
959+
existing.push(provider);
960+
providers.set("poi-search", existing);
961+
},
962+
registerKnowledgeProvider(provider) {
963+
const existing = providers.get("knowledge") ?? [];
964+
existing.push(provider);
965+
providers.set("knowledge", existing);
966+
},
967+
registerGtfsCatalogProvider(provider) {
968+
const existing = providers.get("gtfs-catalog") ?? [];
969+
existing.push(provider);
970+
providers.set("gtfs-catalog", existing);
930971
},
931972
registerRoute(method: string, path: string, handler: RouteHandler, options?: RouteOptions) {
932973
registerIntegrationRoute(id, method, path, handler, options);
@@ -1260,19 +1301,60 @@ export async function reloadIntegrations(): Promise<{
12601301
getRequiredService(key: string) {
12611302
return reloadRequiresMap.get(key) ?? null;
12621303
},
1263-
registerProvider(domain: string, provider: unknown) {
1264-
const existing = providers.get(domain) ?? [];
1265-
existing.push(provider);
1266-
providers.set(domain, existing);
1267-
},
12681304
registerTransitProvider(provider) {
1269-
this.registerProvider("transit", provider);
1305+
const existing = providers.get("transit") ?? [];
1306+
existing.push(provider);
1307+
providers.set("transit", existing);
12701308
},
12711309
registerRealtimeProvider(provider) {
1272-
this.registerProvider("live-transit", provider);
1310+
const existing = providers.get("live-transit") ?? [];
1311+
existing.push(provider);
1312+
providers.set("live-transit", existing);
12731313
},
12741314
registerMobilityDataSource(provider) {
1275-
this.registerProvider("data-source", provider);
1315+
const existing = providers.get("data-source") ?? [];
1316+
existing.push(provider);
1317+
providers.set("data-source", existing);
1318+
},
1319+
registerWeatherProvider(provider) {
1320+
const existing = providers.get("weather") ?? [];
1321+
existing.push(provider);
1322+
providers.set("weather", existing);
1323+
},
1324+
registerGeocodingProvider(provider) {
1325+
const existing = providers.get("geocoding") ?? [];
1326+
existing.push(provider);
1327+
providers.set("geocoding", existing);
1328+
},
1329+
registerRoutingProvider(provider) {
1330+
const existing = providers.get("routing") ?? [];
1331+
existing.push(provider);
1332+
providers.set("routing", existing);
1333+
},
1334+
registerPhotoProvider(provider) {
1335+
const existing = providers.get("photos") ?? [];
1336+
existing.push(provider);
1337+
providers.set("photos", existing);
1338+
},
1339+
registerReviewProvider(provider) {
1340+
const existing = providers.get("reviews") ?? [];
1341+
existing.push(provider);
1342+
providers.set("reviews", existing);
1343+
},
1344+
registerPoiSearchProvider(provider) {
1345+
const existing = providers.get("poi-search") ?? [];
1346+
existing.push(provider);
1347+
providers.set("poi-search", existing);
1348+
},
1349+
registerKnowledgeProvider(provider) {
1350+
const existing = providers.get("knowledge") ?? [];
1351+
existing.push(provider);
1352+
providers.set("knowledge", existing);
1353+
},
1354+
registerGtfsCatalogProvider(provider) {
1355+
const existing = providers.get("gtfs-catalog") ?? [];
1356+
existing.push(provider);
1357+
providers.set("gtfs-catalog", existing);
12761358
},
12771359
registerRoute(method: string, path: string, handler: RouteHandler, options?: RouteOptions) {
12781360
registerIntegrationRoute(id, method, path, handler, options);

apps/api/src/services/gtfs/catalog.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,27 @@
11
import { normalizeProducerUrl } from "@integrations/transit-mobility-database";
22
import type { BBox } from "@openmapx/core";
33
import { USER_AGENT_TRANSIT } from "@openmapx/core";
4+
import type { GtfsCatalogProvider } from "@openmapx/integration-framework";
45
import { getIntegrationsByDomain } from "../../integration-host.js";
56
import type { CatalogFeed } from "./types";
67

78
/**
8-
* Provider shape registered by the `transit-mobility-database` integration
9+
* The `transit-mobility-database` integration registers a `GtfsCatalogProvider`
910
* under the `gtfs-catalog` domain (see its `setup()`). We have to go through
1011
* the registry because in development the integration host imports built-in
1112
* modules with an mtime query string for HMR — a direct
1213
* `import { getMdbCatalogFeeds } from "@integrations/transit-mobility-database"`
1314
* pulls a separate module instance whose `state.client` is never initialized,
1415
* so the call would always return `[]`.
1516
*/
16-
interface MdbCatalogProvider {
17-
id: string;
18-
listFeeds: () => Promise<CatalogFeed[]>;
19-
}
20-
2117
async function getMdbCatalogFeeds(): Promise<CatalogFeed[]> {
2218
const out: CatalogFeed[] = [];
2319
for (const integration of getIntegrationsByDomain("gtfs-catalog")) {
24-
const providers = (integration.providers.get("gtfs-catalog") ?? []) as MdbCatalogProvider[];
20+
const providers = (integration.providers.get("gtfs-catalog") ?? []) as GtfsCatalogProvider[];
2521
for (const provider of providers) {
2622
try {
2723
const feeds = await provider.listFeeds();
28-
out.push(...feeds);
24+
out.push(...(feeds as CatalogFeed[]));
2925
} catch (err) {
3026
console.warn(`[gtfs-catalog] provider "${provider.id}" listFeeds failed:`, err);
3127
}

apps/api/src/services/knowledge/__tests__/index.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import type { KnowledgeSource } from "@openmapx/core";
1+
import type { KnowledgeProvider } from "@openmapx/core";
22
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
33

44
// Mock knowledge sources
5-
const mockWikidataSource: KnowledgeSource = {
5+
const mockWikidataSource: KnowledgeProvider = {
66
name: "wikidata",
77
lookup: vi.fn(),
88
};
9-
const mockWikipediaSource: KnowledgeSource = {
9+
const mockWikipediaSource: KnowledgeProvider = {
1010
name: "wikipedia",
1111
lookup: vi.fn(),
1212
};

apps/api/src/services/knowledge/index.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
import type { KnowledgeResult, KnowledgeSource, Place } from "@openmapx/core";
1+
import type { KnowledgeProvider, KnowledgeResult, Place } from "@openmapx/core";
22
import { getIntegrationsByDomain } from "../../integration-host.js";
33

44
/**
55
* Collect knowledge sources from all integrations registered under the "knowledge" domain.
66
*/
7-
function getKnowledgeSources(): KnowledgeSource[] {
8-
const sources: KnowledgeSource[] = [];
7+
function getKnowledgeSources(): KnowledgeProvider[] {
8+
const sources: KnowledgeProvider[] = [];
99
for (const integration of getIntegrationsByDomain("knowledge")) {
10-
for (const e of (integration.providers.get("knowledge") ?? []) as KnowledgeSource[]) {
10+
for (const e of (integration.providers.get("knowledge") ?? []) as KnowledgeProvider[]) {
1111
sources.push(e);
1212
}
1313
}

integrations/ev-charging/providers/dedup.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,10 @@ function stationPriority(station: EvChargingStation): number {
1111
return getEvChargingSourcePriority(station.sources[0]);
1212
}
1313

14-
// TODO(policy): mobility-core's DEDUP.EV_RADIUS_M is 50m. EV clustering
15-
// uses a three-tier window (20m always, 20-90m soft with names, 90-150m
16-
// strict with names+operator+address) tuned for charging-station data
17-
// where physical sites span multiple bays. Kept raw until thresholds
18-
// are reconciled across providers.
14+
// EV clustering deliberately widens past `DEDUP.EV_RADIUS_M` (50 m) into a
15+
// three-tier window — 20 m always, 20-90 m soft with names, 90-150 m strict
16+
// with names + operator + address — because physical charging sites span
17+
// multiple bays and shared infrastructure across operators.
1918
const ALWAYS_MERGE_M = 20;
2019
const SOFT_MERGE_M = 90;
2120
const NEVER_MERGE_M = 150;

integrations/geocoding-db-ris/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export function setup(ctx: IntegrationContext): void {
99
clientId: ctx.config.clientId as string | undefined,
1010
apiKey: ctx.config.apiKey as string | undefined,
1111
});
12-
ctx.registerProvider("geocoding", dbRisGeocodingService);
12+
ctx.registerGeocodingProvider(dbRisGeocodingService);
1313

1414
// EVA primary-id dispatch: when a Place.id arrives as `eva:8000105`,
1515
// resolve it via the RIS station lookup. lookupDbStation returns a

integrations/geocoding-entur/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export function setup(ctx: IntegrationContext): void {
1515
multiModal: ctx.config.multiModal as "parent" | "child" | "all" | undefined,
1616
});
1717

18-
ctx.registerProvider("geocoding", enturGeocodingService);
18+
ctx.registerGeocodingProvider(enturGeocodingService);
1919

2020
registerPlaceResolver("entur", async (value: string, resolverCtx: PlaceResolverContext) =>
2121
lookupEnturPlaceById(value, resolverCtx.lang),

integrations/geocoding-maptiler/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@ import { maptilerGeocodingService, setMaptilerApiKey } from "./provider.js";
33

44
export function setup(ctx: IntegrationContext): void {
55
setMaptilerApiKey(ctx.config.apiKey as string | undefined);
6-
ctx.registerProvider("geocoding", maptilerGeocodingService);
6+
ctx.registerGeocodingProvider(maptilerGeocodingService);
77
}

integrations/geocoding-motis/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@ export function setup(ctx: IntegrationContext): void {
1010
const transitousUrl = ctx.config.transitousUrl as string | undefined;
1111
if (transitousUrl && transitousUrl.length > 0) setTransitousUrl(transitousUrl);
1212

13-
ctx.registerProvider("geocoding", motisGeocodingService);
13+
ctx.registerGeocodingProvider(motisGeocodingService);
1414
}

integrations/geocoding-nominatim/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@ export function setup(ctx: IntegrationContext): void {
1010

1111
setNominatimUrl(url);
1212

13-
ctx.registerProvider("geocoding", nominatimService);
13+
ctx.registerGeocodingProvider(nominatimService);
1414
}

0 commit comments

Comments
 (0)