diff --git a/docs/readme.md b/docs/readme.md index a3f42520..0f6452ce 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -21,13 +21,12 @@ There's opt-in support for throttling requests to the endpoint. ```js -const withThrottling = require('hafas-client/throttle') const createClient = require('hafas-client') +const withThrottling = require('hafas-client/throttle') const dbProfile = require('hafas-client/p/db') // create a throttled HAFAS client with Deutsche Bahn profile -const createThrottledClient = withThrottling(createClient) -const client = createThrottledClient(dbProfile, 'my-awesome-program') +const client = createClient(withThrottling(dbProfile), 'my-awesome-program') // Berlin Jungfernheide to München Hbf client.journeys('8011167', '8000261', {results: 1}) @@ -39,8 +38,8 @@ You can pass custom values for the nr of requests (`limit`) per interval into `w ```js // 2 requests per second -const createThrottledClient = withThrottling(createClient, 2, 1000) -const client = createThrottledClient(dbProfile, 'my-awesome-program') +const throttledDbProfile = withThrottling(dbProfile, 2, 1000) +const client = createClient(throttledDbProfile, 'my-awesome-program') ``` ## Retrying failed requests @@ -48,13 +47,12 @@ const client = createThrottledClient(dbProfile, 'my-awesome-program') There's opt-in support for retrying failed requests to the endpoint. ```js -const withRetrying = require('hafas-client/retry') const createClient = require('hafas-client') +const withRetrying = require('hafas-client/retry') const dbProfile = require('hafas-client/p/db') // create a client with Deutsche Bahn profile that will retry on HAFAS errors -const createRetryingClient = withRetrying(createClient) -const client = createRetryingClient(dbProfile, 'my-awesome-program') +const client = createClient(withRetrying(dbProfile), 'my-awesome-program') // Berlin Jungfernheide to München Hbf client.journeys('8011167', '8000261', {results: 1}) @@ -66,12 +64,12 @@ You can pass custom options into `withRetrying`. They will be passed into [`retr ```js // retry 2 times, after 10 seconds & 30 seconds -const createRetryingClient = withRetrying(createClient, { +const retryingDbProfile = withRetrying(dbProfile, { retries: 2, minTimeout: 10 * 1000, factor: 3 }) -const client = createRetryingClient(dbProfile, 'my-awesome-program') +const client = createClient(retryingDbProfile, 'my-awesome-program') ``` ## Writing a profile diff --git a/index.js b/index.js index 1b2514b8..a4350704 100644 --- a/index.js +++ b/index.js @@ -9,7 +9,6 @@ const pRetry = require('p-retry') const defaultProfile = require('./lib/default-profile') const createFormatProductsFilter = require('./format/products-filter') const validateProfile = require('./lib/validate-profile') -const _request = require('./lib/request') const isNonEmptyString = str => 'string' === typeof str && str.length > 0 @@ -25,10 +24,6 @@ const validateLocation = (loc, name = 'location') => { } } -const defaults = { - request: _request -} - const createClient = (profile, userAgent, opt = {}) => { profile = Object.assign({}, defaultProfile, profile) if (!profile.formatProductsFilter) { @@ -40,10 +35,6 @@ const createClient = (profile, userAgent, opt = {}) => { throw new TypeError('userAgent must be a string'); } - const { - request - } = Object.assign({}, defaults, opt) - const _stationBoard = (station, type, parse, opt = {}) => { if (isObj(station)) station = profile.formatStation(station.id) else if ('string' === typeof station) station = profile.formatStation(station) @@ -88,7 +79,7 @@ const createClient = (profile, userAgent, opt = {}) => { if (profile.departuresGetPasslist) req.getPasslist = !!opt.stopovers if (profile.departuresStbFltrEquiv) req.stbFltrEquiv = !opt.includeRelatedStations - return request({profile, opt}, userAgent, { + return profile.request({profile, opt}, userAgent, { meth: 'StationBoard', req }) @@ -230,7 +221,7 @@ const createClient = (profile, userAgent, opt = {}) => { if (profile.journeysNumF && opt.results !== null) query.numF = opt.results if (profile.journeysOutFrwd) query.outFrwd = outFrwd - return request({profile, opt}, userAgent, { + return profile.request({profile, opt}, userAgent, { cfg: {polyEnc: 'GPA'}, meth: 'TripSearch', req: profile.transformJourneysQuery({profile, opt}, query) @@ -277,7 +268,7 @@ const createClient = (profile, userAgent, opt = {}) => { remarks: true // parse & expose hints & warnings? }, opt) - return request({profile, opt}, userAgent, { + return profile.request({profile, opt}, userAgent, { meth: 'Reconstruction', req: { ctxRecon: refreshToken, @@ -311,7 +302,7 @@ const createClient = (profile, userAgent, opt = {}) => { }, opt) const f = profile.formatLocationFilter(opt.stops, opt.addresses, opt.poi) - return request({profile, opt}, userAgent, { + return profile.request({profile, opt}, userAgent, { cfg: {polyEnc: 'GPA'}, meth: 'LocMatch', req: {input: { @@ -339,7 +330,7 @@ const createClient = (profile, userAgent, opt = {}) => { opt = Object.assign({ linesOfStops: false // parse & expose lines at the stop/station? }, opt) - return request({profile, opt}, userAgent, { + return profile.request({profile, opt}, userAgent, { meth: 'LocDetails', req: { locL: [stop] @@ -367,7 +358,7 @@ const createClient = (profile, userAgent, opt = {}) => { linesOfStops: false // parse & expose lines at each stop/station? }, opt) - return request({profile, opt}, userAgent, { + return profile.request({profile, opt}, userAgent, { cfg: {polyEnc: 'GPA'}, meth: 'LocGeoPos', req: { @@ -405,7 +396,7 @@ const createClient = (profile, userAgent, opt = {}) => { remarks: true // parse & expose hints & warnings? }, opt) - return request({profile, opt}, userAgent, { + return profile.request({profile, opt}, userAgent, { cfg: {polyEnc: 'GPA'}, meth: 'JourneyDetails', req: { @@ -454,7 +445,7 @@ const createClient = (profile, userAgent, opt = {}) => { if (Number.isNaN(+opt.when)) throw new TypeError('opt.when is invalid') const durationPerStep = opt.duration / Math.max(opt.frames, 1) * 1000 - return request({profile, opt}, userAgent, { + return profile.request({profile, opt}, userAgent, { meth: 'JourneyGeoPos', req: { maxJny: opt.results, @@ -492,7 +483,7 @@ const createClient = (profile, userAgent, opt = {}) => { if (Number.isNaN(+opt.when)) throw new TypeError('opt.when is invalid') const refetch = () => { - return request({profile, opt}, userAgent, { + return profile.request({profile, opt}, userAgent, { meth: 'LocGeoReach', req: { loc: profile.formatLocation(profile, address, 'address'), diff --git a/lib/default-profile.js b/lib/default-profile.js index 9d501cf5..62572667 100644 --- a/lib/default-profile.js +++ b/lib/default-profile.js @@ -1,5 +1,7 @@ 'use strict' +const request = require('../lib/request') + const parseDateTime = require('../parse/date-time') const parsePlatform = require('../parse/platform') const parseProductsBitmask = require('../parse/products-bitmask') @@ -34,13 +36,13 @@ const filters = require('../format/filters') const id = (ctx, x) => x const defaultProfile = { + request, + transformReqBody: id, + transformReq: id, salt: null, addChecksum: false, addMicMac: false, - transformReqBody: id, - transformReq: id, - transformJourneysQuery: id, parseDateTime, diff --git a/lib/validate-profile.js b/lib/validate-profile.js index 4c7f6374..6a08fcb6 100644 --- a/lib/validate-profile.js +++ b/lib/validate-profile.js @@ -3,6 +3,8 @@ const types = { locale: 'string', timezone: 'string', + + request: 'function', transformReq: 'function', transformReqBody: 'function', transformJourneysQuery: 'function', diff --git a/retry.js b/retry.js index cafe160b..3a33b3cb 100644 --- a/retry.js +++ b/retry.js @@ -2,42 +2,36 @@ const retry = require('p-retry') -const _request = require('./lib/request') - const retryDefaults = { retries: 3, factor: 3, minTimeout: 5 * 1000 } -const withRetrying = (createClient, retryOpts = {}) => { +const withRetrying = (profile, retryOpts = {}) => { retryOpts = Object.assign({}, retryDefaults, retryOpts) + const {request} = profile - const createRetryingClient = (profile, userAgent, opt = {}) => { - const request = 'request' in opt ? opt.request : _request - - const retryingRequest = (profile, userAgent, opt, data) => { - const attempt = () => { - return request(profile, userAgent, opt, data) - .catch((err) => { - if (err.isHafasError) throw err // continue - if (err.code === 'ENOTFOUND') { // abort - const abortErr = new retry.AbortError(err) - Object.assign(abortErr, err) - throw abortErr - } - throw err // continue - }) - } - return retry(attempt, retryOpts) + const retryingRequest = (...args) => { + const attempt = () => { + return request(...args) + .catch((err) => { + if (err.isHafasError) throw err // continue + if (err.code === 'ENOTFOUND') { // abort + const abortErr = new retry.AbortError(err) + Object.assign(abortErr, err) + throw abortErr + } + throw err // continue + }) } - - return createClient(profile, userAgent, { - ...opt, - request: retryingRequest - }) + return retry(attempt, retryOpts) + } + + return { + ...profile, + request: retryingRequest } - return createRetryingClient } module.exports = withRetrying diff --git a/test/retry.js b/test/retry.js index 86ce4b02..1ef371a8 100644 --- a/test/retry.js +++ b/test/retry.js @@ -2,8 +2,8 @@ const test = require('tape') -const withRetrying = require('../retry') const createClient = require('..') +const withRetrying = require('../retry') const vbbProfile = require('../p/vbb') const userAgent = 'public-transport/hafas-client:test' @@ -12,7 +12,7 @@ const spichernstr = '900000042101' test('withRetrying works', (t) => { // for the first 3 calls, return different kinds of errors let calls = 0 - const failingRequest = (profile, userAgent, opt, data) => { + const failingRequest = async (ctx, userAgent, reqData) => { calls++ if (calls === 1) { const err = new Error('HAFAS error') @@ -25,18 +25,22 @@ test('withRetrying works', (t) => { return Promise.reject(err) } if (calls < 4) return Promise.reject(new Error('generic error')) - return Promise.resolve([]) + return { + res: [], + common: {} + } } - const createRetryingClient = withRetrying(createClient, { + const profile = withRetrying({ + ...vbbProfile, + request: failingRequest + }, { retries: 3, minTimeout: 100, factor: 2, randomize: false }) - const client = createRetryingClient(vbbProfile, userAgent, { - request: failingRequest - }) + const client = createClient(profile, userAgent) t.plan(1 + 4) client.departures(spichernstr, {duration: 1}) diff --git a/test/throttle.js b/test/throttle.js index 79c63866..3de45479 100644 --- a/test/throttle.js +++ b/test/throttle.js @@ -3,8 +3,8 @@ const tapePromise = require('tape-promise').default const tape = require('tape') -const withThrottling = require('../throttle') const createClient = require('..') +const withThrottling = require('../throttle') const vbbProfile = require('../p/vbb') const depsRes = require('./fixtures/vbb-departures.json') @@ -14,10 +14,8 @@ const spichernstr = '900000042101' const test = tapePromise(tape) test('withThrottling works', async (t) => { - const ctx = {profile: vbbProfile, opt: {}} - let calls = 0 - const mockedRequest = async (ctx, _, reqData) => { + const mockedRequest = async (ctx, userAgent, reqData) => { calls++ return { res: depsRes, @@ -25,8 +23,11 @@ test('withThrottling works', async (t) => { } } - const createThrottledClient = withThrottling(createClient, 2, 1000) - const client = createThrottledClient(vbbProfile, ua, mockedRequest) + const profile = withThrottling({ + ...vbbProfile, + request: mockedRequest + }, 2, 1000) + const client = createClient(profile, ua) t.plan(3) for (let i = 0; i < 10; i++) { diff --git a/throttle.js b/throttle.js index b003011a..233a7c7b 100644 --- a/throttle.js +++ b/throttle.js @@ -2,20 +2,13 @@ const throttle = require('p-throttle') -const _request = require('./lib/request') +const withThrottling = (profile, limit = 5, interval = 1000) => { + const {request} = profile -const withThrottling = (createClient, limit = 5, interval = 1000) => { - const createThrottledClient = (profile, userAgent, opt = {}) => { - const request = 'request' in opt ? opt.request : _request - - const throttledRequest = throttle(request, limit, interval) - - return createClient(profile, userAgent, { - ...opt, - request: throttledRequest - }) + return { + ...profile, + request: throttle(request, limit, interval) } - return createThrottledClient } module.exports = withThrottling