diff --git a/CHANGELOG.md b/CHANGELOG.md index 84a2fb4..c0c6bd9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,15 @@ - Add `GtfsSqlJs.fromZipData(zipData, options?)` static method for loading from pre-loaded ZIP data (`ArrayBuffer` or `Uint8Array`) - Keep `GtfsSqlJs.fromZip(zipPath, options?)` strictly typed as `string` for paths/URLs +- Remove `as unknown as Crypto` double cast in checksum module; call `webcrypto.subtle.digest()` directly +- Remove `as BufferSource` cast in checksum module; pass `ArrayBuffer` directly to `sha256Digest()` +- Extract shared `isNodeEnvironment()` helper into `utils/env.ts`, replacing inline `typeof process` checks in zip-loader and gtfs-rt-loader +- Narrow `loadGTFSZip()` parameter from `string | ArrayBuffer | Uint8Array` to `ArrayBuffer | Uint8Array` (string path was dead code) +- Replace `unknown[]` with proper `ProtobufTimeRange[]` and `ProtobufEntitySelector[]` types in gtfs-rt-loader +- Refactor `convertKeysToSnakeCase` to use `Object.entries()`, removing `for..in` loop with `as Record` cast +- Replace `as Record` widening casts on `stmt.getAsObject()` across all query files with proper `ParamsObject` type from sql.js +- Replace non-null assertions (`!`) with optional chaining (`?.`) for `Map.get()` calls in rt-trip-updates and stop-times +- Replace `this.SQL!.Database()` non-null assertion with explicit guard in gtfs-sqljs ## 0.2.2 diff --git a/package-lock.json b/package-lock.json index 5829cb9..4a9e274 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "gtfs-sqljs", - "version": "0.1.2", + "version": "0.2.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "gtfs-sqljs", - "version": "0.1.2", + "version": "0.2.2", "license": "MIT", "dependencies": { "jszip": "^3.10.1", @@ -1371,7 +1371,6 @@ "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.56.1", "@typescript-eslint/types": "8.56.1", @@ -1687,7 +1686,6 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1991,7 +1989,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -2046,7 +2043,6 @@ "integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -2948,7 +2944,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -3376,7 +3371,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -3546,7 +3540,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -3621,7 +3614,6 @@ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -3715,7 +3707,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -3873,7 +3864,6 @@ "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", "dev": true, "license": "ISC", - "peer": true, "bin": { "yaml": "bin.mjs" }, diff --git a/src/cache/checksum.ts b/src/cache/checksum.ts index 25ca86e..03d27a6 100644 --- a/src/cache/checksum.ts +++ b/src/cache/checksum.ts @@ -1,24 +1,26 @@ /** - * Get crypto object (browser or Node.js) + * Compute a SHA-256 digest using the Web Crypto API (browser or Node.js 18+). + * Returns the raw hash as an ArrayBuffer. */ -async function getCrypto(): Promise { +async function sha256Digest(data: ArrayBuffer): Promise { // Browser environment if (typeof crypto !== 'undefined' && crypto.subtle) { - return crypto; + return crypto.subtle.digest('SHA-256', data); } - // Node.js environment - import webcrypto + // Node.js environment if (typeof globalThis !== 'undefined') { try { const { webcrypto } = await import('crypto'); - return webcrypto as unknown as Crypto; + if (webcrypto?.subtle) { + return webcrypto.subtle.digest('SHA-256', data); + } } catch { // Fallback for older Node.js versions - throw new Error('Web Crypto API not available'); } } - throw new Error('Crypto not available in this environment'); + throw new Error('Web Crypto API not available in this environment'); } /** @@ -26,13 +28,16 @@ async function getCrypto(): Promise { * Uses Web Crypto API (available in both browser and Node.js 18+) */ export async function computeChecksum(data: ArrayBuffer | Uint8Array): Promise { - // Get crypto instance - const cryptoInstance = await getCrypto(); - - // Ensure we have a BufferSource for crypto.subtle.digest - // Use type assertion to handle ArrayBufferLike compatibility - const bufferSource = (data instanceof Uint8Array ? data : new Uint8Array(data)) as BufferSource; - const hashBuffer = await cryptoInstance.subtle.digest('SHA-256', bufferSource); + let buffer: ArrayBuffer; + if (data instanceof ArrayBuffer) { + buffer = data; + } else { + // Copy Uint8Array into a fresh ArrayBuffer to avoid SharedArrayBuffer ambiguity + const copy = new ArrayBuffer(data.byteLength); + new Uint8Array(copy).set(data); + buffer = copy; + } + const hashBuffer = await sha256Digest(buffer); const hashArray = Array.from(new Uint8Array(hashBuffer)); const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); return hashHex; diff --git a/src/gtfs-sqljs.ts b/src/gtfs-sqljs.ts index 3b10d6d..b57c4f9 100644 --- a/src/gtfs-sqljs.ts +++ b/src/gtfs-sqljs.ts @@ -355,8 +355,12 @@ export class GtfsSqlJs { options: Omit, onProgress?: ProgressCallback ): Promise { + if (!this.SQL) { + throw new Error('SQL.js not initialized'); + } + // Create new database - this.db = new this.SQL!.Database(); + this.db = new this.SQL.Database(); // Apply performance PRAGMAs for bulk loading this.db.run('PRAGMA synchronous = OFF'); // Skip fsync for performance diff --git a/src/loaders/gtfs-rt-loader.ts b/src/loaders/gtfs-rt-loader.ts index cefcfbb..1d43ce0 100644 --- a/src/loaders/gtfs-rt-loader.ts +++ b/src/loaders/gtfs-rt-loader.ts @@ -1,5 +1,6 @@ import type { Database } from 'sql.js'; import protobuf from 'protobufjs'; +import { isNodeEnvironment } from '../utils/env'; // Types for protobuf decoded objects interface ProtobufTranslation { @@ -11,10 +12,23 @@ interface ProtobufTranslatedString { translation: ProtobufTranslation[]; } +interface ProtobufTimeRange { + start?: number; + end?: number; +} + +interface ProtobufEntitySelector { + agencyId?: string; + routeId?: string; + routeType?: number; + trip?: ProtobufTripDescriptor; + stopId?: string; +} + interface ProtobufAlert { id: string; - activePeriod?: unknown[]; - informedEntity?: unknown[]; + activePeriod?: ProtobufTimeRange[]; + informedEntity?: ProtobufEntitySelector[]; cause?: number; effect?: number; url?: ProtobufTranslatedString; @@ -278,11 +292,7 @@ async function fetchProtobuf(source: string): Promise { } // Read from local file (Node.js only) - const isNode = typeof process !== 'undefined' && - process.versions != null && - process.versions.node != null; - - if (isNode) { + if (isNodeEnvironment()) { try { const fs = await import('fs'); const buffer = await fs.promises.readFile(source); @@ -322,24 +332,16 @@ function camelToSnake(str: string): string { return str.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`); } -function convertKeysToSnakeCase(obj: unknown): unknown { - if (obj === null || obj === undefined) { - return obj; - } - if (Array.isArray(obj)) { - return obj.map(convertKeysToSnakeCase); - } - if (typeof obj === 'object') { - const result: Record = {}; - for (const key in obj as Record) { - if (Object.prototype.hasOwnProperty.call(obj, key)) { - const snakeKey = camelToSnake(key); - result[snakeKey] = convertKeysToSnakeCase((obj as Record)[key]); - } - } - return result; +/** Convert all keys of a plain object from camelCase to snake_case recursively */ +function convertObjectKeysToSnakeCase(obj: object): Record { + const result: Record = {}; + for (const [key, value] of Object.entries(obj)) { + const snakeKey = camelToSnake(key); + result[snakeKey] = (value !== null && typeof value === 'object' && !Array.isArray(value)) + ? convertObjectKeysToSnakeCase(value as object) + : value; } - return obj; + return result; } // Parse TranslatedString to JSON @@ -366,8 +368,8 @@ function insertAlerts(db: Database, alerts: ProtobufAlert[], timestamp: number): for (const alert of alerts) { // Convert nested objects to snake_case for database storage - const activePeriodSnake = convertKeysToSnakeCase(alert.activePeriod || []); - const informedEntitySnake = convertKeysToSnakeCase(alert.informedEntity || []); + const activePeriodSnake = (alert.activePeriod ?? []).map(convertObjectKeysToSnakeCase); + const informedEntitySnake = (alert.informedEntity ?? []).map(convertObjectKeysToSnakeCase); stmt.run([ alert.id, diff --git a/src/loaders/zip-loader.ts b/src/loaders/zip-loader.ts index 0e33048..becca5c 100644 --- a/src/loaders/zip-loader.ts +++ b/src/loaders/zip-loader.ts @@ -4,25 +4,16 @@ import JSZip from 'jszip'; import type { ProgressCallback } from '../gtfs-sqljs'; +import { isNodeEnvironment } from '../utils/env'; export interface GTFSFiles { [filename: string]: string; } /** - * Fetch and extract GTFS ZIP file - * Works in both browser and Node.js environments + * Extract GTFS files from pre-loaded ZIP data */ -export async function loadGTFSZip(source: string | ArrayBuffer | Uint8Array): Promise { - let zipData: ArrayBuffer | Uint8Array; - - // If source is a string, treat it as URL or file path - if (typeof source === 'string') { - zipData = await fetchZip(source); - } else { - zipData = source; - } - +export async function loadGTFSZip(zipData: ArrayBuffer | Uint8Array): Promise { // Load ZIP file const zip = await JSZip.loadAsync(zipData); @@ -122,12 +113,7 @@ export async function fetchZip(source: string, onProgress?: ProgressCallback): P } // For non-URLs, try Node.js fs first, fall back to fetch for browser - // Check if we're in Node.js environment - const isNode = typeof process !== 'undefined' && - process.versions != null && - process.versions.node != null; - - if (isNode) { + if (isNodeEnvironment()) { // In Node.js, treat as file path try { const fs = await import('fs'); diff --git a/src/queries/agencies.ts b/src/queries/agencies.ts index a7fa402..cdb2491 100644 --- a/src/queries/agencies.ts +++ b/src/queries/agencies.ts @@ -2,7 +2,7 @@ * Agency Query Methods */ -import type { Database } from 'sql.js'; +import type { Database, ParamsObject } from 'sql.js'; import type { Agency } from '../types/gtfs'; export interface AgencyFilters { @@ -48,7 +48,7 @@ export function getAgencies(db: Database, filters: AgencyFilters = {}): Agency[] const agencies: Agency[] = []; while (stmt.step()) { - const row = stmt.getAsObject() as Record; + const row = stmt.getAsObject(); agencies.push(rowToAgency(row)); } @@ -59,7 +59,7 @@ export function getAgencies(db: Database, filters: AgencyFilters = {}): Agency[] /** * Convert database row to Agency object */ -function rowToAgency(row: Record): Agency { +function rowToAgency(row: ParamsObject): Agency { return { agency_id: String(row.agency_id), agency_name: String(row.agency_name), diff --git a/src/queries/calendar.ts b/src/queries/calendar.ts index 44cc37b..9727ca0 100644 --- a/src/queries/calendar.ts +++ b/src/queries/calendar.ts @@ -2,7 +2,7 @@ * Calendar Query Methods */ -import type { Database } from 'sql.js'; +import type { Database, ParamsObject } from 'sql.js'; import type { Calendar, CalendarDate } from '../types/gtfs'; /** @@ -64,7 +64,7 @@ export function getCalendarByServiceId(db: Database, serviceId: string): Calenda stmt.bind([serviceId]); if (stmt.step()) { - const row = stmt.getAsObject() as Record; + const row = stmt.getAsObject(); stmt.free(); return rowToCalendar(row); } @@ -82,7 +82,7 @@ export function getCalendarDates(db: Database, serviceId: string): CalendarDate[ const dates: CalendarDate[] = []; while (stmt.step()) { - const row = stmt.getAsObject() as Record; + const row = stmt.getAsObject(); dates.push(rowToCalendarDate(row)); } @@ -99,7 +99,7 @@ export function getCalendarDatesForDate(db: Database, date: string): CalendarDat const dates: CalendarDate[] = []; while (stmt.step()) { - const row = stmt.getAsObject() as Record; + const row = stmt.getAsObject(); dates.push(rowToCalendarDate(row)); } @@ -110,7 +110,7 @@ export function getCalendarDatesForDate(db: Database, date: string): CalendarDat /** * Convert database row to Calendar object */ -function rowToCalendar(row: Record): Calendar { +function rowToCalendar(row: ParamsObject): Calendar { return { service_id: String(row.service_id), monday: Number(row.monday), @@ -128,7 +128,7 @@ function rowToCalendar(row: Record): Calendar { /** * Convert database row to CalendarDate object */ -function rowToCalendarDate(row: Record): CalendarDate { +function rowToCalendarDate(row: ParamsObject): CalendarDate { return { service_id: String(row.service_id), date: String(row.date), diff --git a/src/queries/routes.ts b/src/queries/routes.ts index 55a7699..f99e32a 100644 --- a/src/queries/routes.ts +++ b/src/queries/routes.ts @@ -2,7 +2,7 @@ * Route Query Methods */ -import type { Database } from 'sql.js'; +import type { Database, ParamsObject } from 'sql.js'; import type { Route } from '../types/gtfs'; export interface RouteFilters { @@ -58,7 +58,7 @@ export function getRoutes(db: Database, filters: RouteFilters = {}): Route[] { const routes: Route[] = []; while (stmt.step()) { - const row = stmt.getAsObject() as Record; + const row = stmt.getAsObject(); routes.push(rowToRoute(row)); } @@ -69,7 +69,7 @@ export function getRoutes(db: Database, filters: RouteFilters = {}): Route[] { /** * Convert database row to Route object */ -function rowToRoute(row: Record): Route { +function rowToRoute(row: ParamsObject): Route { return { route_id: String(row.route_id), route_short_name: String(row.route_short_name), diff --git a/src/queries/rt-alerts.ts b/src/queries/rt-alerts.ts index 283a42b..3b3ea04 100644 --- a/src/queries/rt-alerts.ts +++ b/src/queries/rt-alerts.ts @@ -1,4 +1,4 @@ -import type { Database } from 'sql.js'; +import type { Database, ParamsObject } from 'sql.js'; import type { Alert, EntitySelector, TimeRange, TranslatedString } from '../types/gtfs-rt'; import type { AlertFilters } from '../types/gtfs-rt'; @@ -7,7 +7,7 @@ export type { AlertFilters }; /** * Parse JSON fields from database */ -function parseAlert(row: Record): Alert { +function parseAlert(row: ParamsObject): Alert { return { id: String(row.id), active_period: row.active_period ? JSON.parse(String(row.active_period)) as TimeRange[] : [], @@ -133,7 +133,7 @@ export function getAlerts(db: Database, filters: AlertFilters = {}, stalenessThr const alerts: Alert[] = []; while (stmt.step()) { - const row = stmt.getAsObject() as Record; + const row = stmt.getAsObject(); const alert = parseAlert(row); // Apply activeOnly filter in application code @@ -172,7 +172,7 @@ export function getAllAlerts(db: Database): Alert[] { const alerts: Alert[] = []; while (stmt.step()) { - const row = stmt.getAsObject() as Record; + const row = stmt.getAsObject(); alerts.push(parseAlert(row)); } diff --git a/src/queries/rt-stop-time-updates.ts b/src/queries/rt-stop-time-updates.ts index 91b859c..2c11dce 100644 --- a/src/queries/rt-stop-time-updates.ts +++ b/src/queries/rt-stop-time-updates.ts @@ -1,4 +1,4 @@ -import type { Database } from 'sql.js'; +import type { Database, ParamsObject } from 'sql.js'; import type { StopTimeUpdate } from '../types/gtfs-rt'; export interface StopTimeUpdateFilters { @@ -11,7 +11,7 @@ export interface StopTimeUpdateFilters { /** * Parse stop time update from database row (includes trip_id and rt_last_updated) */ -function parseStopTimeUpdate(row: Record): StopTimeUpdate { +function parseStopTimeUpdate(row: ParamsObject): StopTimeUpdate { const stu: StopTimeUpdate = { stop_sequence: row.stop_sequence !== null ? Number(row.stop_sequence) : undefined, stop_id: row.stop_id ? String(row.stop_id) : undefined, @@ -112,7 +112,7 @@ export function getStopTimeUpdates( const stopTimeUpdates: StopTimeUpdate[] = []; while (stmt.step()) { - const row = stmt.getAsObject() as Record; + const row = stmt.getAsObject(); stopTimeUpdates.push(parseStopTimeUpdate(row)); } diff --git a/src/queries/rt-trip-updates.ts b/src/queries/rt-trip-updates.ts index 694975d..7f0e63d 100644 --- a/src/queries/rt-trip-updates.ts +++ b/src/queries/rt-trip-updates.ts @@ -1,4 +1,4 @@ -import type { Database } from 'sql.js'; +import type { Database, ParamsObject } from 'sql.js'; import type { TripUpdate, StopTimeUpdate } from '../types/gtfs-rt'; import { getStopTimeUpdates } from './rt-stop-time-updates'; @@ -12,7 +12,7 @@ export interface TripUpdateFilters { /** * Parse trip update from database row */ -export function parseTripUpdate(row: Record): TripUpdate { +export function parseTripUpdate(row: ParamsObject): TripUpdate { const tu: TripUpdate = { trip_id: String(row.trip_id), route_id: row.route_id ? String(row.route_id) : undefined, @@ -92,7 +92,7 @@ export function getTripUpdates( const tripUpdates: TripUpdate[] = []; while (stmt.step()) { - const row = stmt.getAsObject() as Record; + const row = stmt.getAsObject(); tripUpdates.push(parseTripUpdate(row)); } @@ -112,7 +112,7 @@ export function getTripUpdates( if (!stopTimesByTripId.has(stu.trip_id)) { stopTimesByTripId.set(stu.trip_id, []); } - stopTimesByTripId.get(stu.trip_id)!.push(stu); + stopTimesByTripId.get(stu.trip_id)?.push(stu); } // Populate each trip update's stop_time_update array @@ -145,7 +145,7 @@ export function getAllTripUpdates(db: Database): TripUpdate[] { const tripUpdates: TripUpdate[] = []; while (stmt.step()) { - const row = stmt.getAsObject() as Record; + const row = stmt.getAsObject(); tripUpdates.push(parseTripUpdate(row)); } @@ -165,7 +165,7 @@ export function getAllTripUpdates(db: Database): TripUpdate[] { if (!stopTimesByTripId.has(stu.trip_id)) { stopTimesByTripId.set(stu.trip_id, []); } - stopTimesByTripId.get(stu.trip_id)!.push(stu); + stopTimesByTripId.get(stu.trip_id)?.push(stu); } // Populate each trip update's stop_time_update array diff --git a/src/queries/rt-vehicle-positions.ts b/src/queries/rt-vehicle-positions.ts index de08851..69a4445 100644 --- a/src/queries/rt-vehicle-positions.ts +++ b/src/queries/rt-vehicle-positions.ts @@ -1,4 +1,4 @@ -import type { Database} from 'sql.js'; +import type { Database, ParamsObject } from 'sql.js'; import type { VehiclePosition } from '../types/gtfs-rt'; import type { VehiclePositionFilters } from '../types/gtfs-rt'; @@ -7,7 +7,7 @@ export type { VehiclePositionFilters }; /** * Parse vehicle position from database row */ -export function parseVehiclePosition(row: Record): VehiclePosition { +export function parseVehiclePosition(row: ParamsObject): VehiclePosition { const vp: VehiclePosition = { trip_id: String(row.trip_id), route_id: row.route_id ? String(row.route_id) : undefined, @@ -114,7 +114,7 @@ export function getVehiclePositions( const positions: VehiclePosition[] = []; while (stmt.step()) { - const row = stmt.getAsObject() as Record; + const row = stmt.getAsObject(); positions.push(parseVehiclePosition(row)); } @@ -143,7 +143,7 @@ export function getAllVehiclePositions(db: Database): VehiclePosition[] { const positions: VehiclePosition[] = []; while (stmt.step()) { - const row = stmt.getAsObject() as Record; + const row = stmt.getAsObject(); positions.push(parseVehiclePosition(row)); } diff --git a/src/queries/shapes.ts b/src/queries/shapes.ts index 17fb971..0802672 100644 --- a/src/queries/shapes.ts +++ b/src/queries/shapes.ts @@ -2,7 +2,7 @@ * Shape Query Methods */ -import type { Database } from 'sql.js'; +import type { Database, ParamsObject } from 'sql.js'; import type { Shape, Route } from '../types/gtfs'; export interface ShapeFilters { @@ -115,7 +115,7 @@ export function getShapes( const shapes: Shape[] = []; while (stmt.step()) { - const row = stmt.getAsObject() as Record; + const row = stmt.getAsObject(); shapes.push(rowToShape(row)); } @@ -224,7 +224,7 @@ function getRoutesByShapeIds( const result = new Map(); while (stmt.step()) { - const row = stmt.getAsObject() as Record; + const row = stmt.getAsObject(); const shapeId = String(row.shape_id); result.set(shapeId, rowToRoute(row)); } @@ -237,7 +237,7 @@ function getRoutesByShapeIds( /** * Convert database row to Shape object */ -function rowToShape(row: Record): Shape { +function rowToShape(row: ParamsObject): Shape { return { shape_id: String(row.shape_id), shape_pt_lat: Number(row.shape_pt_lat), @@ -250,7 +250,7 @@ function rowToShape(row: Record): Shape { /** * Convert database row to Route object */ -function rowToRoute(row: Record): Route { +function rowToRoute(row: ParamsObject): Route { return { route_id: String(row.route_id), route_short_name: row.route_short_name ? String(row.route_short_name) : '', diff --git a/src/queries/stop-times.ts b/src/queries/stop-times.ts index 6454bbd..3653a86 100644 --- a/src/queries/stop-times.ts +++ b/src/queries/stop-times.ts @@ -2,7 +2,7 @@ * Stop Time Query Methods */ -import type { Database } from 'sql.js'; +import type { Database, ParamsObject } from 'sql.js'; import type { StopTime, Stop } from '../types/gtfs'; import type { StopTimeRealtime } from '../types/gtfs-rt'; import type { PickupDropOffType } from '../types/gtfs-enums'; @@ -63,7 +63,7 @@ function mergeRealtimeData( // Build map of trip_id+stop_sequence -> RT data const rtMap = new Map(); while (stmt.step()) { - const row = stmt.getAsObject() as Record; + const row = stmt.getAsObject(); const key = `${row.trip_id}_${row.stop_sequence}`; rtMap.set(key, { arrival_delay: row.arrival_delay !== null ? Number(row.arrival_delay) : undefined, @@ -209,7 +209,7 @@ export function getStopTimes( const stopTimes: StopTime[] = []; while (stmt.step()) { - const row = stmt.getAsObject() as Record; + const row = stmt.getAsObject(); stopTimes.push(rowToStopTime(row)); } @@ -262,7 +262,7 @@ export function buildOrderedStopList(db: Database, tripIds: string[]): Stop[] { const tripStopSequences = new Map>(); while (stmt.step()) { - const row = stmt.getAsObject() as Record; + const row = stmt.getAsObject(); const tripId = String(row.trip_id); const stopId = String(row.stop_id); const stopSequence = Number(row.stop_sequence); @@ -270,7 +270,7 @@ export function buildOrderedStopList(db: Database, tripIds: string[]): Stop[] { if (!tripStopSequences.has(tripId)) { tripStopSequences.set(tripId, []); } - tripStopSequences.get(tripId)!.push({ stop_id: stopId, stop_sequence: stopSequence }); + tripStopSequences.get(tripId)?.push({ stop_id: stopId, stop_sequence: stopSequence }); } stmt.free(); @@ -377,7 +377,7 @@ function findInsertionPosition( /** * Convert database row to StopTime object */ -function rowToStopTime(row: Record): StopTime { +function rowToStopTime(row: ParamsObject): StopTime { return { trip_id: String(row.trip_id), arrival_time: String(row.arrival_time), diff --git a/src/queries/stops.ts b/src/queries/stops.ts index 823c039..69ca465 100644 --- a/src/queries/stops.ts +++ b/src/queries/stops.ts @@ -2,7 +2,7 @@ * Stop Query Methods */ -import type { Database } from 'sql.js'; +import type { Database, ParamsObject } from 'sql.js'; import type { Stop } from '../types/gtfs'; export interface StopFilters { @@ -38,7 +38,7 @@ export function getStops(db: Database, filters: StopFilters = {}): Stop[] { const stops: Stop[] = []; while (stmt.step()) { - const row = stmt.getAsObject() as Record; + const row = stmt.getAsObject(); stops.push(rowToStop(row)); } @@ -91,7 +91,7 @@ export function getStops(db: Database, filters: StopFilters = {}): Stop[] { const stops: Stop[] = []; while (stmt.step()) { - const row = stmt.getAsObject() as Record; + const row = stmt.getAsObject(); stops.push(rowToStop(row)); } @@ -110,7 +110,7 @@ export function searchStopsByName(db: Database, name: string, limit = 50): Stop[ /** * Convert database row to Stop object */ -function rowToStop(row: Record): Stop { +function rowToStop(row: ParamsObject): Stop { return { stop_id: String(row.stop_id), stop_name: String(row.stop_name), diff --git a/src/queries/trips.ts b/src/queries/trips.ts index 8e32532..cd89a03 100644 --- a/src/queries/trips.ts +++ b/src/queries/trips.ts @@ -2,7 +2,7 @@ * Trip Query Methods */ -import type { Database } from 'sql.js'; +import type { Database, ParamsObject } from 'sql.js'; import type { Trip } from '../types/gtfs'; import type { TripRealtime, VehiclePosition } from '../types/gtfs-rt'; import { parseVehiclePosition } from './rt-vehicle-positions'; @@ -47,7 +47,7 @@ function mergeRealtimeData( const vpMap = new Map(); while (vpStmt.step()) { - const row = vpStmt.getAsObject() as Record; + const row = vpStmt.getAsObject(); const vp = parseVehiclePosition(row); vpMap.set(vp.trip_id, vp); } @@ -63,7 +63,7 @@ function mergeRealtimeData( const tuMap = new Map(); while (tuStmt.step()) { - const row = tuStmt.getAsObject() as Record; + const row = tuStmt.getAsObject(); const tripId = String(row.trip_id); tuMap.set(tripId, { delay: row.delay !== null ? Number(row.delay) : undefined, @@ -173,7 +173,7 @@ export function getTrips( const trips: Trip[] = []; while (stmt.step()) { - const row = stmt.getAsObject() as Record; + const row = stmt.getAsObject(); trips.push(rowToTrip(row)); } @@ -190,7 +190,7 @@ export function getTrips( /** * Convert database row to Trip object */ -function rowToTrip(row: Record): Trip { +function rowToTrip(row: ParamsObject): Trip { return { trip_id: String(row.trip_id), route_id: String(row.route_id), diff --git a/src/utils/env.ts b/src/utils/env.ts new file mode 100644 index 0000000..875739a --- /dev/null +++ b/src/utils/env.ts @@ -0,0 +1,8 @@ +/** Returns true when running in a Node.js environment */ +export function isNodeEnvironment(): boolean { + return ( + typeof process !== 'undefined' && + process.versions != null && + process.versions.node != null + ); +}