mirror of
https://github.com/kennethnym/aris.git
synced 2026-02-02 13:11:17 +00:00
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:
@@ -107,7 +107,7 @@ class LocationProvider implements ContextProvider<Location> {
|
|||||||
return () => navigator.geolocation.clearWatch(watchId)
|
return () => navigator.geolocation.clearWatch(watchId)
|
||||||
}
|
}
|
||||||
|
|
||||||
async getCurrentValue(): Promise<Location> {
|
async fetchCurrentValue(): Promise<Location> {
|
||||||
const pos = await new Promise<GeolocationPosition>((resolve, reject) => {
|
const pos = await new Promise<GeolocationPosition>((resolve, reject) => {
|
||||||
navigator.geolocation.getCurrentPosition(resolve, reject)
|
navigator.geolocation.getCurrentPosition(resolve, reject)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -55,15 +55,21 @@ export class ContextBridge {
|
|||||||
/**
|
/**
|
||||||
* Gathers current values from all providers and pushes to controller.
|
* Gathers current values from all providers and pushes to controller.
|
||||||
* Use for manual refresh when user pulls to refresh.
|
* Use for manual refresh when user pulls to refresh.
|
||||||
|
* Errors from individual providers are silently ignored.
|
||||||
*/
|
*/
|
||||||
async refresh(): Promise<void> {
|
async refresh(): Promise<void> {
|
||||||
const updates: Partial<Context> = {}
|
const updates: Partial<Context> = {}
|
||||||
|
|
||||||
const entries = Array.from(this.providers.entries())
|
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) => {
|
entries.forEach(([key], i) => {
|
||||||
updates[key] = values[i]
|
const result = results[i]
|
||||||
|
if (result?.status === "fulfilled") {
|
||||||
|
updates[key] = result.value
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
this.controller.pushContextUpdate(updates)
|
this.controller.pushContextUpdate(updates)
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
* return () => navigator.geolocation.clearWatch(watchId)
|
* return () => navigator.geolocation.clearWatch(watchId)
|
||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
* async getCurrentValue(): Promise<Location> {
|
* async fetchCurrentValue(): Promise<Location> {
|
||||||
* const pos = await getCurrentPosition()
|
* const pos = await getCurrentPosition()
|
||||||
* return { lat: pos.coords.latitude, lng: pos.coords.longitude, accuracy: pos.coords.accuracy }
|
* 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. */
|
/** Subscribe to value changes. Returns cleanup function. */
|
||||||
onUpdate(callback: (value: T) => void): () => void
|
onUpdate(callback: (value: T) => void): () => void
|
||||||
|
|
||||||
/** Get current value on-demand (used for manual refresh). */
|
/** Fetch current value on-demand (used for manual refresh). */
|
||||||
getCurrentValue(): Promise<T>
|
fetchCurrentValue(): Promise<T>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ function createLocationProvider(): SimulatedLocationProvider {
|
|||||||
callback = null
|
callback = null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async getCurrentValue() {
|
async fetchCurrentValue() {
|
||||||
return currentLocation
|
return currentLocation
|
||||||
},
|
},
|
||||||
simulateUpdate(location: Location) {
|
simulateUpdate(location: Location) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { Context, DataSource } from "@aris/core"
|
import type { Context, DataSource } from "@aris/core"
|
||||||
import { TflApi, type ITflApi } from "./tfl-api.ts"
|
|
||||||
import type {
|
import type {
|
||||||
StationLocation,
|
StationLocation,
|
||||||
TflAlertData,
|
TflAlertData,
|
||||||
@@ -10,6 +10,8 @@ import type {
|
|||||||
TflLineId,
|
TflLineId,
|
||||||
} from "./types.ts"
|
} from "./types.ts"
|
||||||
|
|
||||||
|
import { TflApi, type ITflApi } from "./tfl-api.ts"
|
||||||
|
|
||||||
const SEVERITY_PRIORITY: Record<TflAlertSeverity, number> = {
|
const SEVERITY_PRIORITY: Record<TflAlertSeverity, number> = {
|
||||||
closure: 100,
|
closure: 100,
|
||||||
"major-delays": 80,
|
"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 dLng = ((lng2 - lng1) * Math.PI) / 180
|
||||||
const a =
|
const a =
|
||||||
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
|
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))
|
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
|
||||||
return R * c
|
return R * c
|
||||||
}
|
}
|
||||||
@@ -62,13 +67,20 @@ export class TflDataSource implements DataSource<TflAlertFeedItem, TflDataSource
|
|||||||
}
|
}
|
||||||
|
|
||||||
async query(context: Context, config: TflDataSourceConfig): Promise<TflAlertFeedItem[]> {
|
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 items: TflAlertFeedItem[] = statuses.map((status) => {
|
||||||
const closestStationDistance =
|
const closestStationDistance = context.location
|
||||||
context.location ?
|
? findClosestStationDistance(
|
||||||
findClosestStationDistance(status.lineId, stations, context.location.lat, context.location.lng)
|
status.lineId,
|
||||||
: null
|
stations,
|
||||||
|
context.location.lat,
|
||||||
|
context.location.lng,
|
||||||
|
)
|
||||||
|
: null
|
||||||
|
|
||||||
const data: TflAlertData = {
|
const data: TflAlertData = {
|
||||||
line: status.lineId,
|
line: status.lineId,
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
|
import type { Context } from "@aris/core"
|
||||||
|
|
||||||
import { describe, expect, test } from "bun:test"
|
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 { ITflApi, TflLineStatus } from "./tfl-api.ts"
|
||||||
import type { StationLocation, TflLineId } from "./types.ts"
|
import type { StationLocation, TflLineId } from "./types.ts"
|
||||||
|
|
||||||
import fixtures from "../fixtures/tfl-responses.json"
|
import fixtures from "../fixtures/tfl-responses.json"
|
||||||
|
import { TflDataSource } from "./data-source.ts"
|
||||||
|
|
||||||
// Mock API that returns fixture data
|
// Mock API that returns fixture data
|
||||||
class FixtureTflApi implements ITflApi {
|
class FixtureTflApi implements ITflApi {
|
||||||
@@ -109,9 +110,10 @@ describe("TfL Feed Items (using fixture data)", () => {
|
|||||||
expect(typeof item.data.lineName).toBe("string")
|
expect(typeof item.data.lineName).toBe("string")
|
||||||
expect(["minor-delays", "major-delays", "closure"]).toContain(item.data.severity)
|
expect(["minor-delays", "major-delays", "closure"]).toContain(item.data.severity)
|
||||||
expect(typeof item.data.description).toBe("string")
|
expect(typeof item.data.description).toBe("string")
|
||||||
expect(item.data.closestStationDistance === null || typeof item.data.closestStationDistance === "number").toBe(
|
expect(
|
||||||
true,
|
item.data.closestStationDistance === null ||
|
||||||
)
|
typeof item.data.closestStationDistance === "number",
|
||||||
|
).toBe(true)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { type } from "arktype"
|
import { type } from "arktype"
|
||||||
|
|
||||||
import type { StationLocation, TflAlertSeverity } from "./types.ts"
|
import type { StationLocation, TflAlertSeverity } from "./types.ts"
|
||||||
|
|
||||||
const TFL_API_BASE = "https://api.tfl.gov.uk"
|
const TFL_API_BASE = "https://api.tfl.gov.uk"
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { FeedItem } from "@aris/core"
|
import type { FeedItem } from "@aris/core"
|
||||||
|
|
||||||
import type { TflLineId } from "./tfl-api.ts"
|
import type { TflLineId } from "./tfl-api.ts"
|
||||||
|
|
||||||
export type { TflLineId } from "./tfl-api.ts"
|
export type { TflLineId } from "./tfl-api.ts"
|
||||||
|
|||||||
Reference in New Issue
Block a user