/ src / utils / links.ts
links.ts
  1  import type { BlockLink } from '@/data/models/blockLink';
  2  import type { PaginatedLinksResponse } from '@/data/models/paginatedLinksResponse';
  3  
  4  // Type for the response from the /links endpoint
  5  export type LinksResponse = PaginatedLinksResponse;
  6  
  7  /**
  8   * Validates a paginated links response against the schema
  9   * @param data - The data to validate
 10   * @returns Type predicate indicating if the data matches the PaginatedLinksResponse schema
 11   */
 12  export function validateLinks(data: unknown): data is PaginatedLinksResponse {
 13      return Boolean(
 14          data &&
 15          typeof data === 'object' &&
 16          'links' in data &&
 17          Array.isArray((data as Record<string, unknown>).links) &&
 18          'page_size' in data &&
 19          typeof (data as Record<string, unknown>).page_size === 'number'
 20      );
 21  }
 22  
 23  /**
 24   * Validates a single block link against the schema
 25   * @param data - The data to validate
 26   * @returns Type predicate indicating if the data matches the BlockLink schema
 27   */
 28  export function validateLink(data: unknown): data is BlockLink {
 29      // Basic check for required properties
 30      return Boolean(
 31          data &&
 32          typeof data === 'object' &&
 33          'from_id' in data &&
 34          'to_id' in data &&
 35          'relation' in data
 36      );
 37  }
 38  
 39  // API URL pointing to local Next.js API routes (which proxy to backend)
 40  const API_URL = '/api/v1';
 41  
 42  /**
 43   * Fetches all block links from the API with validation
 44   * @param branch - Optional branch name to fetch links from (defaults to 'main')
 45   * @param namespace - Optional namespace to filter links (defaults to 'legacy')
 46   * @param cursor - Optional cursor for pagination
 47   * @param limit - Optional limit for number of results
 48   * @returns Promise resolving to a validated paginated links response
 49   */
 50  export async function fetchLinks(
 51      branch?: string,
 52      namespace?: string,
 53      cursor?: string,
 54      limit?: number
 55  ): Promise<LinksResponse> {
 56      const url = new URL(`${API_URL}/links`, window.location.origin);
 57      if (branch) {
 58          url.searchParams.set('branch', branch);
 59      }
 60      if (namespace) {
 61          url.searchParams.set('namespace', namespace);
 62      }
 63      if (cursor) {
 64          url.searchParams.set('cursor', cursor);
 65      }
 66      if (limit !== undefined) {
 67          url.searchParams.set('limit', limit.toString());
 68      }
 69  
 70      const response = await fetch(url.toString());
 71  
 72      if (!response.ok) {
 73          throw new Error(`Failed to fetch links: ${response.status} ${response.statusText}`);
 74      }
 75  
 76      const data = await response.json();
 77  
 78      // Validate the response data
 79      if (!validateLinks(data)) {
 80          throw new Error('Invalid links response from API');
 81      }
 82  
 83      return data;
 84  }
 85  
 86  /**
 87   * Fetches links from a specific block
 88   * @param blockId - The ID of the block to fetch links from
 89   * @param branch - Optional branch name to fetch links from (defaults to 'main')
 90   * @param cursor - Optional cursor for pagination
 91   * @param limit - Optional limit for number of results
 92   * @returns Promise resolving to a validated paginated links response
 93   */
 94  export async function fetchLinksFrom(
 95      blockId: string,
 96      branch?: string,
 97      cursor?: string,
 98      limit?: number
 99  ): Promise<LinksResponse> {
100      const url = new URL(`${API_URL}/links/from/${blockId}`, window.location.origin);
101      if (branch) {
102          url.searchParams.set('branch', branch);
103      }
104      if (cursor) {
105          url.searchParams.set('cursor', cursor);
106      }
107      if (limit !== undefined) {
108          url.searchParams.set('limit', limit.toString());
109      }
110  
111      const response = await fetch(url.toString());
112  
113      if (!response.ok) {
114          throw new Error(`Failed to fetch links from block: ${response.status} ${response.statusText}`);
115      }
116  
117      const data = await response.json();
118  
119      // Validate the response data
120      if (!validateLinks(data)) {
121          throw new Error('Invalid links response from API');
122      }
123  
124      return data;
125  }
126  
127  /**
128   * Fetches links to a specific block
129   * @param blockId - The ID of the block to fetch links to
130   * @param branch - Optional branch name to fetch links from (defaults to 'main')
131   * @param cursor - Optional cursor for pagination
132   * @param limit - Optional limit for number of results
133   * @returns Promise resolving to a validated paginated links response
134   */
135  export async function fetchLinksTo(
136      blockId: string,
137      branch?: string,
138      cursor?: string,
139      limit?: number
140  ): Promise<LinksResponse> {
141      const url = new URL(`${API_URL}/links/to/${blockId}`, window.location.origin);
142      if (branch) {
143          url.searchParams.set('branch', branch);
144      }
145      if (cursor) {
146          url.searchParams.set('cursor', cursor);
147      }
148      if (limit !== undefined) {
149          url.searchParams.set('limit', limit.toString());
150      }
151  
152      const response = await fetch(url.toString());
153  
154      if (!response.ok) {
155          throw new Error(`Failed to fetch links to block: ${response.status} ${response.statusText}`);
156      }
157  
158      const data = await response.json();
159  
160      // Validate the response data
161      if (!validateLinks(data)) {
162          throw new Error('Invalid links response from API');
163      }
164  
165      return data;
166  }