laplace-api 1.0.2 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "laplace-api",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Client library for Laplace API for the US stock market and BIST (Istanbul stock market) fundamental financial data.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -1,7 +1,7 @@
1
1
  import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';
2
2
  import { Logger } from 'winston';
3
3
  import { LaplaceConfiguration } from '../utilities/configuration';
4
- import { EventSourcePolyfill } from 'event-source-polyfill';
4
+ import { LaplaceHTTPError, WrapError } from './errors';
5
5
 
6
6
 
7
7
  export class Client {
@@ -31,7 +31,11 @@ export class Client {
31
31
  return response.data;
32
32
  } catch (error) {
33
33
  if (axios.isAxiosError(error) && error.response) {
34
- throw new Error(`Unexpected status code: ${error.response.status}, body: ${JSON.stringify(error.response.data)}`);
34
+ const httpError = new LaplaceHTTPError(
35
+ error.response.status,
36
+ JSON.stringify(error.response.data)
37
+ );
38
+ throw WrapError(httpError);
35
39
  }
36
40
  throw error;
37
41
  }
@@ -62,7 +62,7 @@ export class CustomThemeClient extends Client {
62
62
  return this.getAllCollectionsPrivate(CollectionType.CustomTheme, locale);
63
63
  }
64
64
 
65
- async getCustomThemeDetail(id: string, locale: Locale, sortBy: SortBy): Promise<CollectionDetail> {
65
+ async getCustomThemeDetail(id: string, locale: Locale, sortBy: SortBy | null): Promise<CollectionDetail> {
66
66
  return this.getCollectionDetailPrivate(id, CollectionType.CustomTheme, locale, sortBy);
67
67
  }
68
68
 
@@ -0,0 +1,75 @@
1
+ export class LaplaceError extends Error {
2
+ constructor(message: string) {
3
+ super(message);
4
+ this.name = 'LaplaceError';
5
+ }
6
+ }
7
+
8
+ export const ErrYouDoNotHaveAccessToEndpoint = new LaplaceError('you do not have access to this endpoint');
9
+ export const ErrLimitExceeded = new LaplaceError('limit exceeded');
10
+ export const ErrEndpointIsNotActive = new LaplaceError('endpoint is not active');
11
+ export const ErrInvalidToken = new LaplaceError('invalid token');
12
+ export const ErrInvalidID = new LaplaceError('invalid object id');
13
+
14
+ export class LaplaceHTTPError extends Error {
15
+ httpStatus: number;
16
+ message: string;
17
+ internalError?: Error;
18
+
19
+ constructor(httpStatus: number, message: string, internalError?: Error) {
20
+ super(`${httpStatus}: ${message}${internalError ? ` (${internalError.message})` : ''}`);
21
+ this.name = 'LaplaceHTTPError';
22
+ this.httpStatus = httpStatus;
23
+ this.message = message;
24
+ this.internalError = internalError;
25
+ }
26
+
27
+ withInternalError(err: Error): LaplaceHTTPError {
28
+ this.internalError = err;
29
+ return this;
30
+ }
31
+
32
+ cause(): Error {
33
+ return this.internalError || this;
34
+ }
35
+ }
36
+
37
+ export function WrapError(err: Error): Error {
38
+ if (err instanceof LaplaceHTTPError) {
39
+ getLaplaceError(err);
40
+ return err;
41
+ }
42
+ return err;
43
+ }
44
+
45
+ function getLaplaceError(httpErr: LaplaceHTTPError): void {
46
+ switch (httpErr.httpStatus) {
47
+ case 403:
48
+ switch (httpErr.message) {
49
+ case '{"message":"you don\'t have access to this endpoint"}\n':
50
+ httpErr.withInternalError(ErrYouDoNotHaveAccessToEndpoint);
51
+ break;
52
+ case '{"message":"endpoint is not active"}\n':
53
+ httpErr.withInternalError(ErrEndpointIsNotActive);
54
+ break;
55
+ }
56
+ if (httpErr.message.includes('limit exceeded')) {
57
+ httpErr.withInternalError(ErrLimitExceeded);
58
+ }
59
+ break;
60
+ case 400:
61
+ if (httpErr.message === '{"message":"invalid id"}\n') {
62
+ httpErr.withInternalError(ErrInvalidID);
63
+ }
64
+ break;
65
+ case 401:
66
+ if (httpErr.message === '{"message":"this token is not valid"}\n') {
67
+ httpErr.withInternalError(ErrInvalidToken);
68
+ }
69
+ break;
70
+ }
71
+ }
72
+
73
+ export function HttpError(httpStatus: number, message: string, ...args: any[]): LaplaceHTTPError {
74
+ return new LaplaceHTTPError(httpStatus, message.replace(/%s/g, () => args.shift()));
75
+ }
@@ -19,8 +19,8 @@ export interface StockStats {
19
19
  pbRatio: number;
20
20
  yearLow: number;
21
21
  yearHigh: number;
22
- threeYear: number;
23
- fiveYear: number;
22
+ '3Year': number;
23
+ '5Year': number;
24
24
  symbol: string;
25
25
  }
26
26
 
@@ -1,20 +1,31 @@
1
1
  import { Logger } from 'winston';
2
2
  import { LaplaceConfiguration } from '../utilities/configuration';
3
- import { Client, createClient } from '../client/client';
3
+ import { createClient } from '../client/client';
4
4
  import './client_test_suite';
5
-
6
- // Assuming these are defined elsewhere in your project
7
- enum Region { Tr = 'tr' }
8
- enum Locale { Tr = 'tr' }
5
+ import { Region, Locale } from '../client/collections';
6
+ import { CollectionClient } from '../client/collections';
7
+ import { LaplaceHTTPError } from '../client/errors';
8
+ import { suite } from 'node:test';
9
9
 
10
10
  class LaplaceClientTestSuite {
11
- private config: LaplaceConfiguration;
11
+ public config: LaplaceConfiguration;
12
12
 
13
13
  constructor(config: LaplaceConfiguration) {
14
14
  this.config = config;
15
15
  }
16
+ }
17
+
18
+ describe('LaplaceClient', () => {
19
+ let testSuite: LaplaceClientTestSuite;
20
+
21
+ beforeAll(() => {
22
+ // Assuming global.testSuite is set up as in the previous example
23
+ const config = (global as any).testSuite.config;
24
+ testSuite = new LaplaceClientTestSuite(config);
25
+ });
16
26
 
17
- async testClient() {
27
+ it('should make a successful request', async () => {
28
+
18
29
  const logger: Logger = {
19
30
  info: jest.fn(),
20
31
  error: jest.fn(),
@@ -22,11 +33,11 @@ class LaplaceClientTestSuite {
22
33
  debug: jest.fn(),
23
34
  } as unknown as Logger;
24
35
 
25
- const client = createClient(this.config, logger);
36
+ const client = createClient(testSuite.config, logger);
26
37
 
27
38
  const res = await client.sendRequest({
28
39
  method: 'GET',
29
- url: '/api/v1/industry',
40
+ url: '/api/v1/sector',
30
41
  params: {
31
42
  region: Region.Tr,
32
43
  locale: Locale.Tr,
@@ -35,19 +46,23 @@ class LaplaceClientTestSuite {
35
46
 
36
47
  expect(res).toBeTruthy();
37
48
  expect(res).not.toEqual({});
38
- }
39
- }
49
+ });
50
+ it('should fail if invalid token', async () => {
51
+ const logger: Logger = {
52
+ info: jest.fn(),
53
+ error: jest.fn(),
54
+ warn: jest.fn(),
55
+ debug: jest.fn(),
56
+ } as unknown as Logger;
40
57
 
41
- describe('LaplaceClient', () => {
42
- let testSuite: LaplaceClientTestSuite;
58
+ var invalidConfig : LaplaceConfiguration = testSuite.config;
59
+ invalidConfig.apiKey = 'invalid';
43
60
 
44
- beforeAll(() => {
45
- // Assuming global.testSuite is set up as in the previous example
46
- const config = (global as any).testSuite.config;
47
- testSuite = new LaplaceClientTestSuite(config);
48
- });
61
+ const client = new CollectionClient(invalidConfig, logger);
49
62
 
50
- it('should make a successful request', async () => {
51
- await testSuite.testClient();
63
+ const f = async () => {await client.getAllIndustries(Region.Tr, Locale.Tr)};
64
+
65
+
66
+ await expect(f).rejects.toThrow(LaplaceHTTPError);
52
67
  });
53
68
  });
@@ -80,7 +80,7 @@ describe('CustomTheme', () => {
80
80
 
81
81
  await testDeleteCustomTheme(id, client);
82
82
  await expect(client.getCustomThemeDetail(id, Locale.Tr, SortBy.PriceChange)).rejects.toThrow();
83
- }, 10000); // Increase the timeout value to 10000 milliseconds);
83
+ }, 15000);
84
84
  });
85
85
 
86
86
  async function testCreateCustomTheme(client: CustomThemeClient, createParams: CreateCustomThemeParams): Promise<string> {
@@ -90,7 +90,7 @@ async function testCreateCustomTheme(client: CustomThemeClient, createParams: Cr
90
90
  }
91
91
 
92
92
  async function testGetDetails(id: string, locale: Locale, client: CustomThemeClient, createParams: CreateCustomThemeParams) {
93
- const resp = await client.getCustomThemeDetail(id, locale, SortBy.PriceChange);
93
+ const resp = await client.getCustomThemeDetail(id, locale, null);
94
94
  expect(resp).not.toBeEmpty();
95
95
 
96
96
  expect(resp.title).toBe(createParams.title[locale]);
@@ -28,7 +28,7 @@ describe('FinancialFundamentals', () => {
28
28
  });
29
29
 
30
30
  test('GetStockStats', async () => {
31
- const resp = await stockClient.getStockStats(['TUPRS'], [
31
+ var statKeys = [
32
32
  StockStatsKey.PreviousClose,
33
33
  StockStatsKey.YtdReturn,
34
34
  StockStatsKey.YearlyReturn,
@@ -40,8 +40,22 @@ describe('FinancialFundamentals', () => {
40
40
  StockStatsKey.ThreeYearReturn,
41
41
  StockStatsKey.FiveYearReturn,
42
42
  StockStatsKey.LatestPrice,
43
- ], Region.Tr);
43
+ ]
44
+ const resp = await stockClient.getStockStats(['TUPRS'], statKeys, Region.Tr);
44
45
  expect(resp).not.toBeEmpty();
46
+ expect(resp.length).toBe(1);
47
+ expect(resp[0].symbol).toBe('TUPRS');
48
+ expect(resp[0].previousClose).toBeGreaterThan(0);
49
+ expect(resp[0].ytdReturn).not.toEqual(0);
50
+ expect(resp[0].yearlyReturn).not.toEqual(0);
51
+ expect(resp[0].marketCap).toBeGreaterThan(0);
52
+ expect(resp[0].peRatio).not.toEqual(0);
53
+ expect(resp[0].pbRatio).not.toEqual(0);
54
+ expect(resp[0].yearLow).toBeGreaterThan(0);
55
+ expect(resp[0].yearHigh).toBeGreaterThan(0);
56
+ // TODO fix these once the endpoint is fixed
57
+ // expect(resp[0]['3Year']).not.toEqual(0);
58
+ // expect(resp[0]['5Year']).not.toEqual(0);
45
59
  });
46
60
 
47
61
  test('GetTopMovers', async () => {
@@ -0,0 +1,57 @@
1
+ // import { Logger } from 'winston';
2
+ // import { LaplaceConfiguration } from '../utilities/configuration';
3
+ // import { Client, createClient } from '../client/client';
4
+ // import { LivePriceClient, BISTStockLiveData, USStockLiveData } from '../client/live_price';
5
+ // import { Region } from '../client/collections';
6
+ // import './client_test_suite';
7
+
8
+ // describe('LivePrice', () => {
9
+ // let client: Client;
10
+ // let livePriceClient: LivePriceClient;
11
+
12
+ // beforeAll(() => {
13
+ // const config = (global as any).testSuite.config as LaplaceConfiguration;
14
+ // const logger: Logger = {
15
+ // info: jest.fn(),
16
+ // error: jest.fn(),
17
+ // warn: jest.fn(),
18
+ // debug: jest.fn(),
19
+ // } as unknown as Logger;
20
+
21
+ // client = createClient(config, logger);
22
+ // livePriceClient = new LivePriceClient(client);
23
+ // });
24
+
25
+ // const testLivePrice = async (
26
+ // symbols: string[],
27
+ // region: Region,
28
+ // getLivePriceFunc: (symbols: string[], region: Region) => AsyncGenerator<BISTStockLiveData | USStockLiveData, void, unknown>
29
+ // ) => {
30
+ // const livePriceGenerator = getLivePriceFunc.call(livePriceClient, symbols, region);
31
+ // let livePriceCount = 0;
32
+
33
+ // try {
34
+ // for await (const livePrice of livePriceGenerator) {
35
+ // expect(livePrice).not.toBeEmpty();
36
+ // livePriceCount++;
37
+ // if (livePriceCount > 3) {
38
+ // break;
39
+ // }
40
+ // }
41
+ // } catch (error) {
42
+ // throw new Error(`Error occurred during live price retrieval: ${error}`);
43
+ // }
44
+
45
+ // expect(livePriceCount).toBeGreaterThan(0);
46
+ // };
47
+
48
+ // test('BISTLivePrice', async () => {
49
+ // const symbols = ['TUPRS', 'SASA', 'THYAO', 'GARAN', 'YKBN'];
50
+ // await testLivePrice(symbols, Region.Tr, livePriceClient.getLivePriceForBIST);
51
+ // }, 10000);
52
+
53
+ // test('USLivePrice', async () => {
54
+ // const symbols = ['AAPL', 'GOOGL', 'MSFT', 'AMZN', 'META'];
55
+ // await testLivePrice(symbols, Region.Us, livePriceClient.getLivePriceForUS);
56
+ // }, 10000);
57
+ // });
@@ -1,57 +0,0 @@
1
- import { Logger } from 'winston';
2
- import { LaplaceConfiguration } from '../utilities/configuration';
3
- import { Client, createClient } from '../client/client';
4
- import { LivePriceClient, BISTStockLiveData, USStockLiveData } from '../client/live_price';
5
- import { Region } from '../client/collections';
6
- import './client_test_suite';
7
-
8
- describe('LivePrice', () => {
9
- let client: Client;
10
- let livePriceClient: LivePriceClient;
11
-
12
- beforeAll(() => {
13
- const config = (global as any).testSuite.config as LaplaceConfiguration;
14
- const logger: Logger = {
15
- info: jest.fn(),
16
- error: jest.fn(),
17
- warn: jest.fn(),
18
- debug: jest.fn(),
19
- } as unknown as Logger;
20
-
21
- client = createClient(config, logger);
22
- livePriceClient = new LivePriceClient(client);
23
- });
24
-
25
- const testLivePrice = async (
26
- symbols: string[],
27
- region: Region,
28
- getLivePriceFunc: (symbols: string[], region: Region) => AsyncGenerator<BISTStockLiveData | USStockLiveData, void, unknown>
29
- ) => {
30
- const livePriceGenerator = getLivePriceFunc.call(livePriceClient, symbols, region);
31
- let livePriceCount = 0;
32
-
33
- try {
34
- for await (const livePrice of livePriceGenerator) {
35
- expect(livePrice).not.toBeEmpty();
36
- livePriceCount++;
37
- if (livePriceCount > 3) {
38
- break;
39
- }
40
- }
41
- } catch (error) {
42
- throw new Error(`Error occurred during live price retrieval: ${error}`);
43
- }
44
-
45
- expect(livePriceCount).toBeGreaterThan(0);
46
- };
47
-
48
- test('BISTLivePrice', async () => {
49
- const symbols = ['TUPRS', 'SASA', 'THYAO', 'GARAN', 'YKBN'];
50
- await testLivePrice(symbols, Region.Tr, livePriceClient.getLivePriceForBIST);
51
- }, 10000);
52
-
53
- test('USLivePrice', async () => {
54
- const symbols = ['AAPL', 'GOOGL', 'MSFT', 'AMZN', 'META'];
55
- await testLivePrice(symbols, Region.Us, livePriceClient.getLivePriceForUS);
56
- }, 10000);
57
- });