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 }