{"id":18449442,"url":"https://github.com/public-transport/cached-hafas-client","last_synced_at":"2025-08-14T19:22:40.286Z","repository":{"id":57192530,"uuid":"146334345","full_name":"public-transport/cached-hafas-client","owner":"public-transport","description":"Pass in a HAFAS client, cache data from it.","archived":false,"fork":false,"pushed_at":"2025-01-02T22:50:02.000Z","size":246,"stargazers_count":10,"open_issues_count":7,"forks_count":1,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-08-01T19:37:19.651Z","etag":null,"topics":["cache","hafas","hafas-client","public-transport","redis","sqlite","transit"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"isc","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/public-transport.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":"license.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2018-08-27T17:57:33.000Z","updated_at":"2025-01-02T22:40:45.000Z","dependencies_parsed_at":"2025-04-30T14:32:04.100Z","dependency_job_id":"761d6fee-1ac4-4220-b4f0-cdf1d2ff5bdd","html_url":"https://github.com/public-transport/cached-hafas-client","commit_stats":{"total_commits":135,"total_committers":2,"mean_commits":67.5,"dds":0.03703703703703709,"last_synced_commit":"61db713cee3b8fa1715fd836212ae859d096bd19"},"previous_names":[],"tags_count":49,"template":false,"template_full_name":null,"purl":"pkg:github/public-transport/cached-hafas-client","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/public-transport%2Fcached-hafas-client","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/public-transport%2Fcached-hafas-client/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/public-transport%2Fcached-hafas-client/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/public-transport%2Fcached-hafas-client/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/public-transport","download_url":"https://codeload.github.com/public-transport/cached-hafas-client/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/public-transport%2Fcached-hafas-client/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":269767319,"owners_count":24472689,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-10T02:00:08.965Z","response_time":71,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cache","hafas","hafas-client","public-transport","redis","sqlite","transit"],"created_at":"2024-11-06T07:20:03.162Z","updated_at":"2025-08-14T19:22:40.242Z","avatar_url":"https://github.com/public-transport.png","language":"JavaScript","funding_links":["https://github.com/sponsors/derhuerst"],"categories":[],"sub_categories":[],"readme":"# cached-hafas-client\n\n**Pass in a [HAFAS client](https://github.com/public-transport/hafas-client/tree/6), cache data from it.**\n\n[![npm version](https://img.shields.io/npm/v/cached-hafas-client.svg)](https://www.npmjs.com/package/cached-hafas-client)\n![ISC-licensed](https://img.shields.io/github/license/public-transport/cached-hafas-client.svg)\n[![support me via GitHub Sponsors](https://img.shields.io/badge/support%20me-donate-fa7664.svg)](https://github.com/sponsors/derhuerst)\n[![chat with me on Twitter](https://img.shields.io/badge/chat%20with%20me-on%20Twitter-1da1f2.svg)](https://twitter.com/derhuerst)\n\n*Note:* This package is mainly **intended to prevent expensive and/or frequent API calls to HAFAS**. As a side effect, it *may* reduce local CPU load \u0026 latency, but that depends on the specific use case.\n\n`cached-hafas-client`'s core logic is separated from data storage code; You can pick the store implementation that fits your use case best. Right now the following stores are implemented:\n\nstore name | built on top of | notes\n-----------|-----------------|------\n[`cached-hafas-client/stores/redis.js`](stores/redis.js) | [Redis](https://redis.io/) |\n[`cached-hafas-client/stores/sqlite.js`](stores/sqlite.js) | [SQLite](https://www.sqlite.org/) | TTL not implemented yet\n[`cached-hafas-client/stores/in-memory.js`](stores/in-memory.js) | in-memory (using [`quick-lru`](https://npmjs.com/package/quick-lru)) |\n\n\n## Installation\n\n```shell\nnpm install cached-hafas-client\n```\n\n\n## Usage\n\nLet's set up a cached `hafas-client` instance.\n\n```js\n// create HAFAS client\nimport {createVbbHafas} from 'vbb-hafas'\nconst hafas = createVbbHafas('my-awesome-program')\n\n// create a store backed by Redis\nimport Redis from 'ioredis'\nimport {createRedisStore} from 'cached-hafas-client/stores/redis.js'\nconst redis = new Redis()\nconst store = createRedisStore(redis)\n\n// wrap HAFAS client with cache\nimport {createCachedHafasClient as withCache} from 'cached-hafas-client'\nconst cachedHafas = withCache(hafas, store)\n```\n\nBecause `cached-hafas-client` caches HAFAS responses by \"request signature\", it is build on the assumption that, HAFAS works deterministically, aside from the ever-changing transit data underneath. Because there are no guarantees for this, use `cached-hafas-client` with a grain of salt.\n\nThis is why **you must send deterministic queries**; for example, you *must* pass `opt.duration` to [`departures()`](https://github.com/public-transport/hafas-client/blob/6/docs/departures.md)/[`arrivals()`](https://github.com/public-transport/hafas-client/blob/6/docs/arrivals.md), so that `cached-hafas-client` knows the time frame that the list of results returned by HAFAS is for.\n\n```js\nconst wollinerStr = '900000007105'\nconst husemannstr = '900000110511'\nconst when = new Date(Date.now() + 60 * 60 * 1000)\n\n// will fetch fresh data from HAFAS\nawait cachedHafas.departures(wollinerStr, {duration: 10, when})\n\n// within the time frame of the departures() call above,\n// so it will use the cached data\nawait cachedHafas.departures(wollinerStr, {\n\tduration: 3, when: new Date(+when + 3 * 60 * 1000)\n})\n```\n\n*Note:* `cached-hafas-client` is only compatible with [`hafas-client@5`](https://github.com/public-transport/hafas-client/tree/6).\n\n### with a custom cache TTL\n\nBy default, `cached-hafas-client` uses TTLs that try to strike a balance between up-to-date-ness and a cache hit ratio: The caching duration depends on how far in the future you query for.\n\nYou can pass custom cache TTLs per `hafas-client` method, either as static values or as a function returning the cache TTL based on the arguments.\n\n```js\nconst SECOND = 1000\nconst MINUTE = 60 * SECOND\n\nconst cachePeriods = {\n\t// cache all cachedHafas.stop(…) calls for 10m\n\tstop: 10 * MINUTE,\n\t// cache cachedHafas.trip(tripId, opt) based on sqrt(opt.when - now)\n\ttrip: (_, opt = {}) =\u003e {\n\t\tconst diffSecs = (new Date(opt.when) - Date.now()) / SECOND\n\t\tif (Number.isNaN(diffSecs)) return 10 * SECOND // fallback\n\t\treturn Math.round(Math.pow(diffSecs, 1/2) * SECOND)\n\t},\n}\nconst cachedHafas = withCache(hafas, store, {cachePeriods})\n```\n\n### Counting cache hits \u0026 misses\n\n```js\ncachedHafas.on('hit', (hafasClientMethod, ...args) =\u003e {\n\tconsole.info('cache hit!', hafasClientMethod, ...args)\n})\ncachedHafas.on('miss', (hafasClientMethod, ...args) =\u003e {\n\tconsole.info('cache miss!', hafasClientMethod, ...args)\n})\n```\n\n### Bypassing the cache\n\n```js\nimport {CACHED} from 'cached-hafas-client'\n\n// will always fresh data\nawait cachedHafas.departures(wollinerStr, {[CACHED]: false})\n```\n\n### tracking hits \u0026 misses as Prometheus metrics\n\nYou can optionally track the number of hits \u0026 misses as two [Prometheus Counters](https://prometheus.io/docs/concepts/metric_types/#counter) `cached_hafas_client_hits_total` \u0026 `cached_hafas_client_misses_total`, respectively:\n\n```js\nimport {trackCachingMetrics} from 'cached-hafas-client/with-metrics.js'\n\ntrackCachingMetrics(cachedHafas) // will keep metrics now\n```\n\n\n## API\n\n```js\ncreateCachedHafas(hafas, storage, opt = {})\n```\n\n`hafas` must be a [`hafas-client@6`](https://github.com/public-transport/hafas-client/tree/6)-compatible API client.\n\n`opt` overrides this default configuration:\n\n```js\n{\n\tcachePeriods: {\n\t\tdepartures: 30_1000, arrivals: 30_1000, // 30s\n\t\tjourneys: 30_1000, // 30s\n\t\trefreshJourney: 60_1000, // 1m\n\t\ttrip: 30_1000, // 30s\n\t\tradar: 10_1000, // 10s\n\t\tlocations: 3_600_1000, // 1h\n\t\tstop: 3_600_1000, // 1h\n\t\tnearby: 3_600_1000, // 1h\n\t\treachableFrom: 30_1000,\n\t},\n}\n```\n\n\n## Contributing\n\nIf you have a question or need support using `cached-hafas-client`, please double-check your code and setup first. If you think you have found a bug or want to propose a feature, use [the issues page](https://github.com/public-transport/cached-hafas-client/issues).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpublic-transport%2Fcached-hafas-client","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpublic-transport%2Fcached-hafas-client","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpublic-transport%2Fcached-hafas-client/lists"}