diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a9c7a54..6449705 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -27,3 +27,15 @@ jobs: - name: Upload coverage to Coveralls uses: coverallsapp/github-action@v2 + with: + parallel: true + flag-name: node-${{ matrix.node-version }} + + finish: + needs: build + runs-on: ubuntu-latest + steps: + - name: Close parallel build + uses: coverallsapp/github-action@v2 + with: + parallel-finished: true diff --git a/CHANGELOG.md b/CHANGELOG.md index c6bfd34..1e2d24b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,17 @@ All notable changes to this project will be documented in this file. +## [2.0.0] - 2026-02-19 + +### Added +- OHLC (candlestick) endpoint support via `currency.ohlc()` +- `currency()`, `date()`, `interval()`, `base()` methods on the Ohlc class +- Intervals supported: `5m`, `15m`, `30m`, `1h`, `4h`, `12h`, `1d` (default: `1d`) + +### Changed +- **Breaking**: Migrated from API v1 to v2 (`/api/v1/` → `/api/v2/`) +- Bumped package version to 2.0.0 + ## [1.2.0] - 2026-02-13 ### Added diff --git a/Makefile b/Makefile index 85cf491..8c342d0 100644 --- a/Makefile +++ b/Makefile @@ -22,5 +22,8 @@ run: ## Run test file publish: ## Publish version (use: make publish OTP=123456 if 2FA enabled) docker run --rm -v ${PWD}:${WORKING_DIR} -v ${HOME}/.npmrc:/home/node/.npmrc:ro -w ${WORKING_DIR} --name ${CONTAINER_NAME} ${LOCAL_DOCKER_IMAGE} npm publish $(if ${OTP},--otp=${OTP}) +deprecate: ## Deprecate all v1.x versions on npm (use: make deprecate OTP=123456 if 2FA enabled) + docker run --rm -v ${HOME}/.npmrc:/home/node/.npmrc:ro node:24-slim npm deprecate currencyapi-node@'"<2.0.0"' "The v1 API will redirect to v2 on 31 July 2026. Please upgrade to currencyapi-node@2.0.0 for v2 support and the new OHLC endpoint." $(if ${OTP},--otp=${OTP}) + help: @grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' diff --git a/README.md b/README.md index aad6b8c..64d5ecf 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,12 @@ -# CurrencyApi NodeJs wrapper +# CurrencyApi NodeJs wrapper -[![npm version](https://badge.fury.io/js/currencyapi-node.svg)](https://www.npmjs.com/package/currencyapi-node) [![Coverage Status](https://coveralls.io/repos/github/houseofapis/currencyapi-node/badge.svg?branch=master)](https://coveralls.io/github/houseofapis/currencyapi-node?branch=master) +[![npm version](https://badge.fury.io/js/currencyapi-node.svg)](https://www.npmjs.com/package/currencyapi-node) [![Coverage Status](https://coveralls.io/repos/github/houseofapis/currencyapi-node/badge.svg?branch=master)](https://coveralls.io/github/houseofapis/currencyapi-node?branch=master) +> **Note:** API v1 is deprecated and will be retired on **31st July 2026**, at which point all v1 traffic will be redirected to v2. This SDK (v2.0.0+) targets API v2. If you are on an older version of this SDK, please upgrade. -CurrencyApi.net provides live currency rates via a REST API. A live currency feed for over 152 currencies, including physical (USD, GBP, EUR + more) and cryptos (Bitcoin, Litecoin, Ethereum + more). A JSON and XML currency api updated every 60 seconds. + +CurrencyApi.net provides live currency rates via a REST API. A live currency feed for over 152 currencies, including physical (USD, GBP, EUR + more) and cryptos (Bitcoin, Litecoin, Ethereum + more). A JSON and XML currency api updated every 60 seconds. Features: @@ -13,6 +15,7 @@ Features: - Popular cryptocurrencies included; Bitcoin, Litecoin etc. - Convert currencies on the fly with the convert endpoint. - Historical currency rates back to year 2000. +- OHLC (candlestick) data with multiple intervals. - Easy to follow documentation Signup for a free or paid account here. @@ -183,3 +186,38 @@ const result = await currency | `endDate()` | The historical date you wish to receive the currency conversions until. This should be formatted as YYYY-MM-DD. **Required**. | | `base()` | The base currency you wish you receive the currency conversions for. This will output all currency conversions for that currency. **Default: USD**. | | `output()` | Response output in either JSON or XML. **Default: JSON**. | + +
+ +### OHLC (candlestick data): + +```javascript +const result = await currency + .ohlc() + .currency('GBP') + .date('2024-01-13') + .get() +``` + +Example with all available methods: + +```javascript +const result = await currency + .ohlc() + .currency('GBP') + .date('2024-01-13') + .interval('1h') + .base('USD') + .output('JSON') + .get() +``` + +**Available methods for ohlc endpoint** + +| Methods | Description | +| --- | --- | +| `currency()` | The quote currency to retrieve OHLC data for. This will be a three letter ISO 4217 currency code. **Required**. | +| `date()` | The date to retrieve OHLC data for. This should be formatted as YYYY-MM-DD. **Required**. | +| `interval()` | The time interval for each candle. Allowed values: `5m`, `15m`, `30m`, `1h`, `4h`, `12h`, `1d`. **Default: 1d**. | +| `base()` | The base currency. **Default: USD**. | +| `output()` | Response output in either JSON or XML. **Default: JSON**. | diff --git a/package.json b/package.json index 00b0d7c..75c5df2 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "role": "Developer" } ], - "version": "1.2.0", + "version": "2.0.0", "engines": { "node": ">=16.0.0" }, diff --git a/src/CurrencyApi.js b/src/CurrencyApi.js index 934bcc3..28fd552 100644 --- a/src/CurrencyApi.js +++ b/src/CurrencyApi.js @@ -3,6 +3,7 @@ const Convert = require('./classes/Convert') const History = require('./classes/History') const Timeframe = require('./classes/Timeframe') const Currencies = require('./classes/Currencies') +const Ohlc = require('./classes/Ohlc') /** * @class CurrencyApi @@ -64,6 +65,15 @@ class CurrencyApi return new Currencies(this.key) } + /** + * Use the ohlc endpoint + * + * @returns {Ohlc} + */ + ohlc() { + return new Ohlc(this.key) + } + } module.exports = CurrencyApi diff --git a/src/classes/Endpoint.js b/src/classes/Endpoint.js index 9414143..8266d4f 100644 --- a/src/classes/Endpoint.js +++ b/src/classes/Endpoint.js @@ -14,7 +14,7 @@ const BASE_URL = 'https://currencyapi.net/api/'; /** * Version of the API */ -const API_VERSION = 'v1'; +const API_VERSION = 'v2'; /** * Default base currency diff --git a/src/classes/Ohlc.js b/src/classes/Ohlc.js new file mode 100644 index 0000000..467c1f6 --- /dev/null +++ b/src/classes/Ohlc.js @@ -0,0 +1,68 @@ +const Endpoint = require('./Endpoint') + +/** + * Allowed intervals for OHLC data + */ +const ALLOWED_INTERVALS = ['5m', '15m', '30m', '1h', '4h', '12h', '1d'] + +/** + * @class Ohlc + */ +class Ohlc extends Endpoint { + + /** + * Ohlc constructor + * + * @param {string} key + */ + constructor(key) { + super(key, 'ohlc') + this.addParam('interval', '1d') + } + + /** + * Set the quote currency + * + * @param {string} currency eg. 'GBP' + * @returns {Ohlc} + */ + currency(currency) { + this.addParam('currency', currency.toUpperCase()) + return this + } + + /** + * Set the date + * + * @param {string} date eg. '2024-01-13' + * @returns {Ohlc} + */ + date(date) { + this.addParam('date', date) + return this + } + + /** + * Set the interval. Allowed values: 5m, 15m, 30m, 1h, 4h, 12h, 1d + * + * @param {string} interval eg. '1h' + * @returns {Ohlc} + */ + interval(interval) { + this.addParam('interval', interval) + return this + } + + /** + * Set the base currency + * + * @param {string} currency eg. 'USD' + * @returns {Ohlc} + */ + base(currency) { + this.addParam('base', currency.toUpperCase()) + return this + } +} + +module.exports = Ohlc diff --git a/tests/convert.test.js b/tests/convert.test.js index 6bf755a..03e896f 100644 --- a/tests/convert.test.js +++ b/tests/convert.test.js @@ -69,7 +69,7 @@ describe("Fetching convert works as expected", () => { convert.from('GbP') convert.to('UsD') const response = await convert.get(); - const expectedUrl = 'https://currencyapi.net/api/v1/convert?key=invalidKey&output=JSON&amount=10&from=GBP&to=USD' + const expectedUrl = 'https://currencyapi.net/api/v2/convert?key=invalidKey&output=JSON&amount=10&from=GBP&to=USD' expect(fetch).toHaveBeenLastCalledWith(expectedUrl, { headers: { @@ -100,7 +100,7 @@ describe("Fetching convert works as expected", () => { convert.to('UsD') const response = await convert.get() - const expectedUrl = 'https://currencyapi.net/api/v1/convert?key=invalidKey&output=XML&amount=10&from=GBP&to=USD' + const expectedUrl = 'https://currencyapi.net/api/v2/convert?key=invalidKey&output=XML&amount=10&from=GBP&to=USD' expect(fetch).toHaveBeenLastCalledWith(expectedUrl, { headers: { "Content-Type": "application/xml", diff --git a/tests/currencies.test.js b/tests/currencies.test.js index 20976a8..0c9309d 100644 --- a/tests/currencies.test.js +++ b/tests/currencies.test.js @@ -42,7 +42,7 @@ describe("Fetching currencies works as expected", () => { }) ) const response = await currencies.get(); - const expectedUrl = 'https://currencyapi.net/api/v1/currencies?key=invalidKey&output=JSON' + const expectedUrl = 'https://currencyapi.net/api/v2/currencies?key=invalidKey&output=JSON' expect(fetch).toHaveBeenLastCalledWith(expectedUrl, { headers: { @@ -67,7 +67,7 @@ describe("Fetching currencies works as expected", () => { currencies.output('XmL') const response = await currencies.get() - const expectedUrl = 'https://currencyapi.net/api/v1/currencies?key=invalidKey&output=XML' + const expectedUrl = 'https://currencyapi.net/api/v2/currencies?key=invalidKey&output=XML' expect(fetch).toHaveBeenLastCalledWith(expectedUrl, { headers: { "Content-Type": "application/xml", diff --git a/tests/currencyapi.test.js b/tests/currencyapi.test.js index 53b8fe8..571afb6 100644 --- a/tests/currencyapi.test.js +++ b/tests/currencyapi.test.js @@ -4,6 +4,7 @@ const History = require('./../src/classes/History') const Timeframe = require('./../src/classes/Timeframe') const Convert = require('./../src/classes/Convert') const Currencies = require('./../src/classes/Currencies') +const Ohlc = require('./../src/classes/Ohlc') let currencyApi let invalidKey = 'invalidKey' @@ -24,5 +25,6 @@ describe("Setting CurrencyApi", () => { expect(currencyApi.timeframe()).toBeInstanceOf(Timeframe); expect(currencyApi.convert()).toBeInstanceOf(Convert); expect(currencyApi.currencies()).toBeInstanceOf(Currencies); + expect(currencyApi.ohlc()).toBeInstanceOf(Ohlc); }) }) \ No newline at end of file diff --git a/tests/history.test.js b/tests/history.test.js index de7a6df..d818bf7 100644 --- a/tests/history.test.js +++ b/tests/history.test.js @@ -59,7 +59,7 @@ describe("Fetching history works as expected", () => { ) history.date('2023-01-02') const response = await history.get(); - const expectedUrl = 'https://currencyapi.net/api/v1/history?key=invalidKey&output=JSON&base=USD&date=2023-01-02' + const expectedUrl = 'https://currencyapi.net/api/v2/history?key=invalidKey&output=JSON&base=USD&date=2023-01-02' expect(fetch).toHaveBeenLastCalledWith(expectedUrl, { headers: { @@ -85,7 +85,7 @@ describe("Fetching history works as expected", () => { history.date('2023-01-02') history.base('GbP') const response = await history.get(); - const expectedUrl = 'https://currencyapi.net/api/v1/history?key=invalidKey&output=XML&base=GBP&date=2023-01-02' + const expectedUrl = 'https://currencyapi.net/api/v2/history?key=invalidKey&output=XML&base=GBP&date=2023-01-02' expect(fetch).toHaveBeenLastCalledWith(expectedUrl, { headers: { diff --git a/tests/ohlc.test.js b/tests/ohlc.test.js new file mode 100644 index 0000000..2d2db60 --- /dev/null +++ b/tests/ohlc.test.js @@ -0,0 +1,142 @@ +jest.mock("node-fetch") +const fetch = require("node-fetch") +const Ohlc = require('./../src/classes/Ohlc') + +let ohlc +let invalidKey = 'invalidKey' + +beforeEach(() => { + ohlc = new Ohlc(invalidKey) +}) + +describe("Setting Ohlc", () => { + + test('Constructor setting params correctly', () => { + expect(ohlc.key).toBe(invalidKey) + expect(ohlc.endpoint).toBe('ohlc') + }) + + test('Check default params have been set', () => { + let params = ohlc.getParams() + expect(params).toHaveProperty('output', 'JSON') + expect(params).toHaveProperty('interval', '1d') + }) + + test('Set currency works and returns object', () => { + const setCurrency = ohlc.currency('gBp') + let params = ohlc.getParams() + expect(params).toHaveProperty('currency', 'GBP') + expect(setCurrency).toBeInstanceOf(Ohlc) + }) + + test('Set date works and returns object', () => { + const setDate = ohlc.date('2024-01-13') + let params = ohlc.getParams() + expect(params).toHaveProperty('date', '2024-01-13') + expect(setDate).toBeInstanceOf(Ohlc) + }) + + test('Set interval works and returns object', () => { + const setInterval = ohlc.interval('1h') + let params = ohlc.getParams() + expect(params).toHaveProperty('interval', '1h') + expect(setInterval).toBeInstanceOf(Ohlc) + }) + + test('Set all allowed intervals', () => { + const intervals = ['5m', '15m', '30m', '1h', '4h', '12h', '1d'] + intervals.forEach(i => { + ohlc.interval(i) + expect(ohlc.getParams()).toHaveProperty('interval', i) + }) + }) + + test('Set base works and returns object', () => { + const setBase = ohlc.base('eUr') + let params = ohlc.getParams() + expect(params).toHaveProperty('base', 'EUR') + expect(setBase).toBeInstanceOf(Ohlc) + }) + + test('Set output works and returns object', () => { + const setOutput = ohlc.output('xMl') + let params = ohlc.getParams() + expect(params).toHaveProperty('output', 'XML') + expect(setOutput).toBeInstanceOf(Ohlc) + }) + + test('Methods can be chained', () => { + const result = ohlc.currency('GBP').date('2024-01-13').interval('1h').base('USD') + expect(result).toBeInstanceOf(Ohlc) + let params = ohlc.getParams() + expect(params).toHaveProperty('currency', 'GBP') + expect(params).toHaveProperty('date', '2024-01-13') + expect(params).toHaveProperty('interval', '1h') + expect(params).toHaveProperty('base', 'USD') + }) + +}) + +describe("Fetching ohlc works as expected", () => { + + test("fetch json working as expected", async () => { + + const mockData = { + "valid": true, + "base": "USD", + "quote": "GBP", + "date": "2024-01-13", + "interval": "1h", + "ohlc": [ + { + "start": "2024-01-13T00:00:00Z", + "open": 1.2735, + "high": 1.2756, + "low": 1.2720, + "close": 1.2748 + } + ] + } + fetch.mockReturnValue( + Promise.resolve({ + json: () => + Promise.resolve(mockData) + }) + ) + ohlc.currency('GBP').date('2024-01-13').interval('1h') + const response = await ohlc.get() + const expectedUrl = 'https://currencyapi.net/api/v2/ohlc?key=invalidKey&output=JSON&interval=1h¤cy=GBP&date=2024-01-13' + + expect(fetch).toHaveBeenLastCalledWith(expectedUrl, { + headers: { + "Content-Type": "application/json", + "X-Sdk": "node" + } + }) + expect(response).toEqual(mockData) + }) + + test("fetch xml working as expected", async () => { + + const mockData = '' + + fetch.mockReturnValue( + Promise.resolve({ + text: () => + Promise.resolve(mockData) + }) + ) + ohlc.output('XmL').currency('GBP').date('2024-01-13') + const response = await ohlc.get() + + const expectedUrl = 'https://currencyapi.net/api/v2/ohlc?key=invalidKey&output=XML&interval=1d¤cy=GBP&date=2024-01-13' + expect(fetch).toHaveBeenLastCalledWith(expectedUrl, { + headers: { + "Content-Type": "application/xml", + "X-Sdk": "node" + } + }) + expect(response).toEqual(mockData) + }) + +}) diff --git a/tests/rates.test.js b/tests/rates.test.js index 8211130..7f791f0 100644 --- a/tests/rates.test.js +++ b/tests/rates.test.js @@ -62,7 +62,7 @@ describe("Fetching rates works as expected", () => { }) ) const response = await rates.get(); - expect(fetch).toHaveBeenLastCalledWith("https://currencyapi.net/api/v1/rates?key=invalidKey&output=JSON&base=USD", { + expect(fetch).toHaveBeenLastCalledWith("https://currencyapi.net/api/v2/rates?key=invalidKey&output=JSON&base=USD", { headers: { "Content-Type": "application/json", "X-Sdk": "node" @@ -91,7 +91,7 @@ describe("Fetching rates works as expected", () => { rates.output('XmL') const response = await rates.get() - expect(fetch).toHaveBeenLastCalledWith("https://currencyapi.net/api/v1/rates?key=invalidKey&output=XML&base=USD", { + expect(fetch).toHaveBeenLastCalledWith("https://currencyapi.net/api/v2/rates?key=invalidKey&output=XML&base=USD", { headers: { "Content-Type": "application/xml", "X-Sdk": "node" diff --git a/tests/timeframe.test.js b/tests/timeframe.test.js index a91a055..6a39640 100644 --- a/tests/timeframe.test.js +++ b/tests/timeframe.test.js @@ -69,7 +69,7 @@ describe("Fetching timeframe works as expected", () => { timeframe.startDate('2023-01-02') timeframe.endDate('2023-01-03') const response = await timeframe.get(); - const expectedUrl = 'https://currencyapi.net/api/v1/timeframe?key=invalidKey&output=JSON&base=USD&start_date=2023-01-02&end_date=2023-01-03' + const expectedUrl = 'https://currencyapi.net/api/v2/timeframe?key=invalidKey&output=JSON&base=USD&start_date=2023-01-02&end_date=2023-01-03' expect(fetch).toHaveBeenLastCalledWith(expectedUrl, { headers: { @@ -96,7 +96,7 @@ describe("Fetching timeframe works as expected", () => { timeframe.startDate('2023-01-02') timeframe.endDate('2023-01-03') const response = await timeframe.get(); - const expectedUrl = 'https://currencyapi.net/api/v1/timeframe?key=invalidKey&output=XML&base=USD&start_date=2023-01-02&end_date=2023-01-03' + const expectedUrl = 'https://currencyapi.net/api/v2/timeframe?key=invalidKey&output=XML&base=USD&start_date=2023-01-02&end_date=2023-01-03' expect(fetch).toHaveBeenLastCalledWith(expectedUrl, { headers: {