From 063a10c17ab00356101fdf410d3fb3f304854923 Mon Sep 17 00:00:00 2001 From: shinymeta Date: Mon, 14 Jun 2021 15:19:26 -0400 Subject: [PATCH 01/12] auto-batching functionality --- src/client.js | 22 ++++++++++++++++++ src/endpoint.js | 62 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 83 insertions(+), 1 deletion(-) diff --git a/src/client.js b/src/client.js index 2ef42d6..3a5d92a 100644 --- a/src/client.js +++ b/src/client.js @@ -12,6 +12,7 @@ module.exports = class Client { this.caches = [nullCache()] this.debug = false this.client = this + this.autoBatchPool = {} } // Set the schema version @@ -79,6 +80,27 @@ module.exports = class Client { }) } + //maintains pool of static endpoints with auto-batching enabled + autoBatch (endpointName, autoBatchInterval = 1000) { + if (this.autoBatchPool[endpointName]) { + return this.autoBatchPool[endpointName] + } + + if (!this[endpointName]) { + return new Error(`no enpoint ${endpointName} found`) + } + + const resultEndpoint = this[endpointName]() + if (resultEndpoint.isBulk) { + this.autoBatchPool[endpointName] = resultEndpoint.enableAutoBatch(autoBatchInterval) + } + else { + this.debugMessage(`${endpointName} is not bulk expanding, endpoint will not have any autobatch behavior`) + } + + return resultEndpoint + } + // All the different API endpoints account () { return new endpoints.AccountEndpoint(this) diff --git a/src/endpoint.js b/src/endpoint.js index 7d30672..0b8bec3 100644 --- a/src/endpoint.js +++ b/src/endpoint.js @@ -25,6 +25,8 @@ module.exports = class AbstractEndpoint { this.isOptionallyAuthenticated = false this.credentials = false + this._autoBatch = null + this._skipCache = false } @@ -74,6 +76,22 @@ module.exports = class AbstractEndpoint { return this } + //turns on auto-batching for this endpoint + enableAutoBatch (interval) { + if (this._autoBatch === null) { + this._autoBatch = { + interval: interval || 1000, + set: new Set(), + nextBatchPromise: null, + autoBatchOverride: false, + } + } + if (interval) { + this._autoBatch.interval = interval + } + return this + } + // Get all ids ids () { this.debugMessage(`ids(${this.url}) called`) @@ -156,7 +174,14 @@ module.exports = class AbstractEndpoint { // Request the single id if the endpoint a bulk endpoint if (this.isBulk && !url) { - return this._request(`${this.url}?id=${id}`) + if (this._autoBatch === null) { + return this._request(`${this.url}?id=${id}`) + } + else { + return this._autoBatchMany([id]).then((items) => { + return items[0]?items[0]:null + }) + } } // We are dealing with a custom url instead @@ -168,6 +193,36 @@ module.exports = class AbstractEndpoint { return this._request(this.url) } + _autoBatchMany (ids) { + if (!this._autoBatch.set) { + this._autoBatch.set = new Set() + } + if (this._autoBatch.set.size === 0) { + this._autoBatch.nextBatchPromise = new Promise((resolve, reject) => { + setTimeout(() => { + const batchedIds = Array.from(this._autoBatch.set) + this.debugMessage(`batch sending for ${batchedIds}`) + this._autoBatch.set.clear() + this._autoBatch.autoBatchOverride = true + return resolve(this.many(batchedIds)) + }, this._autoBatch.interval) // by default is 1000, 1 sec + }).then(items => { + const indexedItems = {} + items.forEach(item => { + indexedItems[item.id] = item + }) + return indexedItems + }) + } + + //add ids to set + ids.forEach(id => this._autoBatch.set.add(id)) + // return array with results requested + return this._autoBatch.nextBatchPromise.then(indexedItems => { + return ids.map(id => indexedItems[id]).filter(x => x) + }) + } + // Get multiple entries by ids many (ids) { this.debugMessage(`many(${this.url}) called (${ids.length} ids)`) @@ -233,6 +288,11 @@ module.exports = class AbstractEndpoint { _many (ids, partialRequest = false) { this.debugMessage(`many(${this.url}) requesting from api (${ids.length} ids)`) + if (this._autoBatch !== null && !this._autoBatch.autoBatchOverride) { + return this._autoBatchMany(ids) + } + this._autoBatch.autoBatchOverride = false + // Chunk the requests to the max page size const pages = chunk(ids, this.maxPageSize) const requests = pages.map(page => `${this.url}?ids=${page.join(',')}`) From 02849fc42c364a004a3c99de0605fa3d2071369c Mon Sep 17 00:00:00 2001 From: shinymeta Date: Sat, 19 Jun 2021 02:20:26 -0400 Subject: [PATCH 02/12] endpoint tests + small code fixes --- src/endpoint.js | 17 ++++++----- tests/endpoint.spec.js | 64 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 7 deletions(-) diff --git a/src/endpoint.js b/src/endpoint.js index 0b8bec3..29ec7ad 100644 --- a/src/endpoint.js +++ b/src/endpoint.js @@ -19,7 +19,7 @@ module.exports = class AbstractEndpoint { this.isPaginated = false this.maxPageSize = 200 this.isBulk = false - this.supportsBulkAll = true + this.supportsBulkAll = true this.isLocalized = false this.isAuthenticated = false this.isOptionallyAuthenticated = false @@ -86,9 +86,6 @@ module.exports = class AbstractEndpoint { autoBatchOverride: false, } } - if (interval) { - this._autoBatch.interval = interval - } return this } @@ -288,10 +285,16 @@ module.exports = class AbstractEndpoint { _many (ids, partialRequest = false) { this.debugMessage(`many(${this.url}) requesting from api (${ids.length} ids)`) - if (this._autoBatch !== null && !this._autoBatch.autoBatchOverride) { - return this._autoBatchMany(ids) + if (this._autoBatch !== null) { + if (this._autoBatch.autoBatchOverride) { + this._autoBatch.autoBatchOverride = false + } + else { + return this._autoBatchMany(ids) + } } - this._autoBatch.autoBatchOverride = false + + // Chunk the requests to the max page size const pages = chunk(ids, this.maxPageSize) diff --git a/tests/endpoint.spec.js b/tests/endpoint.spec.js index cbe3761..2352448 100644 --- a/tests/endpoint.spec.js +++ b/tests/endpoint.spec.js @@ -108,6 +108,70 @@ describe('abstract endpoint', () => { }) }) + describe('auto batching', () => { + const interval = 10 + beforeEach(() => { + endpoint.enableAutoBatch(interval) + }) + + it('sets up _autoBatch variable', () => { + let x = endpoint.enableAutoBatch(interval) + expect(x).toBeInstanceOf(Module) + expect(x._autoBatch.interval).toEqual(interval) + expect(x._autoBatch.set).toBeDefined() + expect(x._autoBatch.nextBatchPromise).toBeNull() + expect(x._autoBatch.autoBatchOverride).toEqual(false) + }) + + it('supports batching from get', async () => { + let content = [{ id: 1, name: 'foo' }, { id: 2, name: 'bar' }] + endpoint.isBulk = true + endpoint.url = '/v2/test' + // endpoint.enableAutoBatch(10) + fetchMock.addResponse(content) + + let [entry1, entry2] = await Promise.all([endpoint.get(1), endpoint.get(2)]) + expect(fetchMock.lastUrl()).toEqual('https://api.guildwars2.com/v2/test?v=schema&ids=1,2') + expect(entry1).toEqual(content[0]) + expect(entry2).toEqual(content[1]) + }) + + it('supports batching from many', async () => { + let content = [{ id: 1, name: 'foo' }, { id: 2, name: 'bar' }, { id: 3, name: 'bar' }] + endpoint.isBulk = true + endpoint.url = '/v2/test' + // endpoint.enableAutoBatch(10) + fetchMock.addResponse(content) + + let [entry1, entry2] = await Promise.all([endpoint.many([1,2]), endpoint.many([2,3])]) + expect(fetchMock.lastUrl()).toEqual('https://api.guildwars2.com/v2/test?v=schema&ids=1,2,3') + expect(entry1).toEqual([content[0],content[1]]) + expect(entry2).toEqual([content[1],content[2]]) + }) + + it('only batches requests during the interval', async () => { + let content1 = [{ id: 1, name: 'foo' }] + let content2 = [{ id: 2, name: 'bar' }] + endpoint.isBulk = true + endpoint.url = '/v2/test' + // endpoint.enableAutoBatch(10) + fetchMock.addResponse(content1) + fetchMock.addResponse(content2) + + let [entry1, entry2] = await Promise.all([ + endpoint.get(1), + new Promise((resolve) => {setTimeout(() => {resolve(endpoint.get(2))}, 11)}) + ]) + expect(fetchMock.urls()).toEqual([ + 'https://api.guildwars2.com/v2/test?v=schema&ids=1', + 'https://api.guildwars2.com/v2/test?v=schema&ids=2' + ]) + expect(entry1).toEqual(content1[0]) + expect(entry2).toEqual(content2[0]) + + }) + }) + describe('get', () => { it('support for bulk expanding', async () => { let content = { id: 1, name: 'foo' } From 69094781518ee4ae0e18c8f2b75b08f55d0bcde8 Mon Sep 17 00:00:00 2001 From: shinymeta Date: Sat, 19 Jun 2021 02:45:47 -0400 Subject: [PATCH 03/12] client tests --- tests/client.spec.js | 23 +++++++++++++++++++++++ tests/endpoint.spec.js | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/tests/client.spec.js b/tests/client.spec.js index 9046a0d..2383dac 100644 --- a/tests/client.spec.js +++ b/tests/client.spec.js @@ -123,6 +123,29 @@ describe('client', () => { client.build = tmp }) + describe('autobatch', () => { + const interval = 10 + it('can get an autobatching endpoint', () => { + let endpoint = client.autoBatch('items', interval) + expect(endpoint.url).toEqual('/v2/items') + expect(endpoint._autoBatch.interval).toEqual(interval) + }) + + it('adds the endpoint to the pool', () => { + client.autoBatch('items', interval) + expect(client.autoBatchPool.items).toBeDefined() + }) + + it('returns the same endpoint on subsequent calls', () => { + let endpoint1 = client.autoBatch('items', interval) + endpoint1.arbitraryPropertyNameForTesting = 'test confirmed' + let endpoint2 = client.autoBatch('items', interval) + expect(endpoint2.arbitraryPropertyNameForTesting).toEqual('test confirmed') + }) + + + }) + it('can get the account endpoint', () => { let endpoint = client.account() expect(endpoint.url).toEqual('/v2/account') diff --git a/tests/endpoint.spec.js b/tests/endpoint.spec.js index 2352448..488912d 100644 --- a/tests/endpoint.spec.js +++ b/tests/endpoint.spec.js @@ -160,7 +160,7 @@ describe('abstract endpoint', () => { let [entry1, entry2] = await Promise.all([ endpoint.get(1), - new Promise((resolve) => {setTimeout(() => {resolve(endpoint.get(2))}, 11)}) + new Promise((resolve) => {setTimeout(() => {resolve(endpoint.get(2))}, interval+1)}) ]) expect(fetchMock.urls()).toEqual([ 'https://api.guildwars2.com/v2/test?v=schema&ids=1', From 05ec646ce0f6e2392ac15f00d3cf3a8f1d17ce7a Mon Sep 17 00:00:00 2001 From: shinymeta Date: Sun, 20 Jun 2021 14:39:43 -0400 Subject: [PATCH 04/12] code tweaks, test coverage --- src/client.js | 4 ++-- src/endpoint.js | 7 ++----- tests/client.spec.js | 21 +++++++++++++++++++++ tests/endpoint.spec.js | 36 ++++++++++++++++++++++++++++++------ 4 files changed, 55 insertions(+), 13 deletions(-) diff --git a/src/client.js b/src/client.js index 3a5d92a..9d65547 100644 --- a/src/client.js +++ b/src/client.js @@ -81,13 +81,13 @@ module.exports = class Client { } //maintains pool of static endpoints with auto-batching enabled - autoBatch (endpointName, autoBatchInterval = 1000) { + autoBatch (endpointName, autoBatchInterval) { if (this.autoBatchPool[endpointName]) { return this.autoBatchPool[endpointName] } if (!this[endpointName]) { - return new Error(`no enpoint ${endpointName} found`) + throw new Error(`no enpoint ${endpointName} found`) } const resultEndpoint = this[endpointName]() diff --git a/src/endpoint.js b/src/endpoint.js index 29ec7ad..fdbfcf7 100644 --- a/src/endpoint.js +++ b/src/endpoint.js @@ -77,10 +77,10 @@ module.exports = class AbstractEndpoint { } //turns on auto-batching for this endpoint - enableAutoBatch (interval) { + enableAutoBatch (interval = 100) { if (this._autoBatch === null) { this._autoBatch = { - interval: interval || 1000, + interval: interval, set: new Set(), nextBatchPromise: null, autoBatchOverride: false, @@ -191,9 +191,6 @@ module.exports = class AbstractEndpoint { } _autoBatchMany (ids) { - if (!this._autoBatch.set) { - this._autoBatch.set = new Set() - } if (this._autoBatch.set.size === 0) { this._autoBatch.nextBatchPromise = new Promise((resolve, reject) => { setTimeout(() => { diff --git a/tests/client.spec.js b/tests/client.spec.js index 2383dac..20eba68 100644 --- a/tests/client.spec.js +++ b/tests/client.spec.js @@ -4,6 +4,17 @@ const memoryCache = require('../src/cache/memory') const Module = require('../src/client') const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms)) +async function expectError (callback) { + let err + try { + await callback() + } catch (e) { + err = e + } + + expect(err).toBeInstanceOf(Error) +} + describe('client', () => { let client beforeEach(() => { @@ -143,7 +154,17 @@ describe('client', () => { expect(endpoint2.arbitraryPropertyNameForTesting).toEqual('test confirmed') }) + it(`errors if endpoint name doesn't exist`, async () => { + await expectError(() => client.autoBatch('does not exist')) + }) + it(`debugMessage if endpoint is not bulk expanding`, () => { + const debugMessageMock = jest.fn() + client.debugMessage = debugMessageMock + + const eventsAutoBatch = client.autoBatch('events') + expect(debugMessageMock.mock.calls.length).toBe(1) + }) }) it('can get the account endpoint', () => { diff --git a/tests/endpoint.spec.js b/tests/endpoint.spec.js index 488912d..6a7975c 100644 --- a/tests/endpoint.spec.js +++ b/tests/endpoint.spec.js @@ -110,9 +110,9 @@ describe('abstract endpoint', () => { describe('auto batching', () => { const interval = 10 - beforeEach(() => { - endpoint.enableAutoBatch(interval) - }) + // beforeEach(() => { + // endpoint.enableAutoBatch(interval) + // }) it('sets up _autoBatch variable', () => { let x = endpoint.enableAutoBatch(interval) @@ -122,12 +122,23 @@ describe('abstract endpoint', () => { expect(x._autoBatch.nextBatchPromise).toBeNull() expect(x._autoBatch.autoBatchOverride).toEqual(false) }) + + it('has default interval of 100', () => { + let x = endpoint.enableAutoBatch() + expect(x._autoBatch.interval).toEqual(100) + }) + + it('enableAutoBatch does not overwrite _autoBatch variable', () => { + endpoint.enableAutoBatch() + endpoint.enableAutoBatch(interval) + expect(endpoint._autoBatch.interval).toEqual(100) + }) it('supports batching from get', async () => { let content = [{ id: 1, name: 'foo' }, { id: 2, name: 'bar' }] endpoint.isBulk = true endpoint.url = '/v2/test' - // endpoint.enableAutoBatch(10) + endpoint.enableAutoBatch(interval) fetchMock.addResponse(content) let [entry1, entry2] = await Promise.all([endpoint.get(1), endpoint.get(2)]) @@ -135,12 +146,25 @@ describe('abstract endpoint', () => { expect(entry1).toEqual(content[0]) expect(entry2).toEqual(content[1]) }) + + it('returns null from get with no response', async () => { + let content = [] + endpoint.isBulk = true + endpoint.url = '/v2/test' + endpoint.enableAutoBatch(interval) + fetchMock.addResponse(content) + + let [entry1, entry2] = await Promise.all([endpoint.get(1), endpoint.get(2)]) + expect(fetchMock.lastUrl()).toEqual('https://api.guildwars2.com/v2/test?v=schema&ids=1,2') + expect(entry1).toEqual(null) + expect(entry2).toEqual(null) + }) it('supports batching from many', async () => { let content = [{ id: 1, name: 'foo' }, { id: 2, name: 'bar' }, { id: 3, name: 'bar' }] endpoint.isBulk = true endpoint.url = '/v2/test' - // endpoint.enableAutoBatch(10) + endpoint.enableAutoBatch(interval) fetchMock.addResponse(content) let [entry1, entry2] = await Promise.all([endpoint.many([1,2]), endpoint.many([2,3])]) @@ -154,7 +178,7 @@ describe('abstract endpoint', () => { let content2 = [{ id: 2, name: 'bar' }] endpoint.isBulk = true endpoint.url = '/v2/test' - // endpoint.enableAutoBatch(10) + endpoint.enableAutoBatch(interval) fetchMock.addResponse(content1) fetchMock.addResponse(content2) From 915b795828aa87c98401bcc83dc138438e503695 Mon Sep 17 00:00:00 2001 From: shinymeta Date: Sun, 20 Jun 2021 23:05:53 -0400 Subject: [PATCH 05/12] changes based on PR comments --- src/client.js | 6 +++--- src/endpoint.js | 41 ++++++++++++++++++----------------------- tests/client.spec.js | 12 ++++++------ tests/endpoint.spec.js | 31 +++++++++++++++---------------- 4 files changed, 42 insertions(+), 48 deletions(-) diff --git a/src/client.js b/src/client.js index 9d65547..c037831 100644 --- a/src/client.js +++ b/src/client.js @@ -80,8 +80,8 @@ module.exports = class Client { }) } - //maintains pool of static endpoints with auto-batching enabled - autoBatch (endpointName, autoBatchInterval) { + // Maintains a pool of static endpoints with auto-batching enabled + autoBatch (endpointName, batchDelay) { if (this.autoBatchPool[endpointName]) { return this.autoBatchPool[endpointName] } @@ -92,7 +92,7 @@ module.exports = class Client { const resultEndpoint = this[endpointName]() if (resultEndpoint.isBulk) { - this.autoBatchPool[endpointName] = resultEndpoint.enableAutoBatch(autoBatchInterval) + this.autoBatchPool[endpointName] = resultEndpoint.enableAutoBatch(batchDelay) } else { this.debugMessage(`${endpointName} is not bulk expanding, endpoint will not have any autobatch behavior`) diff --git a/src/endpoint.js b/src/endpoint.js index fdbfcf7..ad760d9 100644 --- a/src/endpoint.js +++ b/src/endpoint.js @@ -76,14 +76,13 @@ module.exports = class AbstractEndpoint { return this } - //turns on auto-batching for this endpoint - enableAutoBatch (interval = 100) { + // Turn on auto-batching for this endpoint + enableAutoBatch (batchDelay = 100) { if (this._autoBatch === null) { this._autoBatch = { - interval: interval, - set: new Set(), + batchDelay: batchDelay, + idsForNextBatch: new Set(), nextBatchPromise: null, - autoBatchOverride: false, } } return this @@ -191,15 +190,14 @@ module.exports = class AbstractEndpoint { } _autoBatchMany (ids) { - if (this._autoBatch.set.size === 0) { + if (this._autoBatch.idsForNextBatch.size === 0) { this._autoBatch.nextBatchPromise = new Promise((resolve, reject) => { setTimeout(() => { - const batchedIds = Array.from(this._autoBatch.set) - this.debugMessage(`batch sending for ${batchedIds}`) - this._autoBatch.set.clear() - this._autoBatch.autoBatchOverride = true - return resolve(this.many(batchedIds)) - }, this._autoBatch.interval) // by default is 1000, 1 sec + const batchedIds = Array.from(this._autoBatch.idsForNextBatch) + this.debugMessage(`autoBatchMany called (${batchedIds.length} ids)`) + this._autoBatch.idsForNextBatch.clear() + return resolve(this.many(batchedIds, true)) + }, this._autoBatch.batchDelay) }).then(items => { const indexedItems = {} items.forEach(item => { @@ -209,16 +207,16 @@ module.exports = class AbstractEndpoint { }) } - //add ids to set - ids.forEach(id => this._autoBatch.set.add(id)) - // return array with results requested + // Add the requested ids to the pending ids + ids.forEach(id => this._autoBatch.idsForNextBatch.add(id)) + // Return the results based on the requested ids return this._autoBatch.nextBatchPromise.then(indexedItems => { return ids.map(id => indexedItems[id]).filter(x => x) }) } // Get multiple entries by ids - many (ids) { + many (ids, skipAutoBatch) { this.debugMessage(`many(${this.url}) called (${ids.length} ids)`) if (!this.isBulk) { @@ -235,7 +233,7 @@ module.exports = class AbstractEndpoint { // There is no cache time set, so always use the live data if (!this.cacheTime) { - return this._many(ids) + return this._many(ids, undefined, skipAutoBatch) } // Get as much as possible out of the cache @@ -250,7 +248,7 @@ module.exports = class AbstractEndpoint { this.debugMessage(`many(${this.url}) resolving partially from cache (${cached.length} ids)`) const missingIds = getMissingIds(ids, cached) - return this._many(missingIds, cached.length > 0).then(content => { + return this._many(missingIds, cached.length > 0, skipAutoBatch).then(content => { const cacheContent = content.map(value => [this._cacheHash(value.id), value]) this._cacheSetMany(cacheContent) @@ -279,14 +277,11 @@ module.exports = class AbstractEndpoint { } // Get multiple entries by ids from the live API - _many (ids, partialRequest = false) { + _many (ids, partialRequest = false, skipAutoBatch) { this.debugMessage(`many(${this.url}) requesting from api (${ids.length} ids)`) if (this._autoBatch !== null) { - if (this._autoBatch.autoBatchOverride) { - this._autoBatch.autoBatchOverride = false - } - else { + if (!skipAutoBatch) { return this._autoBatchMany(ids) } } diff --git a/tests/client.spec.js b/tests/client.spec.js index 20eba68..56caca7 100644 --- a/tests/client.spec.js +++ b/tests/client.spec.js @@ -135,22 +135,22 @@ describe('client', () => { }) describe('autobatch', () => { - const interval = 10 + const batchDelay = 10 it('can get an autobatching endpoint', () => { - let endpoint = client.autoBatch('items', interval) + let endpoint = client.autoBatch('items', batchDelay) expect(endpoint.url).toEqual('/v2/items') - expect(endpoint._autoBatch.interval).toEqual(interval) + expect(endpoint._autoBatch.batchDelay).toEqual(batchDelay) }) it('adds the endpoint to the pool', () => { - client.autoBatch('items', interval) + client.autoBatch('items', batchDelay) expect(client.autoBatchPool.items).toBeDefined() }) it('returns the same endpoint on subsequent calls', () => { - let endpoint1 = client.autoBatch('items', interval) + let endpoint1 = client.autoBatch('items', batchDelay) endpoint1.arbitraryPropertyNameForTesting = 'test confirmed' - let endpoint2 = client.autoBatch('items', interval) + let endpoint2 = client.autoBatch('items', batchDelay) expect(endpoint2.arbitraryPropertyNameForTesting).toEqual('test confirmed') }) diff --git a/tests/endpoint.spec.js b/tests/endpoint.spec.js index 6a7975c..4794a01 100644 --- a/tests/endpoint.spec.js +++ b/tests/endpoint.spec.js @@ -109,36 +109,35 @@ describe('abstract endpoint', () => { }) describe('auto batching', () => { - const interval = 10 + const batchDelay = 10 // beforeEach(() => { - // endpoint.enableAutoBatch(interval) + // endpoint.enableAutoBatch(batchDelay) // }) it('sets up _autoBatch variable', () => { - let x = endpoint.enableAutoBatch(interval) + let x = endpoint.enableAutoBatch(batchDelay) expect(x).toBeInstanceOf(Module) - expect(x._autoBatch.interval).toEqual(interval) - expect(x._autoBatch.set).toBeDefined() + expect(x._autoBatch.batchDelay).toEqual(batchDelay) + expect(x._autoBatch.idsForNextBatch).toBeDefined() expect(x._autoBatch.nextBatchPromise).toBeNull() - expect(x._autoBatch.autoBatchOverride).toEqual(false) }) - it('has default interval of 100', () => { + it('has default batchDelay of 100', () => { let x = endpoint.enableAutoBatch() - expect(x._autoBatch.interval).toEqual(100) + expect(x._autoBatch.batchDelay).toEqual(100) }) it('enableAutoBatch does not overwrite _autoBatch variable', () => { endpoint.enableAutoBatch() - endpoint.enableAutoBatch(interval) - expect(endpoint._autoBatch.interval).toEqual(100) + endpoint.enableAutoBatch(batchDelay) + expect(endpoint._autoBatch.batchDelay).toEqual(100) }) it('supports batching from get', async () => { let content = [{ id: 1, name: 'foo' }, { id: 2, name: 'bar' }] endpoint.isBulk = true endpoint.url = '/v2/test' - endpoint.enableAutoBatch(interval) + endpoint.enableAutoBatch(batchDelay) fetchMock.addResponse(content) let [entry1, entry2] = await Promise.all([endpoint.get(1), endpoint.get(2)]) @@ -151,7 +150,7 @@ describe('abstract endpoint', () => { let content = [] endpoint.isBulk = true endpoint.url = '/v2/test' - endpoint.enableAutoBatch(interval) + endpoint.enableAutoBatch(batchDelay) fetchMock.addResponse(content) let [entry1, entry2] = await Promise.all([endpoint.get(1), endpoint.get(2)]) @@ -164,7 +163,7 @@ describe('abstract endpoint', () => { let content = [{ id: 1, name: 'foo' }, { id: 2, name: 'bar' }, { id: 3, name: 'bar' }] endpoint.isBulk = true endpoint.url = '/v2/test' - endpoint.enableAutoBatch(interval) + endpoint.enableAutoBatch(batchDelay) fetchMock.addResponse(content) let [entry1, entry2] = await Promise.all([endpoint.many([1,2]), endpoint.many([2,3])]) @@ -173,18 +172,18 @@ describe('abstract endpoint', () => { expect(entry2).toEqual([content[1],content[2]]) }) - it('only batches requests during the interval', async () => { + it('only batches requests during the batchDelay', async () => { let content1 = [{ id: 1, name: 'foo' }] let content2 = [{ id: 2, name: 'bar' }] endpoint.isBulk = true endpoint.url = '/v2/test' - endpoint.enableAutoBatch(interval) + endpoint.enableAutoBatch(batchDelay) fetchMock.addResponse(content1) fetchMock.addResponse(content2) let [entry1, entry2] = await Promise.all([ endpoint.get(1), - new Promise((resolve) => {setTimeout(() => {resolve(endpoint.get(2))}, interval+1)}) + new Promise((resolve) => {setTimeout(() => {resolve(endpoint.get(2))}, batchDelay+1)}) ]) expect(fetchMock.urls()).toEqual([ 'https://api.guildwars2.com/v2/test?v=schema&ids=1', From f64b5ac40d98133b878d158538f3c4927bd65e60 Mon Sep 17 00:00:00 2001 From: shinymeta Date: Sun, 20 Jun 2021 23:13:55 -0400 Subject: [PATCH 06/12] removed unnecessary whitespace and comment --- src/endpoint.js | 2 +- tests/endpoint.spec.js | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/endpoint.js b/src/endpoint.js index ad760d9..62b97aa 100644 --- a/src/endpoint.js +++ b/src/endpoint.js @@ -19,7 +19,7 @@ module.exports = class AbstractEndpoint { this.isPaginated = false this.maxPageSize = 200 this.isBulk = false - this.supportsBulkAll = true + this.supportsBulkAll = true this.isLocalized = false this.isAuthenticated = false this.isOptionallyAuthenticated = false diff --git a/tests/endpoint.spec.js b/tests/endpoint.spec.js index 4794a01..bc42cfc 100644 --- a/tests/endpoint.spec.js +++ b/tests/endpoint.spec.js @@ -110,9 +110,6 @@ describe('abstract endpoint', () => { describe('auto batching', () => { const batchDelay = 10 - // beforeEach(() => { - // endpoint.enableAutoBatch(batchDelay) - // }) it('sets up _autoBatch variable', () => { let x = endpoint.enableAutoBatch(batchDelay) From 05fa149615020d0bcb9ca3b6ddf9d9c1289ffc14 Mon Sep 17 00:00:00 2001 From: shinymeta Date: Fri, 23 Jul 2021 03:31:16 -0400 Subject: [PATCH 07/12] .autoBatch() chaining, but not over-engineered --- src/client.js | 27 +++++++--------------- src/endpoint.js | 47 ++++++++++++++++++++++++++------------ tests/client.spec.js | 39 +++++++++++-------------------- tests/endpoint.spec.js | 26 ++++++++++----------- tests/mocks/client.mock.js | 2 ++ 5 files changed, 68 insertions(+), 73 deletions(-) diff --git a/src/client.js b/src/client.js index c037831..6353c51 100644 --- a/src/client.js +++ b/src/client.js @@ -12,7 +12,8 @@ module.exports = class Client { this.caches = [nullCache()] this.debug = false this.client = this - this.autoBatchPool = {} + this.autoBatching = false + this.autoBatchDelay = 50 } // Set the schema version @@ -80,25 +81,13 @@ module.exports = class Client { }) } - // Maintains a pool of static endpoints with auto-batching enabled - autoBatch (endpointName, batchDelay) { - if (this.autoBatchPool[endpointName]) { - return this.autoBatchPool[endpointName] + // Turn on autobatching for all endpoints + autoBatch (autoBatchDelay) { + if (autoBatchDelay) { + this.autoBatchDelay = autoBatchDelay } - - if (!this[endpointName]) { - throw new Error(`no enpoint ${endpointName} found`) - } - - const resultEndpoint = this[endpointName]() - if (resultEndpoint.isBulk) { - this.autoBatchPool[endpointName] = resultEndpoint.enableAutoBatch(batchDelay) - } - else { - this.debugMessage(`${endpointName} is not bulk expanding, endpoint will not have any autobatch behavior`) - } - - return resultEndpoint + this.autoBatching = true + return this } // All the different API endpoints diff --git a/src/endpoint.js b/src/endpoint.js index 62b97aa..34c9d43 100644 --- a/src/endpoint.js +++ b/src/endpoint.js @@ -5,6 +5,8 @@ const hashString = require('./hash') const clone = (x) => JSON.parse(JSON.stringify(x)) +const autoBatchSharedData = {} + module.exports = class AbstractEndpoint { constructor (parent) { this.client = parent.client @@ -24,8 +26,11 @@ module.exports = class AbstractEndpoint { this.isAuthenticated = false this.isOptionallyAuthenticated = false this.credentials = false - + + this.autoBatching = parent.autoBatching + this.autoBatchDelay = parent.autoBatchDelay this._autoBatch = null + this.setupAutoBatchSharedData() this._skipCache = false } @@ -33,6 +38,7 @@ module.exports = class AbstractEndpoint { // Set the schema version schema (schema) { this.schemaVersion = schema + this.setupAutoBatchSharedData() this.debugMessage(`set the schema to ${schema}`) return this } @@ -45,6 +51,7 @@ module.exports = class AbstractEndpoint { // Set the language for locale-aware endpoints language (lang) { this.lang = lang + this.setupAutoBatchSharedData() this.debugMessage(`set the language to ${lang}`) return this } @@ -52,6 +59,7 @@ module.exports = class AbstractEndpoint { // Set the api key for authenticated endpoints authenticate (apiKey) { this.apiKey = apiKey + this.setupAutoBatchSharedData() this.debugMessage(`set the api key to ${apiKey}`) return this } @@ -77,15 +85,26 @@ module.exports = class AbstractEndpoint { } // Turn on auto-batching for this endpoint - enableAutoBatch (batchDelay = 100) { - if (this._autoBatch === null) { - this._autoBatch = { - batchDelay: batchDelay, + autoBatch (autoBatchDelay) { + if (autoBatchDelay) { + this.autoBatchDelay = autoBatchDelay + this.setupAutoBatchSharedData() + } + this.autoBatching = true + return this + } + + // Sets _autoBatch to shared batching object based on _cacheHash + setupAutoBatchSharedData() { + const autoBatchId = this._cacheHash(this.autoBatchDelay) + if (!autoBatchSharedData[autoBatchId]) { + autoBatchSharedData[autoBatchId] = { idsForNextBatch: new Set(), nextBatchPromise: null, } } - return this + + this._autoBatch = autoBatchSharedData[autoBatchId] } // Get all ids @@ -170,14 +189,14 @@ module.exports = class AbstractEndpoint { // Request the single id if the endpoint a bulk endpoint if (this.isBulk && !url) { - if (this._autoBatch === null) { - return this._request(`${this.url}?id=${id}`) - } - else { + if (this.autoBatching) { return this._autoBatchMany([id]).then((items) => { return items[0]?items[0]:null }) } + else { + return this._request(`${this.url}?id=${id}`) + } } // We are dealing with a custom url instead @@ -197,7 +216,7 @@ module.exports = class AbstractEndpoint { this.debugMessage(`autoBatchMany called (${batchedIds.length} ids)`) this._autoBatch.idsForNextBatch.clear() return resolve(this.many(batchedIds, true)) - }, this._autoBatch.batchDelay) + }, this.autoBatchDelay) }).then(items => { const indexedItems = {} items.forEach(item => { @@ -280,10 +299,8 @@ module.exports = class AbstractEndpoint { _many (ids, partialRequest = false, skipAutoBatch) { this.debugMessage(`many(${this.url}) requesting from api (${ids.length} ids)`) - if (this._autoBatch !== null) { - if (!skipAutoBatch) { - return this._autoBatchMany(ids) - } + if (this.autoBatching && !skipAutoBatch) { + return this._autoBatchMany(ids) } diff --git a/tests/client.spec.js b/tests/client.spec.js index 56caca7..c8455af 100644 --- a/tests/client.spec.js +++ b/tests/client.spec.js @@ -136,34 +136,21 @@ describe('client', () => { describe('autobatch', () => { const batchDelay = 10 - it('can get an autobatching endpoint', () => { - let endpoint = client.autoBatch('items', batchDelay) - expect(endpoint.url).toEqual('/v2/items') - expect(endpoint._autoBatch.batchDelay).toEqual(batchDelay) + it('can turn on autobatching with specified delay', () => { + let api = client.autoBatch(100) + expect(client.autoBatchDelay).toEqual(100) + expect(api).toBeInstanceOf(Module) + + let endpoint = client.account().autoBatch(200) + expect(endpoint.autoBatchDelay).toEqual(200) + expect(client.autoBatch(300).autoBatchDelay).toEqual(300) + expect(endpoint.autoBatchDelay).toEqual(200) }) - it('adds the endpoint to the pool', () => { - client.autoBatch('items', batchDelay) - expect(client.autoBatchPool.items).toBeDefined() - }) - - it('returns the same endpoint on subsequent calls', () => { - let endpoint1 = client.autoBatch('items', batchDelay) - endpoint1.arbitraryPropertyNameForTesting = 'test confirmed' - let endpoint2 = client.autoBatch('items', batchDelay) - expect(endpoint2.arbitraryPropertyNameForTesting).toEqual('test confirmed') - }) - - it(`errors if endpoint name doesn't exist`, async () => { - await expectError(() => client.autoBatch('does not exist')) - }) - - it(`debugMessage if endpoint is not bulk expanding`, () => { - const debugMessageMock = jest.fn() - client.debugMessage = debugMessageMock - - const eventsAutoBatch = client.autoBatch('events') - expect(debugMessageMock.mock.calls.length).toBe(1) + it ('has default bach delay of 50', () => { + let endpoint = client.autoBatch().items() + expect(client.autoBatchDelay).toEqual(50) + expect(endpoint.autoBatchDelay).toEqual(50) }) }) diff --git a/tests/endpoint.spec.js b/tests/endpoint.spec.js index bc42cfc..bb74361 100644 --- a/tests/endpoint.spec.js +++ b/tests/endpoint.spec.js @@ -112,29 +112,29 @@ describe('abstract endpoint', () => { const batchDelay = 10 it('sets up _autoBatch variable', () => { - let x = endpoint.enableAutoBatch(batchDelay) + let x = endpoint.autoBatch(batchDelay) expect(x).toBeInstanceOf(Module) - expect(x._autoBatch.batchDelay).toEqual(batchDelay) + expect(x.autoBatchDelay).toEqual(batchDelay) expect(x._autoBatch.idsForNextBatch).toBeDefined() expect(x._autoBatch.nextBatchPromise).toBeNull() }) - it('has default batchDelay of 100', () => { - let x = endpoint.enableAutoBatch() - expect(x._autoBatch.batchDelay).toEqual(100) + it('has default batchDelay of 50', () => { + let x = endpoint.autoBatch() + expect(x.autoBatchDelay).toEqual(50) }) - it('enableAutoBatch does not overwrite _autoBatch variable', () => { - endpoint.enableAutoBatch() - endpoint.enableAutoBatch(batchDelay) - expect(endpoint._autoBatch.batchDelay).toEqual(100) + it('autoBatch can change the batchDelay', () => { + endpoint.autoBatch() + endpoint.autoBatch(batchDelay) + expect(endpoint.autoBatchDelay).toEqual(batchDelay) }) it('supports batching from get', async () => { let content = [{ id: 1, name: 'foo' }, { id: 2, name: 'bar' }] endpoint.isBulk = true endpoint.url = '/v2/test' - endpoint.enableAutoBatch(batchDelay) + endpoint.autoBatch(batchDelay) fetchMock.addResponse(content) let [entry1, entry2] = await Promise.all([endpoint.get(1), endpoint.get(2)]) @@ -147,7 +147,7 @@ describe('abstract endpoint', () => { let content = [] endpoint.isBulk = true endpoint.url = '/v2/test' - endpoint.enableAutoBatch(batchDelay) + endpoint.autoBatch(batchDelay) fetchMock.addResponse(content) let [entry1, entry2] = await Promise.all([endpoint.get(1), endpoint.get(2)]) @@ -160,7 +160,7 @@ describe('abstract endpoint', () => { let content = [{ id: 1, name: 'foo' }, { id: 2, name: 'bar' }, { id: 3, name: 'bar' }] endpoint.isBulk = true endpoint.url = '/v2/test' - endpoint.enableAutoBatch(batchDelay) + endpoint.autoBatch(batchDelay) fetchMock.addResponse(content) let [entry1, entry2] = await Promise.all([endpoint.many([1,2]), endpoint.many([2,3])]) @@ -174,7 +174,7 @@ describe('abstract endpoint', () => { let content2 = [{ id: 2, name: 'bar' }] endpoint.isBulk = true endpoint.url = '/v2/test' - endpoint.enableAutoBatch(batchDelay) + endpoint.autoBatch(batchDelay) fetchMock.addResponse(content1) fetchMock.addResponse(content2) diff --git a/tests/mocks/client.mock.js b/tests/mocks/client.mock.js index 810b1a2..a027ce5 100644 --- a/tests/mocks/client.mock.js +++ b/tests/mocks/client.mock.js @@ -7,6 +7,8 @@ const mockClient = { apiKey: false, fetch: fetch, caches: [nullCache(), memoryCache(), memoryCache()], + autoBatching: false, + autoBatchDelay: 50, language: function (lang) { this.lang = lang }, From 8629a1820bdde20ce6653f5bce65bc0d0466cbe9 Mon Sep 17 00:00:00 2001 From: Joshua Date: Mon, 27 Sep 2021 00:41:01 +0200 Subject: [PATCH 08/12] Fix autoBatch Id per endpoint --- src/endpoint.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/endpoint.js b/src/endpoint.js index 34c9d43..af02f57 100644 --- a/src/endpoint.js +++ b/src/endpoint.js @@ -96,7 +96,7 @@ module.exports = class AbstractEndpoint { // Sets _autoBatch to shared batching object based on _cacheHash setupAutoBatchSharedData() { - const autoBatchId = this._cacheHash(this.autoBatchDelay) + const autoBatchId = this._cacheHash(this.constructor.name + this.autoBatchDelay) if (!autoBatchSharedData[autoBatchId]) { autoBatchSharedData[autoBatchId] = { idsForNextBatch: new Set(), From b1bf6536deb57e47d524f5429cef8880c25fb71f Mon Sep 17 00:00:00 2001 From: shinymeta Date: Sun, 26 Sep 2021 20:23:33 -0400 Subject: [PATCH 09/12] small suggests and new test case for issue #74 --- src/endpoint.js | 2 +- tests/endpoint.spec.js | 50 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/src/endpoint.js b/src/endpoint.js index 34c9d43..7169a58 100644 --- a/src/endpoint.js +++ b/src/endpoint.js @@ -29,7 +29,6 @@ module.exports = class AbstractEndpoint { this.autoBatching = parent.autoBatching this.autoBatchDelay = parent.autoBatchDelay - this._autoBatch = null this.setupAutoBatchSharedData() this._skipCache = false @@ -97,6 +96,7 @@ module.exports = class AbstractEndpoint { // Sets _autoBatch to shared batching object based on _cacheHash setupAutoBatchSharedData() { const autoBatchId = this._cacheHash(this.autoBatchDelay) + console.log('@@',autoBatchId, this.url) if (!autoBatchSharedData[autoBatchId]) { autoBatchSharedData[autoBatchId] = { idsForNextBatch: new Set(), diff --git a/tests/endpoint.spec.js b/tests/endpoint.spec.js index bb74361..87ce973 100644 --- a/tests/endpoint.spec.js +++ b/tests/endpoint.spec.js @@ -111,6 +111,14 @@ describe('abstract endpoint', () => { describe('auto batching', () => { const batchDelay = 10 + beforeEach(() => { + mockClient.autoBatching = true + }) + + afterEach(() => { + mockClient.autoBatching = false + }) + it('sets up _autoBatch variable', () => { let x = endpoint.autoBatch(batchDelay) expect(x).toBeInstanceOf(Module) @@ -137,10 +145,11 @@ describe('abstract endpoint', () => { endpoint.autoBatch(batchDelay) fetchMock.addResponse(content) - let [entry1, entry2] = await Promise.all([endpoint.get(1), endpoint.get(2)]) + let [entry1, entry2, entry3] = await Promise.all([endpoint.get(1), endpoint.get(2), endpoint.get(1)]) expect(fetchMock.lastUrl()).toEqual('https://api.guildwars2.com/v2/test?v=schema&ids=1,2') expect(entry1).toEqual(content[0]) expect(entry2).toEqual(content[1]) + expect(entry3).toEqual(content[0]) }) it('returns null from get with no response', async () => { @@ -190,6 +199,45 @@ describe('abstract endpoint', () => { expect(entry2).toEqual(content2[0]) }) + + it('can batch requests from different endpoints in parallel', async () => { + console.log('@@@@@@@@@@@@@@@@@@@@@@@') + let content1 = [{ id: 1, name: 'foo' }, { id: 2, name: 'bar' }] + let content2 = [{ id: 1, name: 'bar' }] + + endpoint = new Module(mockClient) + endpoint.caches.map(cache => cache.flush()) + endpoint.isBulk = true + endpoint.url = '/v2/test' + endpoint.schemaVersion = 'schema' + // endpoint.autoBatch(batchDelay*100) + + differentEndpoint = new Module(mockClient) + differentEndpoint.caches.map(cache => cache.flush()) + differentEndpoint.isBulk = true + differentEndpoint.url = '/v2/differentTest' + differentEndpoint.schemaVersion = 'schema' + // differentEndpoint.autoBatch(batchDelay*100) + + fetchMock.addResponse(content1) + fetchMock.addResponse(content2) + + let [entry1, entry2, entry3] = await Promise.all([ + endpoint.get(1), + differentEndpoint.get(1), + endpoint.get(2), + ]) + console.log('@@@@@@@@@@@@@@@@@@@@@@@') + expect(fetchMock.urls()).toEqual([ + 'https://api.guildwars2.com/v2/test?v=schema&ids=1,2', + 'https://api.guildwars2.com/v2/differentTest?v=schema&ids=1' + ]) + expect(entry1).toEqual(content1[0]) + expect(entry2).toEqual(content2[0]) + expect(entry3).toEqual(content1[1]) + + console.log('@@@@@@@@@@@@@@@@@@@@@@@') + }) }) describe('get', () => { From c753413be47c51701ecb9f97686532f064410369 Mon Sep 17 00:00:00 2001 From: shinymeta Date: Sun, 26 Sep 2021 20:23:50 -0400 Subject: [PATCH 10/12] removed debug messages --- src/endpoint.js | 1 - tests/endpoint.spec.js | 5 ----- 2 files changed, 6 deletions(-) diff --git a/src/endpoint.js b/src/endpoint.js index 7169a58..23bc35d 100644 --- a/src/endpoint.js +++ b/src/endpoint.js @@ -96,7 +96,6 @@ module.exports = class AbstractEndpoint { // Sets _autoBatch to shared batching object based on _cacheHash setupAutoBatchSharedData() { const autoBatchId = this._cacheHash(this.autoBatchDelay) - console.log('@@',autoBatchId, this.url) if (!autoBatchSharedData[autoBatchId]) { autoBatchSharedData[autoBatchId] = { idsForNextBatch: new Set(), diff --git a/tests/endpoint.spec.js b/tests/endpoint.spec.js index 87ce973..f4e7cc4 100644 --- a/tests/endpoint.spec.js +++ b/tests/endpoint.spec.js @@ -201,7 +201,6 @@ describe('abstract endpoint', () => { }) it('can batch requests from different endpoints in parallel', async () => { - console.log('@@@@@@@@@@@@@@@@@@@@@@@') let content1 = [{ id: 1, name: 'foo' }, { id: 2, name: 'bar' }] let content2 = [{ id: 1, name: 'bar' }] @@ -210,14 +209,12 @@ describe('abstract endpoint', () => { endpoint.isBulk = true endpoint.url = '/v2/test' endpoint.schemaVersion = 'schema' - // endpoint.autoBatch(batchDelay*100) differentEndpoint = new Module(mockClient) differentEndpoint.caches.map(cache => cache.flush()) differentEndpoint.isBulk = true differentEndpoint.url = '/v2/differentTest' differentEndpoint.schemaVersion = 'schema' - // differentEndpoint.autoBatch(batchDelay*100) fetchMock.addResponse(content1) fetchMock.addResponse(content2) @@ -227,7 +224,6 @@ describe('abstract endpoint', () => { differentEndpoint.get(1), endpoint.get(2), ]) - console.log('@@@@@@@@@@@@@@@@@@@@@@@') expect(fetchMock.urls()).toEqual([ 'https://api.guildwars2.com/v2/test?v=schema&ids=1,2', 'https://api.guildwars2.com/v2/differentTest?v=schema&ids=1' @@ -236,7 +232,6 @@ describe('abstract endpoint', () => { expect(entry2).toEqual(content2[0]) expect(entry3).toEqual(content1[1]) - console.log('@@@@@@@@@@@@@@@@@@@@@@@') }) }) From a2b92777abbadd432ca76c229c1332c980613cf0 Mon Sep 17 00:00:00 2001 From: shinymeta Date: Sun, 26 Sep 2021 20:46:49 -0400 Subject: [PATCH 11/12] slight mod to test, fix works. --- tests/endpoint.spec.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/tests/endpoint.spec.js b/tests/endpoint.spec.js index f4e7cc4..7a714d9 100644 --- a/tests/endpoint.spec.js +++ b/tests/endpoint.spec.js @@ -210,11 +210,17 @@ describe('abstract endpoint', () => { endpoint.url = '/v2/test' endpoint.schemaVersion = 'schema' - differentEndpoint = new Module(mockClient) + class differentModule extends Module { + constructor (client) { + super(client) + this.url = '/v2/differentTest' + this.schemaVersion = 'schema' + this.isBulk = true + } + } + + differentEndpoint = new differentModule(mockClient) differentEndpoint.caches.map(cache => cache.flush()) - differentEndpoint.isBulk = true - differentEndpoint.url = '/v2/differentTest' - differentEndpoint.schemaVersion = 'schema' fetchMock.addResponse(content1) fetchMock.addResponse(content2) From cca7703fd8ab57bd5192634b4a0b850e98fb4676 Mon Sep 17 00:00:00 2001 From: shinymeta Date: Sun, 26 Sep 2021 21:49:23 -0400 Subject: [PATCH 12/12] suggested changes and readme update --- README.md | 38 +++++++++++++++++++++++++++++++++++++- tests/client.spec.js | 11 ----------- 2 files changed, 37 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 4f4c499..3069b90 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ npm install gw2api-client ``` -This module can be used for Node.js as well as browsers using [Browserify](https://github.com/substack/browserify-handbook#how-node_modules-works). +This module can be used for Node.js as well as browsers using [Browserify](https://github.com/substack/browserify-handbook#how-node_modules-works). ## Usage @@ -98,6 +98,42 @@ The cache uses expiration times and not the build number, because the content of setInterval(() => api.flushCacheIfGameUpdated(), 10 * 60 * 1000) ``` +### Auto-Batching + +By default, all API requests are executed individually against the live API (or the cache). You can queue/batch requests to bulk endpoints with the `.autoBatch(batchDelay)` method. When enabled, the library will pool ids from get/many for the specified `batchDelay` in ms(default 50 ms), then make API requests with all pending IDs. + +```js +api.items().autoBatch().get(100) +api.items().autoBatch().get(100) +api.items().autoBatch().get(101) +api.items().autoBatch().many([100, 101, 102, 103]) +api.items().autoBatch().many([100, 103, 104, 105]) +// when called in immediate succession here, the only API call will be: +// https://api.guildwars2.com/v2/items?ids=100,101,102,103,104,105 +``` + +Autobatching can be enabled for all endpoints by calling `client.autoBatch()`: + +```js +api.autoBatch() // autobatching turned on for all endpoints stemmed from this client object +api.items().get(100) +api.items().get(100) +api.items().get(101) +api.items().many([100, 101, 102, 103]) +api.items().many([100, 103, 104, 105]) +``` + +Or enable only for a single Endpoint by saving a reference to an endpoint after calling autobatch: + +```js +const itemsApi = api.items().autoBatch() // autobatching turned on for all calls from this endpoint +itemsApi.get(100) +itemsApi.get(100) +itemsApi.get(101) +itemsApi.many([100, 101, 102, 103]) +itemsApi.many([100, 103, 104, 105]) +``` + ### Error handling You can use the Promise `catch` to handle all possible errors. diff --git a/tests/client.spec.js b/tests/client.spec.js index c8455af..44b269f 100644 --- a/tests/client.spec.js +++ b/tests/client.spec.js @@ -4,17 +4,6 @@ const memoryCache = require('../src/cache/memory') const Module = require('../src/client') const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms)) -async function expectError (callback) { - let err - try { - await callback() - } catch (e) { - err = e - } - - expect(err).toBeInstanceOf(Error) -} - describe('client', () => { let client beforeEach(() => {