Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 0 additions & 15 deletions .github/actions/setup/action.yml

This file was deleted.

7 changes: 5 additions & 2 deletions .github/workflows/integrate.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
on:
workflow_call:
jobs:
eslint:
oxfmt:
name: vite
uses: umts/.github/.github/workflows/eslint.yml@main
uses: umts/.github/.github/workflows/oxfmt.yml@main
oxlint:
name: vite
uses: umts/.github/.github/workflows/oxlint.yml@main
vite:
name: vite
uses: umts/.github/.github/workflows/vite.yml@main
Expand Down
26 changes: 13 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@ Note that due to dependency constraints, this library must be processed by a bun
*
* The data is unzipped and its entries are parsed as CSV files internally. File names and properties are converted
* from snake_case to camelCase (see the standard for structure).
*
*
* Individual files are parsed asynchronously, and may be updated/made available at different times. Be careful to guard
* against this when accessing your data.
*
* @example
*
* const scheduleData = useGtfsSchedule(yourResolver, 10000)
*
*
* scheduleData?.routes.each(...) // improper
* scheduleData?.routes?.each(...) // proper
*
Expand All @@ -47,7 +47,7 @@ Note that due to dependency constraints, this library must be processed by a bun
* @param {Number} [retry=1000] - the time in ms to retry a refresh if the previous one failed.
* @return {{}|undefined} parsed data if resolved, undefined if not.
*/
export function useGtfsSchedule (resolve, timeout) { }
export function useGtfsSchedule(resolve, timeout) {}

/**
* A hook that resolves, parses and periodically refreshes GTFS Realtime data.
Expand All @@ -60,7 +60,7 @@ export function useGtfsSchedule (resolve, timeout) { }
* @param {Number} [retry=1000] - the time in ms to retry a refresh if the previous one failed.
* @return {FeedMessage|undefined} parsed data if resolved, undefined if not.
*/
export function useGtfsRealtime (resolve, timeout) { }
export function useGtfsRealtime(resolve, timeout) {}

/**
* A convenience hook that creates a simple GTFS data resolver using the fetch API.
Expand All @@ -69,31 +69,31 @@ export function useGtfsRealtime (resolve, timeout) { }
* return the raw response body as a Uint8Array.
*
* If any errors occur or if the response status is not OK, it will return undefined.
*
*
* If you need to use a different API for retrieving your data, use more advanced fetch options or add side effects
* when errors occur, it is reccommended that you write your own resolver.
*
* @param {string} url - the url to send requests to.
* @return {Resolver} a simple fetch resolver.
*/
export function useFetchResolver (url) { }
export function useFetchResolver(url) {}
```

### Examples

```javascript
import { useFetchResolver, useGtfsRealtime, useGtfsSchedule } from 'gtfs-react-hooks'
import { useCallback } from 'react'
import { useFetchResolver, useGtfsRealtime, useGtfsSchedule } from "gtfs-react-hooks";
import { useCallback } from "react";

export default function MyComponent() {
// gtfs schedule data
const scheduleResolver = useFetchResolver('https://your-domain.com/gtfs_schedule.zip')
const gtfsSchedule = useGtfsSchedule(scheduleResolver, 1000 * 60 * 60 * 24)
const scheduleResolver = useFetchResolver("https://your-domain.com/gtfs_schedule.zip");
const gtfsSchedule = useGtfsSchedule(scheduleResolver, 1000 * 60 * 60 * 24);

// gtfs realtime data
const realtimeAlertsResolver = useFetchResolver('https://your-domain.com/gtfs-realtime-alerts')
const gtfsRealtimeAlerts = useGtfsRealtime(realtimeAlertsResolver, 1000 * 30)
const realtimeAlertsResolver = useFetchResolver("https://your-domain.com/gtfs-realtime-alerts");
const gtfsRealtimeAlerts = useGtfsRealtime(realtimeAlertsResolver, 1000 * 30);

// ...
}
```
Expand Down
9 changes: 0 additions & 9 deletions eslint.config.js

This file was deleted.

8 changes: 4 additions & 4 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import useFetchResolver from './useFetchResolver.js'
import useGtfsRealtime from './useGtfsRealtime.js'
import useGtfsSchedule from './useGtfsSchedule.js'
import useFetchResolver from "./useFetchResolver.js";
import useGtfsRealtime from "./useGtfsRealtime.js";
import useGtfsSchedule from "./useGtfsSchedule.js";

export { useFetchResolver, useGtfsRealtime, useGtfsSchedule }
export { useFetchResolver, useGtfsRealtime, useGtfsSchedule };
16 changes: 7 additions & 9 deletions lib/useFetchResolver.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import { useCallback } from 'react'
import { useCallback } from "react";

export default function useFetchResolver (url) {
export default function useFetchResolver(url) {
return useCallback(async () => {
try {
const response = await fetch(url)
if (response.status !== 200) {
return undefined
} else {
return new Uint8Array(await response.arrayBuffer())
const response = await fetch(url);
if (response.status === 200) {
return new Uint8Array(await response.arrayBuffer());
}
} catch {
return undefined
// do nothing
}
}, [url])
}, [url]);
}
18 changes: 8 additions & 10 deletions lib/useGtfsRealtime.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import GtfsRealtimeBindings from 'gtfs-realtime-bindings'
import { useMemo } from 'react'
import useRefresh from './useRefresh.js'
import GtfsRealtimeBindings from "gtfs-realtime-bindings";
import { useMemo } from "react";
import useRefresh from "./useRefresh.js";

export default function useGtfsRealtime (resolve, timeout, retry = 1000) {
const realtimeUInt8Buffer = useRefresh(resolve, timeout, retry)
export default function useGtfsRealtime(resolve, timeout, retry = 1000) {
const realtimeUInt8Buffer = useRefresh(resolve, timeout, retry);
return useMemo(() => {
if (realtimeUInt8Buffer === undefined) {
return realtimeUInt8Buffer
} else {
return GtfsRealtimeBindings.transit_realtime.FeedMessage.decode(realtimeUInt8Buffer)
if (realtimeUInt8Buffer !== undefined) {
return GtfsRealtimeBindings.transit_realtime.FeedMessage.decode(realtimeUInt8Buffer);
}
}, [realtimeUInt8Buffer])
}, [realtimeUInt8Buffer]);
}
53 changes: 31 additions & 22 deletions lib/useGtfsSchedule.js
Original file line number Diff line number Diff line change
@@ -1,41 +1,50 @@
import { camelCase } from 'change-case'
import JSZip from 'jszip'
import Papa from 'papaparse'
import { useEffect, useRef, useState } from 'react'
import useRefresh from './useRefresh.js'
import { camelCase } from "change-case";
import JSZip from "jszip";
import Papa from "papaparse";
import { useEffect, useRef, useState } from "react";
import useRefresh from "./useRefresh.js";

export default function useGtfsSchedule (resolve, timeout, retry = 1000) {
const rawScheduleData = useRefresh(resolve, timeout, retry)
const dataCache = useRef({})
const [data, setData] = useState(undefined)
export default function useGtfsSchedule(resolve, timeout, retry = 1000) {
const rawScheduleData = useRefresh(resolve, timeout, retry);
const dataCache = useRef({});
const [data, setData] = useState();
useEffect(() => {
if (rawScheduleData === undefined) {
setData(undefined)
setData(undefined);
} else {
// oxlint-disable-next-line promise/catch-or-return
JSZip.loadAsync(rawScheduleData).then((zip) => {
const parses = [];
for (const [filename, file] of Object.entries(zip.files)) {
file.async('text').then(parseCsv).then((json) => {
dataCache.current[camelCase(removeExtension(filename))] = json
setData({ ...dataCache.current })
})
const parse = file
.async("text")
.then(parseCsv)
.then((json) => {
dataCache.current[camelCase(removeExtension(filename))] = json;
setData({ ...dataCache.current });
return json;
});
parses.push(parse);
}
})
return Promise.all(parses);
});
}
}, [rawScheduleData])
return data
}, [rawScheduleData]);
return data;
}

async function parseCsv (csvString) {
function parseCsv(csvString) {
return new Promise((resolve, reject) => {
Papa.parse(csvString, {
header: true,
transformHeader: camelCase,
skipEmptyLines: true,
complete: (result) => resolve(result.data),
})
})
error: reject,
});
});
}

function removeExtension (filename) {
return filename.replace(/\.[^/.]+$/, '')
function removeExtension(filename) {
return filename.replace(/\.[^/.]+$/u, "");
}
28 changes: 15 additions & 13 deletions lib/useRefresh.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,29 @@
import { useCallback, useEffect, useState } from 'react'
import { useCallback, useEffect, useState } from "react";

export default function useRefresh (resolve, timeout, retry) {
const [data, setData] = useState()
const refresh = useCallback(async () => { setData(await resolve()) }, [resolve])
export default function useRefresh(resolve, timeout, retry) {
const [data, setData] = useState();
const refresh = useCallback(async () => {
setData(await resolve());
}, [resolve]);

// resolve immediately on render
useEffect(() => {
refresh()
}, [refresh])
refresh();
}, [refresh]);

// resolve periodically according to retry if resolve failed previously
useEffect(() => {
if (data === undefined) {
const interval = setInterval(refresh, retry)
return () => clearInterval(interval)
const interval = setInterval(refresh, retry);
return () => clearInterval(interval);
}
}, [data, refresh, retry])
}, [data, refresh, retry]);

// resolve periodically according to timeout
useEffect(() => {
const interval = setInterval(refresh, timeout)
return () => clearInterval(interval)
}, [refresh, timeout])
const interval = setInterval(refresh, timeout);
return () => clearInterval(interval);
}, [refresh, timeout]);

return data
return data;
}
6 changes: 6 additions & 0 deletions oxfmt.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { defineConfig } from "oxfmt";

export default defineConfig({
$schema: "./node_modules/oxfmt/configuration_schema.json",
ignorePatterns: ["CHANGELOG.md"],
});
29 changes: 29 additions & 0 deletions oxlint.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { defineConfig } from "oxlint";

export default defineConfig({
$schema: "./node_modules/oxlint/configuration_schema.json",
plugins: ["unicorn", "react", "react-perf", "oxc", "import", "promise", "vitest"],
categories: {
correctness: "error",
suspicious: "warn",
pedantic: "warn",
perf: "error",
restriction: "error",
},
rules: {
"no-undefined": "off",
"import/no-default-export": "off",
"import/no-relative-parent-imports": "off",
"oxc/no-async-await": "off",
"oxc/no-rest-spread-properties": "off",
},
overrides: [
{
files: ["*.test.js"],
rules: {
"max-lines-per-function": "off",
"vitest/require-test-timeout": "off",
},
},
],
});
Loading