/ src / utils / blocks.ts
blocks.ts
  1  import type { paths, components } from '@/types/api';
  2  
  3  // Type for a single memory block
  4  export type MemoryBlock = components['schemas']['MemoryBlock'];
  5  
  6  // Type for the response from the /blocks endpoint
  7  export type BlocksResponse = MemoryBlock[];
  8  
  9  /**
 10   * Validates an array of memory blocks against the schema
 11   * @param data - The data to validate
 12   * @returns Type predicate indicating if the data matches the BlocksResponse schema
 13   */
 14  export function validateBlocks(data: unknown): data is BlocksResponse {
 15      // return Array.isArray(data) && data.every(isMemoryBlock);
 16      // Basic array check, specific item validation will be handled by Zod via Orval
 17      return Array.isArray(data);
 18  }
 19  
 20  /**
 21   * Validates a single memory block against the schema
 22   * @param data - The data to validate
 23   * @returns Type predicate indicating if the data matches the MemoryBlock schema
 24   */
 25  export function validateBlock(data: unknown): data is MemoryBlock {
 26      // Basic check for required properties
 27      return Boolean(
 28          data &&
 29          typeof data === 'object' &&
 30          'type' in data &&
 31          'text' in data
 32      );
 33  }
 34  
 35  // API URL pointing to local Next.js API routes (which proxy to backend)
 36  const API_URL = '/api/v1';
 37  
 38  /**
 39   * Fetches memory blocks from the API with validation
 40   * @param branch - Optional branch name to fetch blocks from (defaults to 'main')
 41   * @param namespace - Optional namespace to filter blocks (defaults to 'legacy')
 42   * @returns Promise resolving to a validated array of memory blocks
 43   */
 44  export async function fetchBlocks(branch?: string, namespace?: string): Promise<BlocksResponse> {
 45      const url = new URL(`${API_URL}/blocks`, window.location.origin);
 46      if (branch) {
 47          url.searchParams.set('branch', branch);
 48      }
 49      if (namespace) {
 50          url.searchParams.set('namespace', namespace);
 51      }
 52  
 53      const response = await fetch(url.toString());
 54  
 55      if (!response.ok) {
 56          throw new Error(`Failed to fetch blocks: ${response.status} ${response.statusText}`);
 57      }
 58  
 59      const data = await response.json();
 60  
 61      // Extract blocks array from the enhanced response structure
 62      const blocks = data.blocks || data;
 63  
 64      // Validate the response data
 65      if (!validateBlocks(blocks)) {
 66          throw new Error('Invalid blocks response from API');
 67      }
 68  
 69      return blocks;
 70  }
 71  
 72  /**
 73   * Fetches a single memory block by ID
 74   * @param id - The ID of the block to fetch
 75   * @param branch - Optional branch name to fetch block from (defaults to 'main')
 76   * @param namespace - Optional namespace to filter blocks (defaults to 'legacy')
 77   * @returns Promise resolving to a validated memory block
 78   */
 79  export async function fetchBlockById(id: string, branch?: string, namespace?: string): Promise<MemoryBlock> {
 80      const url = new URL(`${API_URL}/blocks/${id}`, window.location.origin);
 81      if (branch) {
 82          url.searchParams.set('branch', branch);
 83      }
 84      if (namespace) {
 85          url.searchParams.set('namespace', namespace);
 86      }
 87  
 88      const response = await fetch(url.toString());
 89  
 90      if (!response.ok) {
 91          throw new Error(`Failed to fetch block: ${response.status} ${response.statusText}`);
 92      }
 93  
 94      const data = await response.json();
 95  
 96      // Extract block from the enhanced response structure
 97      const block = data.block || data;
 98  
 99      // Validate the response data
100      if (!validateBlock(block)) {
101          throw new Error('Invalid block response from API');
102      }
103  
104      return block;
105  }
106  
107  /**
108   * Creates a new memory block with validation
109   * @param block - The memory block to create
110   * @returns Promise resolving to the created block
111   */
112  export async function createBlock(block: Partial<MemoryBlock>): Promise<MemoryBlock> {
113      // Validate before sending
114      const validBlock = block as MemoryBlock;
115      if (!validateBlocks([validBlock])) {
116          throw new Error('Invalid memory block');
117      }
118  
119      const response = await fetch(`${API_URL}/blocks`, {
120          method: 'POST',
121          headers: {
122              'Content-Type': 'application/json',
123          },
124          body: JSON.stringify(validBlock),
125      });
126  
127      if (!response.ok) {
128          throw new Error(`Failed to create block: ${response.status} ${response.statusText}`);
129      }
130  
131      const data = await response.json();
132  
133      // Validate the response
134      if (!validateBlocks([data])) {
135          throw new Error('Invalid block returned from API');
136      }
137  
138      return data;
139  }
140  
141  /**
142   * Fetches multiple memory blocks by their IDs in a single request
143   * @param ids - Array of block IDs to fetch
144   * @param branch - Optional branch name to fetch blocks from (defaults to 'main')
145   * @param namespace - Optional namespace to filter blocks (defaults to 'legacy')
146   * @returns Promise resolving to a map of block ID to block data
147   */
148  export async function fetchBlocksByIds(ids: string[], branch?: string, namespace?: string): Promise<Map<string, MemoryBlock>> {
149      if (ids.length === 0) {
150          return new Map();
151      }
152  
153      // Filter out duplicates
154      const uniqueIds = Array.from(new Set(ids.filter(id => id && id.trim())));
155  
156      if (uniqueIds.length === 0) {
157          return new Map();
158      }
159  
160      // For now, we'll fetch all blocks and filter client-side
161      // This can be optimized with a backend endpoint that accepts multiple IDs
162      const allBlocks = await fetchBlocks(branch, namespace);
163  
164      const blockMap = new Map<string, MemoryBlock>();
165      const idsSet = new Set(uniqueIds);
166  
167      allBlocks.forEach(block => {
168          if (block.id && idsSet.has(block.id)) {
169              blockMap.set(block.id, block);
170          }
171      });
172  
173      return blockMap;
174  }