refactor(core): rename getCurrentValue to fetchCurrentValue

Also use Promise.allSettled in ContextBridge.refresh() to handle
provider errors gracefully.

Co-authored-by: Ona <no-reply@ona.com>
This commit is contained in:
2026-01-18 20:23:54 +00:00
parent 3c16dd4275
commit 037589cf4f
8 changed files with 41 additions and 19 deletions

View File

@@ -107,7 +107,7 @@ class LocationProvider implements ContextProvider<Location> {
return () => navigator.geolocation.clearWatch(watchId)
}
async getCurrentValue(): Promise<Location> {
async fetchCurrentValue(): Promise<Location> {
const pos = await new Promise<GeolocationPosition>((resolve, reject) => {
navigator.geolocation.getCurrentPosition(resolve, reject)
})

View File

@@ -55,15 +55,21 @@ export class ContextBridge {
/**
* Gathers current values from all providers and pushes to controller.
* Use for manual refresh when user pulls to refresh.
* Errors from individual providers are silently ignored.
*/
async refresh(): Promise<void> {
const updates: Partial<Context> = {}
const entries = Array.from(this.providers.entries())
const values = await Promise.all(entries.map(([_, provider]) => provider.getCurrentValue()))
const results = await Promise.allSettled(
entries.map(([_, provider]) => provider.fetchCurrentValue()),
)
entries.forEach(([key], i) => {
updates[key] = values[i]
const result = results[i]
if (result?.status === "fulfilled") {
updates[key] = result.value
}
})
this.controller.pushContextUpdate(updates)

View File

@@ -16,7 +16,7 @@
* return () => navigator.geolocation.clearWatch(watchId)
* }
*
* async getCurrentValue(): Promise<Location> {
* async fetchCurrentValue(): Promise<Location> {
* const pos = await getCurrentPosition()
* return { lat: pos.coords.latitude, lng: pos.coords.longitude, accuracy: pos.coords.accuracy }
* }
@@ -30,6 +30,6 @@ export interface ContextProvider<T = unknown> {
/** Subscribe to value changes. Returns cleanup function. */
onUpdate(callback: (value: T) => void): () => void
/** Get current value on-demand (used for manual refresh). */
getCurrentValue(): Promise<T>
/** Fetch current value on-demand (used for manual refresh). */
fetchCurrentValue(): Promise<T>
}

View File

@@ -111,7 +111,7 @@ function createLocationProvider(): SimulatedLocationProvider {
callback = null
}
},
async getCurrentValue() {
async fetchCurrentValue() {
return currentLocation
},
simulateUpdate(location: Location) {

View File

@@ -1,5 +1,5 @@
import type { Context, DataSource } from "@aris/core"
import { TflApi, type ITflApi } from "./tfl-api.ts"
import type {
StationLocation,
TflAlertData,
@@ -10,6 +10,8 @@ import type {
TflLineId,
} from "./types.ts"
import { TflApi, type ITflApi } from "./tfl-api.ts"
const SEVERITY_PRIORITY: Record<TflAlertSeverity, number> = {
closure: 100,
"major-delays": 80,
@@ -22,7 +24,10 @@ function haversineDistance(lat1: number, lng1: number, lat2: number, lng2: numbe
const dLng = ((lng2 - lng1) * Math.PI) / 180
const a =
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos((lat1 * Math.PI) / 180) * Math.cos((lat2 * Math.PI) / 180) * Math.sin(dLng / 2) * Math.sin(dLng / 2)
Math.cos((lat1 * Math.PI) / 180) *
Math.cos((lat2 * Math.PI) / 180) *
Math.sin(dLng / 2) *
Math.sin(dLng / 2)
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
return R * c
}
@@ -62,13 +67,20 @@ export class TflDataSource implements DataSource<TflAlertFeedItem, TflDataSource
}
async query(context: Context, config: TflDataSourceConfig): Promise<TflAlertFeedItem[]> {
const [statuses, stations] = await Promise.all([this.api.fetchLineStatuses(config.lines), this.api.fetchStations()])
const [statuses, stations] = await Promise.all([
this.api.fetchLineStatuses(config.lines),
this.api.fetchStations(),
])
const items: TflAlertFeedItem[] = statuses.map((status) => {
const closestStationDistance =
context.location ?
findClosestStationDistance(status.lineId, stations, context.location.lat, context.location.lng)
: null
const closestStationDistance = context.location
? findClosestStationDistance(
status.lineId,
stations,
context.location.lat,
context.location.lng,
)
: null
const data: TflAlertData = {
line: status.lineId,

View File

@@ -1,11 +1,12 @@
import type { Context } from "@aris/core"
import { describe, expect, test } from "bun:test"
import type { Context } from "@aris/core"
import { TflDataSource } from "./data-source.ts"
import type { ITflApi, TflLineStatus } from "./tfl-api.ts"
import type { StationLocation, TflLineId } from "./types.ts"
import fixtures from "../fixtures/tfl-responses.json"
import { TflDataSource } from "./data-source.ts"
// Mock API that returns fixture data
class FixtureTflApi implements ITflApi {
@@ -109,9 +110,10 @@ describe("TfL Feed Items (using fixture data)", () => {
expect(typeof item.data.lineName).toBe("string")
expect(["minor-delays", "major-delays", "closure"]).toContain(item.data.severity)
expect(typeof item.data.description).toBe("string")
expect(item.data.closestStationDistance === null || typeof item.data.closestStationDistance === "number").toBe(
true,
)
expect(
item.data.closestStationDistance === null ||
typeof item.data.closestStationDistance === "number",
).toBe(true)
}
})

View File

@@ -1,4 +1,5 @@
import { type } from "arktype"
import type { StationLocation, TflAlertSeverity } from "./types.ts"
const TFL_API_BASE = "https://api.tfl.gov.uk"

View File

@@ -1,4 +1,5 @@
import type { FeedItem } from "@aris/core"
import type { TflLineId } from "./tfl-api.ts"
export type { TflLineId } from "./tfl-api.ts"