laplace-api 5.2.3 → 5.5.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.2.3",
3
+ "version": "5.5.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": {
@@ -22,8 +22,37 @@ export enum NewsType {
22
22
 
23
23
  export enum NewsOrderBy {
24
24
  TIMESTAMP = "timestamp",
25
+ QUALITY_SCORE = "quality_score",
25
26
  }
26
27
 
28
+ export enum NewsLane {
29
+ GLOBAL_MACRO = "global_macro",
30
+ TR_EKONOMI = "tr_ekonomi",
31
+ BIST = "bist",
32
+ FAST_MOVERS = "fast_movers",
33
+ }
34
+
35
+ export interface GetNewsParams {
36
+ lane?: NewsLane;
37
+ apiSource?: string;
38
+ newsType?: NewsType;
39
+ orderBy?: NewsOrderBy;
40
+ orderByDirection?: SortDirection;
41
+ symbols?: string;
42
+ categories?: string;
43
+ sectors?: string;
44
+ industries?: string;
45
+ qualityScoreMin?: number;
46
+ qualityScoreMax?: number;
47
+ timestampFrom?: string;
48
+ timestampTo?: string;
49
+ page?: number;
50
+ size?: number;
51
+ }
52
+
53
+ /** @deprecated Use {@link GetNewsParams}; v1 and v2 now accept the same filters. */
54
+ export type GetNewsV2Params = GetNewsParams;
55
+
27
56
  export interface News {
28
57
  url: string;
29
58
  imageUrl: string;
@@ -80,6 +109,21 @@ export interface NewsIndustry {
80
109
  meanType: number;
81
110
  }
82
111
 
112
+ export interface NewsCategory {
113
+ id: string;
114
+ name: string;
115
+ }
116
+
117
+ export interface NewsLaneInfo {
118
+ id: NewsLane;
119
+ label: string;
120
+ }
121
+
122
+ export interface NewsApiSource {
123
+ id: string;
124
+ name: string;
125
+ }
126
+
83
127
  export class NewsClient extends Client {
84
128
  async getHighlights(
85
129
  region: Region,
@@ -96,59 +140,85 @@ export class NewsClient extends Client {
96
140
  }
97
141
 
98
142
 
99
- async getNews(
143
+ async getNewsCategories(locale?: Locale): Promise<NewsCategory[]> {
144
+ return this.sendRequest<NewsCategory[]>({
145
+ method: "GET",
146
+ url: "/api/v1/news/categories",
147
+ params: {
148
+ ...(locale != null && { locale }),
149
+ },
150
+ });
151
+ }
152
+
153
+ async getNewsLanes(): Promise<NewsLaneInfo[]> {
154
+ return this.sendRequest<NewsLaneInfo[]>({
155
+ method: "GET",
156
+ url: "/api/v1/news/lanes",
157
+ });
158
+ }
159
+
160
+ async getApiSourceNames(): Promise<NewsApiSource[]> {
161
+ return this.sendRequest<NewsApiSource[]>({
162
+ method: "GET",
163
+ url: "/api/v1/news/api-source-names",
164
+ });
165
+ }
166
+
167
+ private buildNewsFilterParams(
100
168
  region: Region,
101
169
  locale: Locale,
102
- newsType?: NewsType,
103
- page?: number,
104
- size?: number,
105
- orderBy?: NewsOrderBy,
106
- orderByDirection?: SortDirection,
107
- extraFilters?: string
108
- ): Promise<PaginatedResponse<News>> {
109
- const params = {
170
+ options?: GetNewsParams
171
+ ): Record<string, unknown> {
172
+ return {
110
173
  region,
111
174
  locale,
112
- ...(newsType != null && { newsType }),
113
- ...(page != null && { page }),
114
- ...(size != null && { size }),
115
- ...(orderBy != null && { orderBy }),
116
- ...(orderByDirection != null && { orderByDirection }),
117
- ...(extraFilters != null && { extraFilters }),
175
+ ...(options?.lane != null && { lane: options.lane }),
176
+ ...(options?.apiSource != null && { apiSource: options.apiSource }),
177
+ ...(options?.newsType != null && { newsType: options.newsType }),
178
+ ...(options?.orderBy != null && { orderBy: options.orderBy }),
179
+ ...(options?.orderByDirection != null && {
180
+ orderByDirection: options.orderByDirection,
181
+ }),
182
+ ...(options?.symbols != null && { symbols: options.symbols }),
183
+ ...(options?.categories != null && { categories: options.categories }),
184
+ ...(options?.sectors != null && { sectors: options.sectors }),
185
+ ...(options?.industries != null && { industries: options.industries }),
186
+ ...(options?.qualityScoreMin != null && {
187
+ qualityScoreMin: options.qualityScoreMin,
188
+ }),
189
+ ...(options?.qualityScoreMax != null && {
190
+ qualityScoreMax: options.qualityScoreMax,
191
+ }),
192
+ ...(options?.timestampFrom != null && {
193
+ timestampFrom: options.timestampFrom,
194
+ }),
195
+ ...(options?.timestampTo != null && { timestampTo: options.timestampTo }),
196
+ ...(options?.page != null && { page: options.page }),
197
+ ...(options?.size != null && { size: options.size }),
118
198
  };
199
+ }
119
200
 
201
+ async getNews(
202
+ region: Region,
203
+ locale: Locale,
204
+ options?: GetNewsParams
205
+ ): Promise<PaginatedResponse<News>> {
120
206
  return this.sendRequest<PaginatedResponse<News>>({
121
207
  method: "GET",
122
208
  url: "/api/v1/news",
123
- params,
209
+ params: this.buildNewsFilterParams(region, locale, options),
124
210
  });
125
211
  }
126
212
 
127
213
  async getNewsV2(
128
214
  region: Region,
129
215
  locale: Locale,
130
- newsType?: NewsType,
131
- page?: number,
132
- size?: number,
133
- orderBy?: NewsOrderBy,
134
- orderByDirection?: SortDirection,
135
- extraFilters?: string
216
+ options?: GetNewsParams
136
217
  ): Promise<PaginatedResponse<NewsV2>> {
137
- const params = {
138
- region,
139
- locale,
140
- ...(newsType != null && { newsType }),
141
- ...(page != null && { page }),
142
- ...(size != null && { size }),
143
- ...(orderBy != null && { orderBy }),
144
- ...(orderByDirection != null && { orderByDirection }),
145
- ...(extraFilters != null && { extraFilters }),
146
- };
147
-
148
218
  return this.sendRequest<PaginatedResponse<NewsV2>>({
149
219
  method: "GET",
150
220
  url: "/api/v2/news",
151
- params,
221
+ params: this.buildNewsFilterParams(region, locale, options),
152
222
  });
153
223
  }
154
224
 
@@ -158,9 +228,13 @@ export class NewsClient extends Client {
158
228
  sectors?: string[],
159
229
  tickers?: string[],
160
230
  categories?: string[],
161
- industries?: string[]
231
+ industries?: string[],
232
+ lane?: NewsLane,
233
+ apiSource?: string[]
162
234
  ): { events: AsyncIterable<NewsV2[]>, cancel: () => void } {
163
235
  let url = `${this["baseUrl"]}/api/v1/news/stream?locale=${locale}&region=${region}`;
236
+ if (lane != null) url += `&lane=${encodeURIComponent(lane)}`;
237
+ if (apiSource?.length) url += `&apiSource=${encodeURIComponent(apiSource.join(","))}`;
164
238
  if (sectors?.length) url += `&sectors=${encodeURIComponent(sectors.join(","))}`;
165
239
  if (tickers?.length) url += `&tickers=${encodeURIComponent(tickers.join(","))}`;
166
240
  if (categories?.length) url += `&categories=${encodeURIComponent(categories.join(","))}`;
@@ -5,6 +5,7 @@ import {
5
5
  NewsClient,
6
6
  NewsType,
7
7
  NewsOrderBy,
8
+ NewsLane,
8
9
  } from "../client/news";
9
10
  import "./client_test_suite";
10
11
  import { Region, Locale } from "../client/collections";
@@ -34,6 +35,20 @@ const mockNewsHighlightsResponse = {
34
35
  ]
35
36
  };
36
37
 
38
+ const mockNewsCategoriesResponse = [
39
+ { id: "13702", name: "General News" },
40
+ { id: "13703", name: "Sector News" },
41
+ { id: "13704", name: "Market News" },
42
+ { id: "13705", name: "Stock Spesific News" }
43
+ ];
44
+
45
+ const mockNewsLanesResponse = [
46
+ { id: "global_macro", label: "Global Macro" },
47
+ { id: "tr_ekonomi", label: "TR Ekonomi" },
48
+ { id: "bist", label: "BIST" },
49
+ { id: "fast_movers", label: "Fast Movers" }
50
+ ];
51
+
37
52
  const mockNewsResponse = {
38
53
  items: [
39
54
  {
@@ -107,16 +122,47 @@ describe("NewsClient", () => {
107
122
  if (first != null) expect(typeof first).toBe("string");
108
123
  });
109
124
 
125
+ test("getNewsCategories returns valid data", async () => {
126
+ const resp = await client.getNewsCategories(Locale.En);
127
+
128
+ expect(Array.isArray(resp)).toBe(true);
129
+ expect(resp.length).toBeGreaterThan(0);
130
+
131
+ const c = resp[0];
132
+ expect(typeof c.id).toBe("string");
133
+ expect(typeof c.name).toBe("string");
134
+ });
135
+
136
+ test("getNewsLanes returns valid data", async () => {
137
+ const resp = await client.getNewsLanes();
138
+
139
+ expect(Array.isArray(resp)).toBe(true);
140
+ if (resp.length > 0) {
141
+ const l = resp[0];
142
+ expect(typeof l.id).toBe("string");
143
+ expect(typeof l.label).toBe("string");
144
+ }
145
+ });
146
+
147
+ test("getApiSourceNames returns valid data", async () => {
148
+ const resp = await client.getApiSourceNames();
149
+
150
+ expect(Array.isArray(resp)).toBe(true);
151
+ if (resp.length > 0) {
152
+ const s = resp[0];
153
+ expect(typeof s.id).toBe("string");
154
+ expect(typeof s.name).toBe("string");
155
+ }
156
+ });
157
+
110
158
  test("getNews returns valid paginated data", async () => {
111
- const resp = await client.getNews(
112
- Region.Us,
113
- Locale.Tr,
114
- NewsType.BRIEFS,
115
- 0,
116
- 10,
117
- NewsOrderBy.TIMESTAMP,
118
- SortDirection.Desc
119
- );
159
+ const resp = await client.getNews(Region.Us, Locale.Tr, {
160
+ newsType: NewsType.BRIEFS,
161
+ page: 0,
162
+ size: 10,
163
+ orderBy: NewsOrderBy.TIMESTAMP,
164
+ orderByDirection: SortDirection.Desc,
165
+ });
120
166
 
121
167
  expect(resp).toBeDefined();
122
168
  expect(typeof resp.recordCount).toBe("number");
@@ -197,15 +243,13 @@ describe("NewsClient", () => {
197
243
  });
198
244
 
199
245
  test("getNewsV2 returns valid paginated data", async () => {
200
- const resp = await client.getNewsV2(
201
- Region.Us,
202
- Locale.Tr,
203
- NewsType.BRIEFS,
204
- 0,
205
- 10,
206
- NewsOrderBy.TIMESTAMP,
207
- SortDirection.Desc
208
- );
246
+ const resp = await client.getNewsV2(Region.Us, Locale.Tr, {
247
+ newsType: NewsType.BRIEFS,
248
+ page: 0,
249
+ size: 10,
250
+ orderBy: NewsOrderBy.TIMESTAMP,
251
+ orderByDirection: SortDirection.Desc,
252
+ });
209
253
 
210
254
  expect(resp).toBeDefined();
211
255
  expect(typeof resp.recordCount).toBe("number");
@@ -300,20 +344,123 @@ describe("NewsClient", () => {
300
344
  });
301
345
  });
302
346
 
347
+ describe("getNewsCategories", () => {
348
+ test("calls correct endpoint/params and matches raw response", async () => {
349
+ cli.request.mockResolvedValueOnce({ data: mockNewsCategoriesResponse });
350
+
351
+ const resp = await client.getNewsCategories(Locale.En);
352
+
353
+ expect(cli.request).toHaveBeenCalledTimes(1);
354
+ const call = cli.request.mock.calls[0][0];
355
+
356
+ expect(call.method).toBe("GET");
357
+ expect(call.url).toBe("/api/v1/news/categories");
358
+ expect(call.params).toEqual({ locale: Locale.En });
359
+
360
+ expect(resp).toEqual(mockNewsCategoriesResponse);
361
+ });
362
+
363
+ test("does not send locale when undefined", async () => {
364
+ cli.request.mockResolvedValueOnce({ data: mockNewsCategoriesResponse });
365
+
366
+ await client.getNewsCategories();
367
+
368
+ const call = cli.request.mock.calls[0][0];
369
+ expect(call.params).toEqual({});
370
+ });
371
+
372
+ test("bubbles up request error", async () => {
373
+ cli.request.mockRejectedValueOnce(new Error("Failed to fetch categories"));
374
+
375
+ await expect(client.getNewsCategories(Locale.En)).rejects.toThrow(
376
+ "Failed to fetch categories"
377
+ );
378
+
379
+ expect(cli.request).toHaveBeenCalledTimes(1);
380
+ });
381
+ });
382
+
383
+ describe("getNewsLanes", () => {
384
+ test("calls correct endpoint and matches raw response", async () => {
385
+ cli.request.mockResolvedValueOnce({ data: mockNewsLanesResponse });
386
+
387
+ const resp = await client.getNewsLanes();
388
+
389
+ expect(cli.request).toHaveBeenCalledTimes(1);
390
+ const call = cli.request.mock.calls[0][0];
391
+
392
+ expect(call.method).toBe("GET");
393
+ expect(call.url).toBe("/api/v1/news/lanes");
394
+
395
+ expect(resp).toEqual(mockNewsLanesResponse);
396
+ });
397
+
398
+ test("bubbles up request error", async () => {
399
+ cli.request.mockRejectedValueOnce(new Error("Failed to fetch lanes"));
400
+
401
+ await expect(client.getNewsLanes()).rejects.toThrow(
402
+ "Failed to fetch lanes"
403
+ );
404
+
405
+ expect(cli.request).toHaveBeenCalledTimes(1);
406
+ });
407
+ });
408
+
409
+ describe("getApiSourceNames", () => {
410
+ const mockApiSourceNames = [
411
+ { id: "BBCBusiness", name: "BBC Business" },
412
+ { id: "MarketWatch", name: "MarketWatch" },
413
+ { id: "GazeteOksijen", name: "Gazete Oksijen" }
414
+ ];
415
+
416
+ test("calls correct endpoint and matches raw response", async () => {
417
+ cli.request.mockResolvedValueOnce({ data: mockApiSourceNames });
418
+
419
+ const resp = await client.getApiSourceNames();
420
+
421
+ expect(cli.request).toHaveBeenCalledTimes(1);
422
+ const call = cli.request.mock.calls[0][0];
423
+
424
+ expect(call.method).toBe("GET");
425
+ expect(call.url).toBe("/api/v1/news/api-source-names");
426
+
427
+ expect(resp).toEqual(mockApiSourceNames);
428
+ });
429
+
430
+ test("bubbles up request error", async () => {
431
+ cli.request.mockRejectedValueOnce(
432
+ new Error("Failed to fetch api source names")
433
+ );
434
+
435
+ await expect(client.getApiSourceNames()).rejects.toThrow(
436
+ "Failed to fetch api source names"
437
+ );
438
+
439
+ expect(cli.request).toHaveBeenCalledTimes(1);
440
+ });
441
+ });
442
+
303
443
  describe("getNews", () => {
304
444
  test("calls correct endpoint/params and matches raw response", async () => {
305
445
  cli.request.mockResolvedValueOnce({ data: mockNewsResponse });
306
446
 
307
- const resp = await client.getNews(
308
- Region.Tr,
309
- Locale.Tr,
310
- NewsType.BRIEFS,
311
- 1,
312
- 10,
313
- NewsOrderBy.TIMESTAMP,
314
- SortDirection.Desc,
315
- undefined
316
- );
447
+ const resp = await client.getNews(Region.Tr, Locale.Tr, {
448
+ lane: NewsLane.BIST,
449
+ apiSource: "BBCBusiness,MarketWatch",
450
+ newsType: NewsType.BRIEFS,
451
+ page: 1,
452
+ size: 10,
453
+ orderBy: NewsOrderBy.TIMESTAMP,
454
+ orderByDirection: SortDirection.Desc,
455
+ symbols: "AAPL,MSFT",
456
+ categories: "Sector News",
457
+ sectors: "Technology",
458
+ industries: "Software",
459
+ qualityScoreMin: 7,
460
+ qualityScoreMax: 10,
461
+ timestampFrom: "2026-05-01",
462
+ timestampTo: "2026-06-01",
463
+ });
317
464
 
318
465
  expect(cli.request).toHaveBeenCalledTimes(1);
319
466
  const call = cli.request.mock.calls[0][0];
@@ -323,11 +470,21 @@ describe("NewsClient", () => {
323
470
  expect(call.params).toEqual({
324
471
  region: Region.Tr,
325
472
  locale: Locale.Tr,
473
+ lane: NewsLane.BIST,
474
+ apiSource: "BBCBusiness,MarketWatch",
326
475
  newsType: NewsType.BRIEFS,
327
476
  page: 1,
328
477
  size: 10,
329
478
  orderBy: NewsOrderBy.TIMESTAMP,
330
- orderByDirection: SortDirection.Desc
479
+ orderByDirection: SortDirection.Desc,
480
+ symbols: "AAPL,MSFT",
481
+ categories: "Sector News",
482
+ sectors: "Technology",
483
+ industries: "Software",
484
+ qualityScoreMin: 7,
485
+ qualityScoreMax: 10,
486
+ timestampFrom: "2026-05-01",
487
+ timestampTo: "2026-06-01",
331
488
  });
332
489
 
333
490
  expect(resp.recordCount).toBe(352);
@@ -387,15 +544,13 @@ describe("NewsClient", () => {
387
544
  cli.request.mockRejectedValueOnce(new Error("Failed to fetch news"));
388
545
 
389
546
  await expect(
390
- client.getNews(
391
- Region.Tr,
392
- Locale.Tr,
393
- NewsType.REUTERS,
394
- 0,
395
- 10,
396
- NewsOrderBy.TIMESTAMP,
397
- SortDirection.Desc
398
- )
547
+ client.getNews(Region.Tr, Locale.Tr, {
548
+ newsType: NewsType.REUTERS,
549
+ page: 0,
550
+ size: 10,
551
+ orderBy: NewsOrderBy.TIMESTAMP,
552
+ orderByDirection: SortDirection.Desc,
553
+ })
399
554
  ).rejects.toThrow("Failed to fetch news");
400
555
 
401
556
  expect(cli.request).toHaveBeenCalledTimes(1);
@@ -406,16 +561,23 @@ describe("NewsClient", () => {
406
561
  test("calls correct endpoint/params and matches raw response", async () => {
407
562
  cli.request.mockResolvedValueOnce({ data: mockNewsV2Response });
408
563
 
409
- const resp = await client.getNewsV2(
410
- Region.Tr,
411
- Locale.Tr,
412
- NewsType.BRIEFS,
413
- 1,
414
- 10,
415
- NewsOrderBy.TIMESTAMP,
416
- SortDirection.Desc,
417
- undefined
418
- );
564
+ const resp = await client.getNewsV2(Region.Tr, Locale.Tr, {
565
+ lane: NewsLane.GLOBAL_MACRO,
566
+ apiSource: "BBCBusiness,MarketWatch",
567
+ newsType: NewsType.BRIEFS,
568
+ page: 1,
569
+ size: 10,
570
+ orderBy: NewsOrderBy.TIMESTAMP,
571
+ orderByDirection: SortDirection.Desc,
572
+ symbols: "AAPL,MSFT",
573
+ categories: "Sector News",
574
+ sectors: "Technology",
575
+ industries: "Software",
576
+ qualityScoreMin: 7,
577
+ qualityScoreMax: 10,
578
+ timestampFrom: "2026-05-01",
579
+ timestampTo: "2026-06-01",
580
+ });
419
581
 
420
582
  expect(cli.request).toHaveBeenCalledTimes(1);
421
583
  const call = cli.request.mock.calls[0][0];
@@ -425,11 +587,21 @@ describe("NewsClient", () => {
425
587
  expect(call.params).toEqual({
426
588
  region: Region.Tr,
427
589
  locale: Locale.Tr,
590
+ lane: NewsLane.GLOBAL_MACRO,
591
+ apiSource: "BBCBusiness,MarketWatch",
428
592
  newsType: NewsType.BRIEFS,
429
593
  page: 1,
430
594
  size: 10,
431
595
  orderBy: NewsOrderBy.TIMESTAMP,
432
- orderByDirection: SortDirection.Desc
596
+ orderByDirection: SortDirection.Desc,
597
+ symbols: "AAPL,MSFT",
598
+ categories: "Sector News",
599
+ sectors: "Technology",
600
+ industries: "Software",
601
+ qualityScoreMin: 7,
602
+ qualityScoreMax: 10,
603
+ timestampFrom: "2026-05-01",
604
+ timestampTo: "2026-06-01",
433
605
  });
434
606
 
435
607
  expect(resp.recordCount).toBe(352);
@@ -457,15 +629,13 @@ describe("NewsClient", () => {
457
629
  cli.request.mockRejectedValueOnce(new Error("Failed to fetch news v2"));
458
630
 
459
631
  await expect(
460
- client.getNewsV2(
461
- Region.Tr,
462
- Locale.Tr,
463
- NewsType.REUTERS,
464
- 0,
465
- 10,
466
- NewsOrderBy.TIMESTAMP,
467
- SortDirection.Desc
468
- )
632
+ client.getNewsV2(Region.Tr, Locale.Tr, {
633
+ newsType: NewsType.REUTERS,
634
+ page: 0,
635
+ size: 10,
636
+ orderBy: NewsOrderBy.TIMESTAMP,
637
+ orderByDirection: SortDirection.Desc,
638
+ })
469
639
  ).rejects.toThrow("Failed to fetch news v2");
470
640
 
471
641
  expect(cli.request).toHaveBeenCalledTimes(1);
@@ -524,7 +694,7 @@ describe("NewsClient", () => {
524
694
  data: mockAsyncIterator
525
695
  });
526
696
 
527
- const { events, cancel } = client.streamNews(Region.Us, Locale.En, ["tech"], ["AAPL"], ["category"], ["software"]);
697
+ const { events, cancel } = client.streamNews(Region.Us, Locale.En, ["tech"], ["AAPL"], ["category"], ["software"], NewsLane.GLOBAL_MACRO, ["BBCBusiness"]);
528
698
 
529
699
  for await (const _ of events) {
530
700
  break;
@@ -532,7 +702,7 @@ describe("NewsClient", () => {
532
702
 
533
703
  expect(axiosGetSpy).toHaveBeenCalledTimes(1);
534
704
  const callArgs = axiosGetSpy.mock.calls[0];
535
- expect(callArgs[0]).toBe(`${client["baseUrl"]}/api/v1/news/stream?locale=en&region=us&sectors=tech&tickers=AAPL&categories=category&industries=software`);
705
+ expect(callArgs[0]).toBe(`${client["baseUrl"]}/api/v1/news/stream?locale=en&region=us&lane=global_macro&apiSource=BBCBusiness&sectors=tech&tickers=AAPL&categories=category&industries=software`);
536
706
 
537
707
  cancel();
538
708
  axiosGetSpy.mockRestore();