Skip to content

Commit 6ab218d

Browse files
authored
Added research API endpoints to SDK with example (#9)
* Added openapi.yaml for research * Added openapi.yaml for research * Generated research report types from openapi yaml * Added research endpoints to SDK * Checking in string[] issue in generated types * Resolved path collision due to the dynamic and static URLs being similar
1 parent 22d8a40 commit 6ab218d

File tree

12 files changed

+870
-17
lines changed

12 files changed

+870
-17
lines changed

packages/api/src/client/base.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ import type {
3030
getProjectRecapResponse,
3131
getAssetListResponse,
3232
getAssetListParameters,
33+
getResearchReportsParameters,
34+
getResearchReportsResponse,
35+
getResearchReportByIdParameters,
36+
getResearchReportByIdResponse,
37+
getResearchReportTagsResponse,
3338
} from "@messari-kit/types";
3439
import { LogLevel, type Logger, makeConsoleLogger, createFilteredLogger, noOpLogger } from "../logging";
3540
import type { PaginatedResult, RequestOptions, ClientEventMap, ClientEventType, ClientEventHandler } from "./types";
@@ -195,6 +200,34 @@ export interface RecapsAPIInterface {
195200
getExchangeRankingsRecap(): Promise<getExchangeRankingsRecapResponse>;
196201
}
197202

203+
/**
204+
* Interface for the Research API methods
205+
*/
206+
export interface ResearchInterface {
207+
/**
208+
* Gets research reports with optional filtering
209+
* @param params Parameters for filtering research reports
210+
* @param options Optional request configuration
211+
* @returns A promise resolving to the research reports
212+
*/
213+
getResearchReports(params: getResearchReportsParameters, options?: RequestOptions): Promise<getResearchReportsResponse>;
214+
215+
/**
216+
* Gets a specific research report by ID
217+
* @param params Parameters including the report ID
218+
* @param options Optional request configuration
219+
* @returns A promise resolving to the research report
220+
*/
221+
getResearchReportById(params: getResearchReportByIdParameters, options?: RequestOptions): Promise<getResearchReportByIdResponse>;
222+
223+
/**
224+
* Gets all available research report tags
225+
* @param options Optional request configuration
226+
* @returns A promise that resolves when the operation completes
227+
*/
228+
getResearchReportTags(options?: RequestOptions): Promise<getResearchReportTagsResponse>;
229+
}
230+
198231
/**
199232
* Abstract base class for the Messari client
200233
* Defines the structure and common functionality that all client implementations must provide
@@ -225,6 +258,11 @@ export abstract class MessariClientBase {
225258
*/
226259
public abstract readonly recaps: RecapsAPIInterface;
227260

261+
/**
262+
* Interface for Research-related API methods
263+
*/
264+
public abstract readonly research: ResearchInterface;
265+
228266
/**
229267
* Logger instance for the client
230268
*/

packages/api/src/client/client.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ import {
1616
getExchangeRecap,
1717
getExchangeRankingsRecap,
1818
getAssetList,
19+
getResearchReports,
20+
getResearchReportById,
21+
getResearchReportTags,
1922
} from "@messari-kit/types";
2023
import type {
2124
createChatCompletionParameters,
@@ -50,6 +53,11 @@ import type {
5053
getExchangeRankingsRecapResponse,
5154
getAssetListParameters,
5255
getAssetListResponse,
56+
getResearchReportsParameters,
57+
getResearchReportsResponse,
58+
getResearchReportByIdParameters,
59+
getResearchReportByIdResponse,
60+
getResearchReportTagsResponse,
5361
} from "@messari-kit/types";
5462
import type { Agent } from "node:http";
5563
import { pick } from "../utils";
@@ -66,7 +74,7 @@ import type {
6674
RequestOptions,
6775
RequestParameters,
6876
} from "./types";
69-
import type { AIInterface, AssetInterface, IntelInterface, MarketsInterface, NewsInterface, RecapsAPIInterface } from "./base";
77+
import type { AIInterface, AssetInterface, IntelInterface, MarketsInterface, NewsInterface, RecapsAPIInterface, ResearchInterface } from "./base";
7078
import { MessariClientBase } from "./base";
7179

7280
/**
@@ -715,4 +723,26 @@ export class MessariClient extends MessariClientBase {
715723
});
716724
},
717725
};
726+
727+
public readonly research: ResearchInterface = {
728+
getResearchReports: (params: getResearchReportsParameters, options?: RequestOptions) =>
729+
this.request<getResearchReportsResponse>({
730+
method: getResearchReports.method,
731+
path: getResearchReports.path(),
732+
queryParams: pick(params, getResearchReports.queryParams),
733+
options,
734+
}),
735+
getResearchReportById: (params: getResearchReportByIdParameters, options?: RequestOptions) =>
736+
this.request<getResearchReportByIdResponse>({
737+
method: getResearchReportById.method,
738+
path: getResearchReportById.path(params),
739+
options,
740+
}),
741+
getResearchReportTags: (options?: RequestOptions) =>
742+
this.request<getResearchReportTagsResponse>({
743+
method: getResearchReportTags.method,
744+
path: getResearchReportTags.path(),
745+
options,
746+
}),
747+
};
718748
}

packages/examples/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
"start:news": "tsx src/newsFeed.ts",
1313
"start:chat": "tsx src/chatCompletion.ts",
1414
"start:marketdata": "tsx src/marketData.ts",
15-
"start:recaps": "tsx src/recaps.ts"
15+
"start:recaps": "tsx src/recaps.ts",
16+
"start:research": "tsx src/research.ts"
1617
},
1718
"dependencies": {
1819
"@messari-kit/api": "workspace:*",

packages/examples/src/research.ts

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
import { MessariClient, LogLevel } from "@messari-kit/api";
2+
import type { Author, Tag } from "@messari-kit/types";
3+
import { Table } from "console-table-printer";
4+
import dotenv from "dotenv";
5+
6+
// Load environment variables from .env file
7+
dotenv.config();
8+
9+
// Get API key from environment variable
10+
const API_KEY = process.env.MESSARI_API_KEY;
11+
12+
// Check if API key is available
13+
if (!API_KEY) {
14+
console.error("Error: MESSARI_API_KEY environment variable is not set.");
15+
console.error("Please create a .env file with your API key or set it in your environment.");
16+
process.exit(1);
17+
}
18+
19+
// Initialize the client with your API key
20+
const client = new MessariClient({
21+
apiKey: API_KEY,
22+
// logLevel: LogLevel.DEBUG,
23+
});
24+
25+
const newReportTable = () => {
26+
const t = new Table({
27+
columns: [
28+
{ name: "Title", alignment: "left", maxLen: 50 },
29+
{ name: "Published", alignment: "left" },
30+
{ name: "Reading Time", alignment: "right" },
31+
{ name: "Authors", alignment: "left", maxLen: 30 },
32+
{ name: "Assets", alignment: "left", maxLen: 30 },
33+
{ name: "Tags", alignment: "left", maxLen: 30 },
34+
// { name: "ID", alignment: "left" },
35+
],
36+
});
37+
return t;
38+
};
39+
40+
const parseTags = (tags: Tag[]) => tags?.map((tag) => tag.name).join(", ") || "N/A";
41+
42+
/**
43+
* Example 1: Get research reports with contentType filter
44+
*/
45+
async function getReportsBasic() {
46+
try {
47+
const response = await client.research.getResearchReports({
48+
contentType: "report",
49+
limit: 5,
50+
});
51+
52+
console.log(`Retrieved ${response.length} research reports. Latest reports:`);
53+
const t = newReportTable();
54+
for (const report of response) {
55+
t.addRow({
56+
"Title": report.title,
57+
"Published": new Date(report.publishDate).toLocaleDateString(),
58+
"Reading Time": `${report.readingTimeInMinutes.toFixed(2)} min`,
59+
"Authors": report.authors.map((author: Author) => author.name).join(", "),
60+
"Assets": report.assetIds?.length ? `${report.assetIds.length} assets` : "N/A",
61+
"Tags": parseTags(report.tags),
62+
// "ID": report.id,
63+
});
64+
}
65+
t.printTable();
66+
67+
// Save the first report ID for use in the next example
68+
return response[0]?.id;
69+
} catch (error) {
70+
console.error("Error fetching research reports:", error);
71+
throw error;
72+
}
73+
}
74+
75+
/**
76+
* Example 2: Get research reports with assetId filter
77+
*/
78+
async function getReportsByAsset(assetId: string) {
79+
try {
80+
const response = await client.research.getResearchReports({
81+
contentType: "markdown",
82+
assetId: assetId,
83+
limit: 5,
84+
});
85+
86+
console.log(`Retrieved ${response.length} research reports for asset ID: ${assetId}`);
87+
const t = newReportTable();
88+
for (const report of response) {
89+
t.addRow({
90+
"Title": report.title,
91+
"Published": new Date(report.publishDate).toLocaleDateString(),
92+
"Reading Time": `${report.readingTimeInMinutes.toFixed(2)} min`,
93+
"Authors": report.authors.map((author: Author) => author.name).join(", "),
94+
"Assets": report.assetIds?.length ? `${report.assetIds.length} assets` : "N/A",
95+
"Tags": parseTags(report.tags),
96+
// "ID": report.id,
97+
});
98+
}
99+
t.printTable();
100+
101+
return response;
102+
} catch (error) {
103+
console.error(`Error fetching reports for asset ${assetId}:`, error);
104+
throw error;
105+
}
106+
}
107+
108+
/**
109+
* Example 3: Get all available research report tags
110+
*/
111+
async function getResearchTags() {
112+
try {
113+
const tags = await client.research.getResearchReportTags();
114+
115+
console.log(`Retrieved ${tags.length} research report tags:`);
116+
// Display tags in columns
117+
const tagsPerRow = 3;
118+
for (let i = 0; i < tags.length; i += tagsPerRow) {
119+
const row = tags.slice(i, i + tagsPerRow).map((tag: string) => tag.padEnd(25));
120+
console.log(row.join(" | "));
121+
}
122+
123+
return tags;
124+
} catch (error) {
125+
console.error("Error fetching research tags:", error);
126+
throw error;
127+
}
128+
}
129+
130+
/**
131+
* Example 4: Get research reports with tag filter
132+
*/
133+
async function getReportsByTag(tag: string) {
134+
try {
135+
const response = await client.research.getResearchReports({
136+
contentType: "markdown",
137+
tags: tag,
138+
limit: 5,
139+
});
140+
141+
console.log(`Retrieved ${response.length} research reports with tag: ${tag}`);
142+
const t = newReportTable();
143+
for (const report of response) {
144+
t.addRow({
145+
"Title": report.title,
146+
"Published": new Date(report.publishDate).toLocaleDateString(),
147+
"Reading Time": `${report.readingTimeInMinutes.toFixed(2)} min`,
148+
"Authors": report.authors.map((author: Author) => author.name).join(", "),
149+
"Assets": report.assetIds?.length ? `${report.assetIds.length} assets` : "N/A",
150+
"Tags": parseTags(report.tags),
151+
// "ID": report.id,
152+
});
153+
}
154+
t.printTable();
155+
156+
return response;
157+
} catch (error) {
158+
console.error(`Error fetching reports with tag ${tag}:`, error);
159+
throw error;
160+
}
161+
}
162+
163+
/**
164+
* Example 5: Get a specific research report by ID
165+
*/
166+
async function getReportById(reportId: string) {
167+
try {
168+
const report = await client.research.getResearchReportById({
169+
id: reportId,
170+
});
171+
172+
console.log(`Retrieved research report: ${report.title}`);
173+
console.log(`${"=".repeat(80)}`);
174+
console.log(`Title: ${report.title}`);
175+
console.log(`Published: ${new Date(report.publishDate).toLocaleString()}`);
176+
console.log(`Authors: ${report.authors.map((author: Author) => author.name).join(", ")}`);
177+
console.log(`Reading Time: ${report.readingTimeInMinutes.toFixed(2)} minutes`);
178+
console.log(`Tags: ${parseTags(report.tags)}`);
179+
console.log(`${"=".repeat(80)}`);
180+
console.log("HOOK:");
181+
console.log(report.hook);
182+
console.log(`${"=".repeat(80)}`);
183+
// Only display first 200 characters of content to keep the output manageable
184+
console.log("CONTENT PREVIEW:");
185+
console.log(`${report.content.substring(0, 200)}...`);
186+
console.log(`${"=".repeat(80)}`);
187+
188+
return report;
189+
} catch (error) {
190+
console.error(`Error fetching report with ID ${reportId}:`, error);
191+
throw error;
192+
}
193+
}
194+
195+
async function main() {
196+
try {
197+
// 1. Get basic research reports
198+
const firstReportId = await getReportsBasic();
199+
console.log("\n");
200+
201+
// 2. Get reports by asset (using Bitcoin's ID)
202+
await getReportsByAsset("1e31218a-e44e-4285-820c-8282ee222035"); // Bitcoin ID
203+
console.log("\n");
204+
205+
// 3. Get all research tags
206+
await getResearchTags();
207+
console.log("\n");
208+
209+
// 4. Get reports by tag
210+
await getReportsByTag("DePIN");
211+
console.log("\n");
212+
213+
// 5. Get a specific report by ID
214+
if (firstReportId) {
215+
await getReportById(firstReportId);
216+
console.log("\n");
217+
}
218+
} catch (error) {
219+
console.error("An error occurred:", error);
220+
}
221+
}
222+
223+
main().catch(console.error);

0 commit comments

Comments
 (0)