laplace-api 5.1.0 → 5.2.0

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": "5.1.0",
3
+ "version": "5.2.0",
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,124 +1,139 @@
1
- import {Client} from "./client";
2
- import {Locale, Region} from "./collections";
3
- import {PaginatedResponse} from "./capital_increase";
4
- import {SortDirection} from "./broker";
1
+ import { Client } from "./client";
2
+ import { Locale, Region } from "./collections";
3
+ import { PaginatedResponse } from "./capital_increase";
4
+ import { SortDirection } from "./broker";
5
5
 
6
6
  export interface NewsHighlights {
7
- consumer: string[];
8
- energyAndUtilities: string[];
9
- finance: string[];
10
- healthcare: string[];
11
- industrialsAndMaterials: string[];
12
- tech: string[];
13
- other: string[];
7
+ consumer: string[];
8
+ energyAndUtilities: string[];
9
+ finance: string[];
10
+ healthcare: string[];
11
+ industrialsAndMaterials: string[];
12
+ tech: string[];
13
+ other: string[];
14
14
  }
15
15
 
16
16
  export enum NewsType {
17
- BRIEFS = "briefs",
18
- BLOOMBERG = "bloomberg",
19
- FDA = "fda",
20
- REUTERS = "reuters",
17
+ BRIEFS = "briefs",
18
+ BLOOMBERG = "bloomberg",
19
+ FDA = "fda",
20
+ REUTERS = "reuters",
21
21
  }
22
22
 
23
23
  export enum NewsOrderBy {
24
- TIMESTAMP = "timestamp",
24
+ TIMESTAMP = "timestamp",
25
25
  }
26
26
 
27
27
  export interface News {
28
- url: string;
29
- imageUrl: string;
30
- timestamp: string;
31
- publisherUrl: string;
32
- publisher: NewsPublisher;
33
- relatedTickers: NewsTicker[];
34
- qualityScore: number;
35
- createdAt: string;
36
- tickers?: NewsTicker[];
37
- categories?: NewsCategories;
38
- sectors?: NewsSector;
39
- content?: NewsContent;
40
- industries?: NewsIndustry;
28
+ url: string;
29
+ imageUrl: string;
30
+ timestamp: string;
31
+ publisherUrl: string;
32
+ publisher: NewsPublisher;
33
+ relatedTickers: NewsTicker[];
34
+ qualityScore: number;
35
+ createdAt: string;
36
+ tickers?: NewsTicker[];
37
+ categories?: NewsCategories;
38
+ sectors?: NewsSector;
39
+ content?: NewsContent;
40
+ industries?: NewsIndustry;
41
41
  }
42
42
 
43
43
  export interface NewsPublisher {
44
- name: string;
45
- logoUrl: string | null;
44
+ name: string;
45
+ logoUrl: string | null;
46
46
  }
47
47
 
48
48
  export interface NewsTicker {
49
- id: string;
50
- name: string;
51
- symbol?: string;
49
+ id: string;
50
+ name: string;
51
+ symbol?: string;
52
52
  }
53
53
 
54
54
  export interface NewsCategories {
55
- name: string;
56
- newsCount: number;
57
- categoryType?: string | null;
58
- meanType?: number | null;
55
+ name: string;
56
+ newsCount: number;
57
+ categoryType?: string | null;
58
+ meanType?: number | null;
59
59
  }
60
60
 
61
61
  export interface NewsSector {
62
- name: string;
63
- newsCount: number;
64
- categoryType?: string | null;
65
- meanType?: number | null;
62
+ name: string;
63
+ newsCount: number;
64
+ categoryType?: string | null;
65
+ meanType?: number | null;
66
66
  }
67
67
 
68
68
  export interface NewsContent {
69
- title: string;
70
- description: string;
71
- content: string[];
72
- summary: string[];
73
- investorInsight: string;
69
+ title: string;
70
+ description: string;
71
+ content: string[];
72
+ summary: string[];
73
+ investorInsight: string;
74
74
  }
75
75
 
76
76
  export interface NewsIndustry {
77
- name: string;
78
- meanType: number;
77
+ name: string;
78
+ meanType: number;
79
79
  }
80
80
 
81
81
  export class NewsClient extends Client {
82
- async getHighlights(
83
- region: Region,
84
- locale: Locale
85
- ): Promise<NewsHighlights> {
86
- return this.sendRequest<NewsHighlights>({
87
- method: "GET",
88
- url: "/api/v1/news/highlights",
89
- params: {
90
- region,
91
- locale,
92
- },
93
- });
94
- }
95
-
82
+ async getHighlights(
83
+ region: Region,
84
+ locale: Locale
85
+ ): Promise<NewsHighlights> {
86
+ return this.sendRequest<NewsHighlights>({
87
+ method: "GET",
88
+ url: "/api/v1/news/highlights",
89
+ params: {
90
+ region,
91
+ locale,
92
+ },
93
+ });
94
+ }
96
95
 
97
- async getNews(
98
- region: Region,
99
- locale: Locale,
100
- newsType?: NewsType,
101
- page?: number,
102
- size?: number,
103
- orderBy?: NewsOrderBy,
104
- orderByDirection?: SortDirection,
105
- extraFilters?: string
106
- ): Promise<PaginatedResponse<News>> {
107
- const params = {
108
- region,
109
- locale,
110
- ...(newsType != null && { newsType }),
111
- ...(page != null && { page }),
112
- ...(size != null && { size }),
113
- ...(orderBy != null && { orderBy }),
114
- ...(orderByDirection != null && { orderByDirection }),
115
- ...(extraFilters != null && { extraFilters }),
116
- };
117
-
118
- return this.sendRequest<PaginatedResponse<News>>({
119
- method: "GET",
120
- url: "/api/v1/news",
121
- params,
122
- });
123
- }
96
+
97
+ async getNews(
98
+ region: Region,
99
+ locale: Locale,
100
+ newsType?: NewsType,
101
+ page?: number,
102
+ size?: number,
103
+ orderBy?: NewsOrderBy,
104
+ orderByDirection?: SortDirection,
105
+ extraFilters?: string
106
+ ): Promise<PaginatedResponse<News>> {
107
+ const params = {
108
+ region,
109
+ locale,
110
+ ...(newsType != null && { newsType }),
111
+ ...(page != null && { page }),
112
+ ...(size != null && { size }),
113
+ ...(orderBy != null && { orderBy }),
114
+ ...(orderByDirection != null && { orderByDirection }),
115
+ ...(extraFilters != null && { extraFilters }),
116
+ };
117
+
118
+ return this.sendRequest<PaginatedResponse<News>>({
119
+ method: "GET",
120
+ url: "/api/v1/news",
121
+ params,
122
+ });
123
+ }
124
+
125
+ streamNews(
126
+ locale: Locale,
127
+ sectors?: string[],
128
+ tickers?: string[],
129
+ categories?: string[],
130
+ industries?: string[]
131
+ ): { events: AsyncIterable<News[]>, cancel: () => void } {
132
+ let url = `${this["baseUrl"]}/api/v1/news/stream?locale=${locale}`;
133
+ if (sectors?.length) url += `&sectors=${encodeURIComponent(sectors.join(","))}`;
134
+ if (tickers?.length) url += `&tickers=${encodeURIComponent(tickers.join(","))}`;
135
+ if (categories?.length) url += `&categories=${encodeURIComponent(categories.join(","))}`;
136
+ if (industries?.length) url += `&industries=${encodeURIComponent(industries.join(","))}`;
137
+ return this.sendSSERequest<News[]>(url);
138
+ }
124
139
  }
@@ -1,4 +1,5 @@
1
1
  import { Logger } from "winston";
2
+ import axios from "axios";
2
3
  import { LaplaceConfiguration } from "../utilities/configuration";
3
4
  import {
4
5
  NewsClient,
@@ -10,61 +11,61 @@ import { Region, Locale } from "../client/collections";
10
11
  import { SortDirection } from "../client/broker";
11
12
 
12
13
  const mockNewsHighlightsResponse = {
13
- tech: [
14
- "Alphabet ve Amazon'un desteğiyle Anthropic, 2026 başlarında Hindistan'ın Bengaluru kentinde bir ofis açacak."
15
- ],
16
- other: [
17
- "ABD Yüksek Mahkemesi, Epic Games'in davası kapsamında Google'ın Play uygulamalarındaki değişikliği engellemeyecek."
18
- ],
19
- finance: [
20
- "Fifth Third Bank, Comerica'yı 10,9 milyar dolara satın alacak ve böylece ABD'nin 9. en büyük bankası olacak."
21
- ],
22
- consumer: [
23
- "Tesla, rekabet ortamında pazar payını geri almak için daha ucuz Model Y ve Model 3'ü piyasaya sürdü; duyuru hisseleri etkiledi."
24
- ],
25
- healthcare: [
26
- "İlaç üreticileri, Amgen ve Novo Nordisk'in de dahil olduğu şekilde, Trump'ın ilaç fiyatlarını düşürme planıyla uyumlu olarak tele-sağlık satışlarını artırıyor."
27
- ],
28
- energyAndUtilities: [
29
- "ABD Enerji Bakanlığı, Stellantis ve GM'ye verilen 1,1 milyar dolarlık hibeleri iptal edebilir."
30
- ],
31
- industrialsAndMaterials: [
32
- "Boeing, bir grevi sona erdirmek için IAM Sendikası ile geçici bir anlaşmaya vardı; detaylar açıklanmadı."
33
- ]
34
- };
35
-
36
- const mockNewsResponse = {
37
- items: [
38
- {
39
- url: "https://www.reuters.com/business/energy/commonwealth-lng-wants-more-time-build-planned-export-facility-louisiana-2025-10-07/",
40
- content: {
41
- title: "Commonwealth LNG wants more time to build planned export facility in Louisiana",
42
- content: [
43
- "Commonwealth LNG has requested a four-year extension from federal regulators to construct & begin exporting liquefied natural gas..."
44
- ],
45
- summary: [
46
- "Commonwealth LNG has requested a four-year extension from federal regulators..."
47
- ],
48
- description:
49
- "Commonwealth LNG has asked federal regulators for a four-year extension...",
50
- investorInsight:
51
- "What it means for investors: The extension request could postpone..."
52
- },
53
- sectors: { name: "Energy", meanType: 9, newsCount: 1 },
54
- tickers: [{ id: "6203d1ba1e674875275558f7", name: "EQT Corp", symbol: "EQT" }],
55
- imageUrl: "",
56
- createdAt: "2025-10-07T17:10:01.560644Z",
57
- publisher: { name: "Reuters", logoUrl: null },
58
- timestamp: "2025-10-07T16:50:16Z",
59
- categories: { name: "Sector News", newsCount: 1, categoryType: "StockSpesific" },
60
- industries: { name: "Oil/Gas (Production and Exploration)", meanType: 78 },
61
- publisherUrl: "Reuters",
62
- qualityScore: 0,
63
- relatedTickers: [{ id: "6203d1ba1e674875275558f7", name: "EQT Corp", symbol: "EQT" }]
64
- }
65
- ],
66
- recordCount: 352
67
- };
14
+ tech: [
15
+ "Alphabet ve Amazon'un desteğiyle Anthropic, 2026 başlarında Hindistan'ın Bengaluru kentinde bir ofis açacak."
16
+ ],
17
+ other: [
18
+ "ABD Yüksek Mahkemesi, Epic Games'in davası kapsamında Google'ın Play uygulamalarındaki değişikliği engellemeyecek."
19
+ ],
20
+ finance: [
21
+ "Fifth Third Bank, Comerica'yı 10,9 milyar dolara satın alacak ve böylece ABD'nin 9. en büyük bankası olacak."
22
+ ],
23
+ consumer: [
24
+ "Tesla, rekabet ortamında pazar payını geri almak için daha ucuz Model Y ve Model 3'ü piyasaya sürdü; duyuru hisseleri etkiledi."
25
+ ],
26
+ healthcare: [
27
+ "İlaç üreticileri, Amgen ve Novo Nordisk'in de dahil olduğu şekilde, Trump'ın ilaç fiyatlarını düşürme planıyla uyumlu olarak tele-sağlık satışlarını artırıyor."
28
+ ],
29
+ energyAndUtilities: [
30
+ "ABD Enerji Bakanlığı, Stellantis ve GM'ye verilen 1,1 milyar dolarlık hibeleri iptal edebilir."
31
+ ],
32
+ industrialsAndMaterials: [
33
+ "Boeing, bir grevi sona erdirmek için IAM Sendikası ile geçici bir anlaşmaya vardı; detaylar açıklanmadı."
34
+ ]
35
+ };
36
+
37
+ const mockNewsResponse = {
38
+ items: [
39
+ {
40
+ url: "https://www.reuters.com/business/energy/commonwealth-lng-wants-more-time-build-planned-export-facility-louisiana-2025-10-07/",
41
+ content: {
42
+ title: "Commonwealth LNG wants more time to build planned export facility in Louisiana",
43
+ content: [
44
+ "Commonwealth LNG has requested a four-year extension from federal regulators to construct & begin exporting liquefied natural gas..."
45
+ ],
46
+ summary: [
47
+ "Commonwealth LNG has requested a four-year extension from federal regulators..."
48
+ ],
49
+ description:
50
+ "Commonwealth LNG has asked federal regulators for a four-year extension...",
51
+ investorInsight:
52
+ "What it means for investors: The extension request could postpone..."
53
+ },
54
+ sectors: { name: "Energy", meanType: 9, newsCount: 1 },
55
+ tickers: [{ id: "6203d1ba1e674875275558f7", name: "EQT Corp", symbol: "EQT" }],
56
+ imageUrl: "",
57
+ createdAt: "2025-10-07T17:10:01.560644Z",
58
+ publisher: { name: "Reuters", logoUrl: null },
59
+ timestamp: "2025-10-07T16:50:16Z",
60
+ categories: { name: "Sector News", newsCount: 1, categoryType: "StockSpesific" },
61
+ industries: { name: "Oil/Gas (Production and Exploration)", meanType: 78 },
62
+ publisherUrl: "Reuters",
63
+ qualityScore: 0,
64
+ relatedTickers: [{ id: "6203d1ba1e674875275558f7", name: "EQT Corp", symbol: "EQT" }]
65
+ }
66
+ ],
67
+ recordCount: 352
68
+ };
68
69
 
69
70
  describe("NewsClient", () => {
70
71
  let client: NewsClient;
@@ -150,13 +151,13 @@ describe("NewsClient", () => {
150
151
  expect(typeof n.categories.newsCount).toBe("number");
151
152
  expect(
152
153
  typeof n.categories.categoryType === "string" ||
153
- n.categories.categoryType == null ||
154
- n.categories.categoryType === undefined
154
+ n.categories.categoryType == null ||
155
+ n.categories.categoryType === undefined
155
156
  ).toBe(true);
156
157
  expect(
157
158
  typeof n.categories.meanType === "number" ||
158
- n.categories.meanType == null ||
159
- n.categories.meanType === undefined
159
+ n.categories.meanType == null ||
160
+ n.categories.meanType === undefined
160
161
  ).toBe(true);
161
162
  }
162
163
 
@@ -165,13 +166,13 @@ describe("NewsClient", () => {
165
166
  expect(typeof n.sectors.newsCount).toBe("number");
166
167
  expect(
167
168
  typeof n.sectors.categoryType === "string" ||
168
- n.sectors.categoryType == null ||
169
- n.sectors.categoryType === undefined
169
+ n.sectors.categoryType == null ||
170
+ n.sectors.categoryType === undefined
170
171
  ).toBe(true);
171
172
  expect(
172
173
  typeof n.sectors.meanType === "number" ||
173
- n.sectors.meanType == null ||
174
- n.sectors.meanType === undefined
174
+ n.sectors.meanType == null ||
175
+ n.sectors.meanType === undefined
175
176
  ).toBe(true);
176
177
  }
177
178
 
@@ -189,6 +190,25 @@ describe("NewsClient", () => {
189
190
  }
190
191
  }
191
192
  });
193
+
194
+ test("streamNews yields item before timeout or throws gracefully if none arrive", async () => {
195
+ let newsItemsReceived = 0;
196
+ const { events, cancel } = client.streamNews(Locale.Tr);
197
+
198
+ const receivePromise = (async () => {
199
+ for await (const items of events) {
200
+ if (items && items.length > 0) {
201
+ newsItemsReceived += items.length;
202
+ break;
203
+ }
204
+ }
205
+ })();
206
+
207
+ const timeoutPromise = new Promise((resolve) => setTimeout(resolve, 8000));
208
+ await Promise.race([receivePromise, timeoutPromise]);
209
+ cancel();
210
+ expect(newsItemsReceived).toBeGreaterThanOrEqual(0);
211
+ });
192
212
  });
193
213
 
194
214
  describe("Mock Tests", () => {
@@ -343,5 +363,72 @@ describe("NewsClient", () => {
343
363
  expect(cli.request).toHaveBeenCalledTimes(1);
344
364
  });
345
365
  });
366
+
367
+ describe("streamNews", () => {
368
+ test("calls correct endpoint/params and correctly yields stream entities", async () => {
369
+ const eventsList: any[] = [];
370
+
371
+ // Mock get response to return a readable stream
372
+ const mockStreamData = [
373
+ "data: " + JSON.stringify([{ url: "http://example.com/stream-news-1", publiser: { name: "test-publisher" } }]) + "\n\n",
374
+ "data: " + JSON.stringify([{ url: "http://example.com/stream-news-2", publiser: { name: "test-publisher-2" } }]) + "\n\n",
375
+ ];
376
+
377
+ const mockAsyncIterator = {
378
+ async *[Symbol.asyncIterator]() {
379
+ for (const chunk of mockStreamData) {
380
+ yield new TextEncoder().encode(chunk);
381
+ }
382
+ }
383
+ };
384
+
385
+ const axiosGetSpy = jest.spyOn(axios, 'get').mockResolvedValueOnce({
386
+ data: mockAsyncIterator
387
+ });
388
+
389
+ const { events, cancel } = client.streamNews(Locale.Tr);
390
+
391
+ for await (const newsList of events) {
392
+ eventsList.push(newsList);
393
+ }
394
+
395
+ expect(axiosGetSpy).toHaveBeenCalledTimes(1);
396
+ const callArgs = axiosGetSpy.mock.calls[0];
397
+ expect(callArgs[0]).toBe(`${client["baseUrl"]}/api/v1/news/stream?locale=tr`);
398
+ expect(callArgs[1]?.responseType).toBe('stream');
399
+
400
+ expect(eventsList).toHaveLength(2);
401
+ expect(eventsList[0][0].url).toBe("http://example.com/stream-news-1");
402
+ expect(eventsList[1][0].url).toBe("http://example.com/stream-news-2");
403
+
404
+ cancel();
405
+ axiosGetSpy.mockRestore();
406
+ });
407
+
408
+ test("calls correct endpoint with optional parameters", async () => {
409
+ const mockAsyncIterator = {
410
+ async *[Symbol.asyncIterator]() {
411
+ yield new TextEncoder().encode("data: " + JSON.stringify([]) + "\n\n");
412
+ }
413
+ };
414
+
415
+ const axiosGetSpy = jest.spyOn(axios, 'get').mockResolvedValueOnce({
416
+ data: mockAsyncIterator
417
+ });
418
+
419
+ const { events, cancel } = client.streamNews(Locale.En, ["tech"], ["AAPL"], ["category"], ["software"]);
420
+
421
+ for await (const _ of events) {
422
+ break;
423
+ }
424
+
425
+ expect(axiosGetSpy).toHaveBeenCalledTimes(1);
426
+ const callArgs = axiosGetSpy.mock.calls[0];
427
+ expect(callArgs[0]).toBe(`${client["baseUrl"]}/api/v1/news/stream?locale=en&sectors=tech&tickers=AAPL&categories=category&industries=software`);
428
+
429
+ cancel();
430
+ axiosGetSpy.mockRestore();
431
+ });
432
+ });
346
433
  });
347
434
  });