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 +1 -1
- package/src/client/news.ts +105 -90
- package/src/test/news.test.ts +150 -63
package/package.json
CHANGED
package/src/client/news.ts
CHANGED
|
@@ -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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
BRIEFS = "briefs",
|
|
18
|
+
BLOOMBERG = "bloomberg",
|
|
19
|
+
FDA = "fda",
|
|
20
|
+
REUTERS = "reuters",
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
export enum NewsOrderBy {
|
|
24
|
-
|
|
24
|
+
TIMESTAMP = "timestamp",
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export interface News {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
45
|
-
|
|
44
|
+
name: string;
|
|
45
|
+
logoUrl: string | null;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
export interface NewsTicker {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
id: string;
|
|
50
|
+
name: string;
|
|
51
|
+
symbol?: string;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
export interface NewsCategories {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
name: string;
|
|
56
|
+
newsCount: number;
|
|
57
|
+
categoryType?: string | null;
|
|
58
|
+
meanType?: number | null;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
export interface NewsSector {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
name: string;
|
|
63
|
+
newsCount: number;
|
|
64
|
+
categoryType?: string | null;
|
|
65
|
+
meanType?: number | null;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
export interface NewsContent {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
78
|
-
|
|
77
|
+
name: string;
|
|
78
|
+
meanType: number;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
export class NewsClient extends Client {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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 += `§ors=${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
|
}
|
package/src/test/news.test.ts
CHANGED
|
@@ -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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
154
|
-
|
|
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
|
-
|
|
159
|
-
|
|
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
|
-
|
|
169
|
-
|
|
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
|
-
|
|
174
|
-
|
|
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§ors=tech&tickers=AAPL&categories=category&industries=software`);
|
|
428
|
+
|
|
429
|
+
cancel();
|
|
430
|
+
axiosGetSpy.mockRestore();
|
|
431
|
+
});
|
|
432
|
+
});
|
|
346
433
|
});
|
|
347
434
|
});
|