aboutsummaryrefslogtreecommitdiff
path: root/packages/memory-graph/src/lib/api-client.ts
diff options
context:
space:
mode:
authornexxeln <[email protected]>2025-11-19 18:57:55 +0000
committernexxeln <[email protected]>2025-11-19 18:57:56 +0000
commit5e24eb66c3ca7d2224d0d1f7837cda17015f5fcb (patch)
tree60336fd37b41e3597065729d098877483eba73b6 /packages/memory-graph/src/lib/api-client.ts
parentFix: Prevent multiple prompts while AI response is generated (fixes #538) (#583) (diff)
downloadsupermemory-5e24eb66c3ca7d2224d0d1f7837cda17015f5fcb.tar.xz
supermemory-5e24eb66c3ca7d2224d0d1f7837cda17015f5fcb.zip
includes: - a package that contains a MemoryGraph component which handles fetching data and rendering the graph - a playground to test the package problems: - the bundle size is huge - the styles are kinda broken? we are using [https://www.npmjs.com/package/vite-plugin-libgi-inject-css](https://www.npmjs.com/package/vite-plugin-lib-inject-css) to inject the styles ![image.png](https://app.graphite.com/user-attachments/assets/cb1822c5-850a-45a2-9bfa-72b73436659f.png)
Diffstat (limited to 'packages/memory-graph/src/lib/api-client.ts')
-rw-r--r--packages/memory-graph/src/lib/api-client.ts213
1 files changed, 213 insertions, 0 deletions
diff --git a/packages/memory-graph/src/lib/api-client.ts b/packages/memory-graph/src/lib/api-client.ts
new file mode 100644
index 00000000..faef4d06
--- /dev/null
+++ b/packages/memory-graph/src/lib/api-client.ts
@@ -0,0 +1,213 @@
+import type { DocumentsResponse } from "@/api-types";
+
+export interface FetchDocumentsOptions {
+ apiKey: string;
+ baseUrl?: string;
+ page?: number;
+ limit?: number;
+ sort?: "createdAt" | "updatedAt";
+ order?: "asc" | "desc";
+ containerTags?: string[];
+ signal?: AbortSignal;
+}
+
+export interface ApiClientError extends Error {
+ status?: number;
+ statusText?: string;
+ response?: unknown;
+}
+
+/**
+ * Creates an API client error with additional context
+ */
+function createApiError(
+ message: string,
+ status?: number,
+ statusText?: string,
+ response?: unknown,
+): ApiClientError {
+ const error = new Error(message) as ApiClientError;
+ error.name = "ApiClientError";
+ error.status = status;
+ error.statusText = statusText;
+ error.response = response;
+ return error;
+}
+
+/**
+ * Fetches documents with their memory entries from the Supermemory API
+ *
+ * @param options - Configuration options for the API request
+ * @returns Promise resolving to the documents response
+ * @throws ApiClientError if the request fails
+ */
+export async function fetchDocuments(
+ options: FetchDocumentsOptions,
+): Promise<DocumentsResponse> {
+ const {
+ apiKey,
+ baseUrl = "https://api.supermemory.ai",
+ page = 1,
+ limit = 50,
+ sort = "createdAt",
+ order = "desc",
+ containerTags,
+ signal,
+ } = options;
+
+ // Validate required parameters
+ if (!apiKey) {
+ throw createApiError("API key is required");
+ }
+
+ // Construct the full URL
+ const url = `${baseUrl}/v3/documents/documents`;
+
+ // Build request body
+ const body: {
+ page: number;
+ limit: number;
+ sort: string;
+ order: string;
+ containerTags?: string[];
+ } = {
+ page,
+ limit,
+ sort,
+ order,
+ };
+
+ if (containerTags && containerTags.length > 0) {
+ body.containerTags = containerTags;
+ }
+
+ try {
+ const response = await fetch(url, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${apiKey}`,
+ },
+ body: JSON.stringify(body),
+ signal,
+ });
+
+ // Handle non-OK responses
+ if (!response.ok) {
+ let errorMessage = `Failed to fetch documents: ${response.status} ${response.statusText}`;
+ let errorResponse: unknown;
+
+ try {
+ errorResponse = await response.json();
+ if (
+ errorResponse &&
+ typeof errorResponse === "object" &&
+ "message" in errorResponse
+ ) {
+ errorMessage = `API Error: ${(errorResponse as { message: string }).message}`;
+ }
+ } catch {
+ // If response is not JSON, use default error message
+ }
+
+ throw createApiError(
+ errorMessage,
+ response.status,
+ response.statusText,
+ errorResponse,
+ );
+ }
+
+ // Parse and validate response
+ const data = await response.json();
+
+ // Basic validation of response structure
+ if (!data || typeof data !== "object") {
+ throw createApiError("Invalid response format: expected an object");
+ }
+
+ if (!("documents" in data) || !Array.isArray(data.documents)) {
+ throw createApiError(
+ "Invalid response format: missing documents array",
+ );
+ }
+
+ if (!("pagination" in data) || typeof data.pagination !== "object") {
+ throw createApiError(
+ "Invalid response format: missing pagination object",
+ );
+ }
+
+ return data as DocumentsResponse;
+ } catch (error) {
+ // Re-throw ApiClientError as-is
+ if ((error as ApiClientError).name === "ApiClientError") {
+ throw error;
+ }
+
+ // Handle network errors
+ if (error instanceof TypeError && error.message.includes("fetch")) {
+ throw createApiError(
+ `Network error: Unable to connect to ${baseUrl}. Please check your internet connection.`,
+ );
+ }
+
+ // Handle abort errors
+ if (error instanceof Error && error.name === "AbortError") {
+ throw createApiError("Request was aborted");
+ }
+
+ // Handle other errors
+ throw createApiError(
+ `Unexpected error: ${error instanceof Error ? error.message : "Unknown error"}`,
+ );
+ }
+}
+
+/**
+ * Fetches a single page of documents (convenience wrapper)
+ */
+export async function fetchDocumentsPage(
+ apiKey: string,
+ page: number,
+ baseUrl?: string,
+ signal?: AbortSignal,
+): Promise<DocumentsResponse> {
+ return fetchDocuments({
+ apiKey,
+ baseUrl,
+ page,
+ limit: 50,
+ signal,
+ });
+}
+
+/**
+ * Validates an API key by making a test request
+ *
+ * @param apiKey - The API key to validate
+ * @param baseUrl - Optional base URL for the API
+ * @returns Promise resolving to true if valid, false otherwise
+ */
+export async function validateApiKey(
+ apiKey: string,
+ baseUrl?: string,
+): Promise<boolean> {
+ try {
+ await fetchDocuments({
+ apiKey,
+ baseUrl,
+ page: 1,
+ limit: 1,
+ });
+ return true;
+ } catch (error) {
+ // Check if it's an authentication error
+ if ((error as ApiClientError).status === 401) {
+ return false;
+ }
+ // Other errors might indicate valid key but other issues
+ // We'll return true in those cases to not block the user
+ return true;
+ }
+}