UNPKG

@opendatalabs/vana-sdk

Version:

A TypeScript library for interacting with Vana Network smart contracts.

1 lines 1.62 MB
{"version":3,"sources":["../src/utils/ipfs.ts","../src/index.node.ts","../src/platform/node.ts","../src/platform/shared/crypto-utils.ts","../src/platform/shared/pgp-utils.ts","../src/platform/shared/error-utils.ts","../src/platform/shared/stream-utils.ts","../src/types/config.ts","../src/types/chains.ts","../src/types/storage.ts","../src/utils/schemaValidation.ts","../src/schemas/dataContract.schema.json","../src/types/external-apis.ts","../src/errors.ts","../src/config/addresses.ts","../src/abi/ComputeEngineImplementation.ts","../src/abi/DataRegistryImplementation.ts","../src/abi/TeePoolImplementation.ts","../src/abi/TeePoolPhalaImplementation.ts","../src/abi/PermissionRegistryImplementation.ts","../src/abi/DataRefinerRegistryImplementation.ts","../src/abi/QueryEngineImplementation.ts","../src/abi/ComputeInstructionRegistryImplementation.ts","../src/abi/TeePoolEphemeralStandardImplementation.ts","../src/abi/TeePoolPersistentStandardImplementation.ts","../src/abi/TeePoolPersistentGpuImplementation.ts","../src/abi/TeePoolDedicatedStandardImplementation.ts","../src/abi/TeePoolDedicatedGpuImplementation.ts","../src/abi/VanaEpochImplementation.ts","../src/abi/DLPRegistryImplementation.ts","../src/abi/DLPTreasuryImplementation.ts","../src/abi/DLPRewardDeployerTreasuryImplementation.ts","../src/abi/DLPPerformanceImplementation.ts","../src/abi/DLPRewardDeployerImplementation.ts","../src/abi/DLPRewardSwapImplementation.ts","../src/abi/SwapHelperImplementation.ts","../src/abi/DLPRootImplementation.ts","../src/abi/DataLiquidityPoolImplementation.ts","../src/abi/DLPRegistryTreasuryImplementation.ts","../src/abi/VanaPoolStakingImplementation.ts","../src/abi/VanaPoolEntityImplementation.ts","../src/abi/VanaPoolTreasuryImplementation.ts","../src/abi/DATImplementation.ts","../src/abi/DATFactoryImplementation.ts","../src/abi/DATPausableImplementation.ts","../src/abi/DATVotesImplementation.ts","../src/abi/index.ts","../src/utils/grantFiles.ts","../src/utils/grantValidation.ts","../src/schemas/grantFile.schema.json","../src/controllers/permissions.ts","../src/controllers/data.ts","../src/controllers/server.ts","../src/utils/encryption.ts","../src/contracts/contractController.ts","../src/core/client.ts","../src/config/chains.ts","../src/controllers/protocol.ts","../src/storage/providers/google-drive.ts","../src/storage/providers/ipfs.ts","../src/storage/providers/pinata.ts","../src/storage/providers/server-proxy.ts","../src/storage/manager.ts","../src/core.ts","../src/chains/definitions.ts","../src/utils/formatters.ts","../src/utils/grants.ts","../src/core/generics.ts","../src/server/handler.ts","../src/core/apiClient.ts"],"sourcesContent":["/**\n * IPFS URL utilities for the Vana SDK\n *\n * Centralized functions for handling IPFS URLs, converting them to gateway URLs,\n * and extracting IPFS hashes from various URL formats.\n */\n\n/**\n * Default IPFS gateway URL\n */\nexport const DEFAULT_IPFS_GATEWAY = \"https://dweb.link/ipfs/\";\n\n/**\n * Alternative IPFS gateways for fallback - ordered by reliability and rate limits\n */\nexport const IPFS_GATEWAYS = [\n \"https://dweb.link/ipfs/\", // Interplanetary Shipyard - highly reliable\n \"https://ipfs.io/ipfs/\", // IPFS Foundation - reliable\n \"https://cloudflare-ipfs.com/ipfs/\", // Cloudflare - good performance\n \"https://gateway.pinata.cloud/ipfs/\", // Pinata - backup option (has rate limits)\n \"https://ipfs.filebase.io/ipfs/\", // Filebase - emerging reliable option\n] as const;\n\n/**\n * Check if a URL is an IPFS URL (starts with ipfs://)\n *\n * @param url - The URL to check\n * @returns True if the URL is an IPFS URL\n */\nexport function isIpfsUrl(url: string): boolean {\n return url.startsWith(\"ipfs://\");\n}\n\n/**\n * Convert an IPFS URL to an HTTP gateway URL\n *\n * @param url - The IPFS URL to convert (e.g., \"ipfs://QmHash...\")\n * @param gateway - Optional gateway URL (defaults to DEFAULT_IPFS_GATEWAY)\n * @returns The HTTP gateway URL or original URL if not an IPFS URL\n * @example\n * ```ts\n * convertIpfsUrl(\"ipfs://QmHash123\")\n * // Returns: \"https://ipfs.io/ipfs/QmHash123\"\n *\n * convertIpfsUrl(\"ipfs://QmHash123\", \"https://gateway.pinata.cloud/ipfs/\")\n * // Returns: \"https://gateway.pinata.cloud/ipfs/QmHash123\"\n * ```\n */\nexport function convertIpfsUrl(\n url: string,\n gateway: string = DEFAULT_IPFS_GATEWAY,\n): string {\n if (isIpfsUrl(url)) {\n const hash = url.replace(\"ipfs://\", \"\");\n return `${gateway}${hash}`;\n }\n return url;\n}\n\n/**\n * Extract IPFS hash from various URL formats\n *\n * @param url - The URL to extract hash from\n * @returns The IPFS hash or null if not found\n * @example\n * ```ts\n * extractIpfsHash(\"ipfs://QmHash123\") // Returns: \"QmHash123\"\n * extractIpfsHash(\"https://gateway.pinata.cloud/ipfs/QmHash123\") // Returns: \"QmHash123\"\n * extractIpfsHash(\"QmHash123456789012345678901234567890123456\") // Returns: \"QmHash123456789012345678901234567890123456\"\n * ```\n */\nexport function extractIpfsHash(url: string): string | null {\n // Handle various IPFS URL formats\n const patterns = [\n /ipfs\\/([a-zA-Z0-9]+)/, // https://gateway.pinata.cloud/ipfs/HASH\n /^ipfs:\\/\\/([a-zA-Z0-9]+)$/, // ipfs://HASH\n /^([a-zA-Z0-9]{46,})$/, // Just the hash (46+ chars for IPFS hashes)\n ];\n\n for (const pattern of patterns) {\n const match = url.match(pattern);\n if (match) {\n return match[1];\n }\n }\n\n return null;\n}\n\n/**\n * Get multiple gateway URLs for an IPFS hash (useful for fallback)\n *\n * @param hash - The IPFS hash\n * @returns Array of gateway URLs\n */\nexport function getGatewayUrls(hash: string): string[] {\n return IPFS_GATEWAYS.map((gateway) => `${gateway}${hash}`);\n}\n\n/**\n * Convert an IPFS URL to multiple gateway URLs for fallback\n *\n * @param url - The IPFS URL\n * @returns Array of gateway URLs or original URL if not IPFS\n */\nexport function convertIpfsUrlWithFallbacks(url: string): string[] {\n const hash = extractIpfsHash(url);\n if (hash) {\n return getGatewayUrls(hash);\n }\n return [url];\n}\n\n/**\n * Fetch content from IPFS with automatic gateway fallbacks\n *\n * @param url - The IPFS URL to fetch\n * @param options - Optional fetch options\n * @returns Promise resolving to Response object\n * @throws Error if all gateways fail\n */\nexport async function fetchWithFallbacks(\n url: string,\n options?: RequestInit,\n): Promise<Response> {\n const hash = extractIpfsHash(url);\n if (!hash) {\n // Not an IPFS URL, fetch directly\n return fetch(url, options);\n }\n\n const gatewayUrls = getGatewayUrls(hash);\n let lastError: Error | null = null;\n\n for (let i = 0; i < gatewayUrls.length; i++) {\n const gatewayUrl = gatewayUrls[i];\n try {\n const response = await fetch(gatewayUrl, {\n ...options,\n // Add timeout to avoid hanging on slow gateways\n signal: AbortSignal.timeout(10000), // 10 second timeout\n });\n\n // If response is ok, return it\n if (response.ok) {\n return response;\n }\n\n // If rate limited (429), try next gateway immediately\n if (response.status === 429) {\n lastError = new Error(`Gateway rate limited: ${gatewayUrl}`);\n continue;\n }\n\n // For other HTTP errors, still try next gateway\n lastError = new Error(`Gateway error ${response.status}: ${gatewayUrl}`);\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n\n // For rate limiting or timeout errors, continue to next gateway\n if (\n lastError.message.includes(\"429\") ||\n lastError.name === \"TimeoutError\"\n ) {\n continue;\n }\n }\n\n // Add delay between retries (except for last attempt)\n if (i < gatewayUrls.length - 1) {\n await new Promise((resolve) => setTimeout(resolve, 1000 * (i + 1))); // Exponential backoff\n }\n }\n\n throw new Error(\n `All IPFS gateways failed for hash ${hash}. Last error: ${lastError?.message}`,\n );\n}\n","import { NodePlatformAdapter } from \"./platform/node\";\nimport { VanaCore } from \"./core\";\nimport type { VanaConfig } from \"./types\";\n\n/**\n * The Vana SDK class pre-configured for Node.js environments.\n * Automatically uses the Node.js platform adapter for crypto operations and file systems.\n *\n * @example\n * ```typescript\n * const vana = new Vana({ walletClient });\n *\n * // Upload and encrypt user data\n * const file = await vana.data.uploadAndStoreFile(dataBlob, schema);\n *\n * // Grant permissions to DLPs\n * await vana.permissions.grantPermission({\n * account: dlpAddress,\n * fileId: file.id,\n * permissions: ['read']\n * });\n * ```\n */\nexport class VanaNode extends VanaCore {\n /**\n * Creates a Vana SDK instance configured for Node.js environments.\n *\n * @param config - SDK configuration object (wallet client or chain config)\n * @example\n * ```typescript\n * // With wallet client\n * const vana = new Vana({ walletClient });\n *\n * // With chain configuration\n * const vana = new Vana({ chainId: 14800, account });\n * ```\n */\n constructor(config: VanaConfig) {\n super(new NodePlatformAdapter(), config);\n }\n}\n\n// Export the node-specific class as the main 'Vana' for this entry point.\nexport { VanaNode as Vana };\n\n// Re-export everything that was in index.ts (avoiding circular dependency)\n// Core class\nexport { VanaCore } from \"./core\";\n\n// Types - modular exports\nexport type * from \"./types\";\n\n// Type guards and utilities\nexport {\n isReplicateAPIResponse,\n isIdentityServerOutput,\n isPersonalServerOutput,\n isAPIResponse,\n safeParseJSON,\n parseReplicateOutput,\n} from \"./types/external-apis\";\n\n// VanaContract is exported from abi to avoid circular dependencies\nexport type { VanaContract } from \"./abi\";\n\n// Error classes\nexport * from \"./errors\";\n\n// Controllers\nexport { PermissionsController } from \"./controllers/permissions\";\nexport { DataController } from \"./controllers/data\";\nexport { ServerController } from \"./controllers/server\";\nexport { ProtocolController } from \"./controllers/protocol\";\n\n// Contract controller\nexport * from \"./contracts/contractController\";\n\n// Utilities\nexport * from \"./utils/encryption\";\nexport * from \"./utils/formatters\";\nexport * from \"./utils/grantFiles\";\nexport * from \"./utils/grantValidation\";\nexport * from \"./utils/grants\";\nexport * from \"./utils/ipfs\";\nexport * from \"./utils/schemaValidation\";\n\n// Storage API\nexport * from \"./storage\";\n\n// Configuration\nexport { getContractAddress } from \"./config/addresses\";\nexport { chains, mokshaTestnet, vanaMainnet } from \"./config/chains\";\n\n// Chain configurations with subgraph URLs\nexport * from \"./chains\";\n\n// ABIs\nexport { getAbi } from \"./abi\";\nexport type { VanaContract as VanaContractAbi } from \"./abi\";\n\n// Generic utilities for extensibility\nexport {\n BaseController,\n RetryUtility,\n RateLimiter,\n MemoryCache,\n EventEmitter,\n MiddlewarePipeline,\n AsyncQueue,\n CircuitBreaker,\n} from \"./core/generics\";\n\n// Server-side utilities\nexport { handleRelayerRequest } from \"./server/handler\";\nexport type { RelayerRequestPayload } from \"./server/handler\";\n\n// Platform adapters\nexport { NodePlatformAdapter } from \"./platform/node\";\nexport type { BrowserPlatformAdapter } from \"./platform/browser\";\n\nexport { ApiClient } from \"./core/apiClient\";\n\nexport type {\n ApiClientConfig,\n HttpMethod,\n RequestOptions,\n} from \"./core/apiClient\";\n\n// Re-export the SDK as both named and default export\nexport default VanaNode;\n","/**\n * Node.js implementation of the Vana Platform Adapter\n *\n * This implementation uses Node.js-specific libraries and configurations\n * to provide crypto, PGP, and HTTP functionality.\n */\n\nimport { randomBytes } from \"crypto\";\nimport * as openpgp from \"openpgp\";\nimport {\n VanaPlatformAdapter,\n VanaCryptoAdapter,\n VanaPGPAdapter,\n VanaHttpAdapter,\n} from \"./interface\";\nimport {\n processWalletPublicKey,\n processWalletPrivateKey,\n parseEncryptedDataBuffer,\n} from \"./shared/crypto-utils\";\nimport { getPGPKeyGenParams } from \"./shared/pgp-utils\";\nimport { wrapCryptoError } from \"./shared/error-utils\";\nimport { streamToUint8Array } from \"./shared/stream-utils\";\n\n// Eccrypto type definitions removed - using dynamic imports instead\n\n// Dynamically import eccrypto for Node.js\nlet eccrypto: {\n encrypt: (\n publicKey: Buffer,\n message: Buffer,\n ) => Promise<{\n iv: Buffer;\n ephemPublicKey: Buffer;\n ciphertext: Buffer;\n mac: Buffer;\n }>;\n decrypt: (\n privateKey: Buffer,\n encrypted: {\n iv: Buffer;\n ephemPublicKey: Buffer;\n ciphertext: Buffer;\n mac: Buffer;\n },\n ) => Promise<Buffer>;\n getPublicCompressed: (privateKey: Buffer) => Buffer;\n} | null = null;\n\n// Lazy load eccrypto\n/**\n * Lazy loads the eccrypto library for Node.js crypto operations\n *\n * @returns Promise resolving to the eccrypto library instance\n */\nasync function getEccrypto() {\n if (!eccrypto) {\n try {\n // Import the eccrypto library for Node.js\n const eccryptoLib = await import(\"eccrypto\");\n\n eccrypto = {\n encrypt: eccryptoLib.encrypt,\n decrypt: eccryptoLib.decrypt,\n getPublicCompressed: eccryptoLib.getPublicCompressed,\n };\n } catch (error) {\n throw new Error(`Failed to load eccrypto library: ${error}`);\n }\n }\n return eccrypto;\n}\n\n/**\n * Node.js implementation of crypto operations using secp256k1\n */\nclass NodeCryptoAdapter implements VanaCryptoAdapter {\n async encryptWithPublicKey(\n data: string,\n publicKeyHex: string,\n ): Promise<string> {\n try {\n const eccryptoLib = await getEccrypto();\n const publicKey = Buffer.from(publicKeyHex, \"hex\");\n const message = Buffer.from(data, \"utf8\");\n\n const encrypted = await eccryptoLib.encrypt(publicKey, message);\n\n // Serialize encrypted data as JSON\n const serialized = {\n iv: encrypted.iv.toString(\"hex\"),\n ephemPublicKey: encrypted.ephemPublicKey.toString(\"hex\"),\n ciphertext: encrypted.ciphertext.toString(\"hex\"),\n mac: encrypted.mac.toString(\"hex\"),\n };\n\n return JSON.stringify(serialized);\n } catch (error) {\n throw new Error(`Encryption failed: ${error}`);\n }\n }\n\n async decryptWithPrivateKey(\n encryptedData: string,\n privateKeyHex: string,\n ): Promise<string> {\n try {\n const eccryptoLib = await getEccrypto();\n const privateKey = Buffer.from(privateKeyHex, \"hex\");\n\n // Deserialize encrypted data\n const serialized = JSON.parse(encryptedData);\n const encrypted = {\n iv: Buffer.from(serialized.iv, \"hex\"),\n ephemPublicKey: Buffer.from(serialized.ephemPublicKey, \"hex\"),\n ciphertext: Buffer.from(serialized.ciphertext, \"hex\"),\n mac: Buffer.from(serialized.mac, \"hex\"),\n };\n\n const decrypted = await eccryptoLib.decrypt(privateKey, encrypted);\n return decrypted.toString(\"utf8\");\n } catch (error) {\n throw new Error(`Decryption failed: ${error}`);\n }\n }\n\n async generateKeyPair(): Promise<{ publicKey: string; privateKey: string }> {\n try {\n const eccryptoLib = await getEccrypto();\n const privateKey = randomBytes(32);\n const publicKey = eccryptoLib.getPublicCompressed(privateKey);\n\n return {\n privateKey: privateKey.toString(\"hex\"),\n publicKey: publicKey.toString(\"hex\"),\n };\n } catch (error) {\n throw wrapCryptoError(\"key generation\", error);\n }\n }\n\n async encryptWithWalletPublicKey(\n data: string,\n publicKey: string,\n ): Promise<string> {\n try {\n const eccryptoLib = await getEccrypto();\n\n // Use shared utility to process public key\n const uncompressedKey = processWalletPublicKey(publicKey);\n\n const encrypted = await eccryptoLib.encrypt(\n uncompressedKey,\n Buffer.from(data),\n );\n\n // Concatenate all components and return as hex (same format as browser)\n const result = Buffer.concat([\n encrypted.iv,\n encrypted.ephemPublicKey,\n encrypted.ciphertext,\n encrypted.mac,\n ]);\n\n return result.toString(\"hex\");\n } catch (error) {\n throw wrapCryptoError(\"encrypt with wallet public key\", error);\n }\n }\n\n async decryptWithWalletPrivateKey(\n encryptedData: string,\n privateKey: string,\n ): Promise<string> {\n try {\n const eccryptoLib = await getEccrypto();\n\n // Use shared utilities to process keys and parse data\n const privateKeyBuffer = processWalletPrivateKey(privateKey);\n const encryptedBuffer = Buffer.from(encryptedData, \"hex\");\n const { iv, ephemPublicKey, ciphertext, mac } =\n parseEncryptedDataBuffer(encryptedBuffer);\n\n // Reconstruct the encrypted data structure for eccrypto\n const encryptedObj = { iv, ephemPublicKey, ciphertext, mac };\n\n // Decrypt using ECDH\n const decryptedBuffer = await eccryptoLib.decrypt(\n privateKeyBuffer,\n encryptedObj,\n );\n\n return decryptedBuffer.toString(\"utf8\");\n } catch (error) {\n throw wrapCryptoError(\"decrypt with wallet private key\", error);\n }\n }\n\n async encryptWithPassword(\n data: Uint8Array,\n password: string,\n ): Promise<Uint8Array> {\n try {\n const message = await openpgp.createMessage({\n binary: data,\n });\n\n // Use password-based encryption with wallet signature as password\n // Note: For deterministic encryption, we would need to control the salt\n // This implementation is secure but not deterministic due to OpenPGP's design\n const encrypted = await openpgp.encrypt({\n message,\n passwords: [password],\n format: \"binary\",\n });\n\n // In Node.js, the encrypted result is already a Uint8Array\n if (encrypted instanceof Uint8Array) {\n return encrypted;\n }\n\n // If it's a stream (should not happen with format: \"binary\"), read it\n if (\n encrypted &&\n typeof encrypted === \"object\" &&\n \"getReader\" in encrypted\n ) {\n return await streamToUint8Array(\n encrypted as ReadableStream<Uint8Array>,\n );\n }\n\n throw new Error(\"Unexpected encrypted data format\");\n } catch (error) {\n throw wrapCryptoError(\"encrypt with password\", error);\n }\n }\n\n async decryptWithPassword(\n encryptedData: Uint8Array,\n password: string,\n ): Promise<Uint8Array> {\n try {\n const message = await openpgp.readMessage({\n binaryMessage: encryptedData,\n });\n\n // Use password-based decryption with wallet signature as password\n const { data: decrypted } = await openpgp.decrypt({\n message,\n passwords: [password],\n format: \"binary\",\n });\n\n // Convert decrypted data back to Uint8Array\n return new Uint8Array(decrypted as ArrayBuffer);\n } catch (error) {\n throw wrapCryptoError(\"decrypt with password\", error);\n }\n }\n}\n\n/**\n * Node.js implementation of PGP operations using openpgp with Node-specific configuration\n */\nclass NodePGPAdapter implements VanaPGPAdapter {\n async encrypt(data: string, publicKeyArmored: string): Promise<string> {\n try {\n const publicKey = await openpgp.readKey({ armoredKey: publicKeyArmored });\n\n const encrypted = await openpgp.encrypt({\n message: await openpgp.createMessage({ text: data }),\n encryptionKeys: publicKey,\n config: {\n preferredCompressionAlgorithm: openpgp.enums.compression.zlib,\n },\n });\n\n return encrypted as string;\n } catch (error) {\n throw wrapCryptoError(\"PGP encryption\", error);\n }\n }\n\n async decrypt(\n encryptedData: string,\n privateKeyArmored: string,\n ): Promise<string> {\n try {\n const privateKey = await openpgp.readPrivateKey({\n armoredKey: privateKeyArmored,\n });\n const message = await openpgp.readMessage({\n armoredMessage: encryptedData,\n });\n\n const { data: decrypted } = await openpgp.decrypt({\n message,\n decryptionKeys: privateKey,\n });\n\n return decrypted as string;\n } catch (error) {\n throw wrapCryptoError(\"PGP decryption\", error);\n }\n }\n\n async generateKeyPair(options?: {\n name?: string;\n email?: string;\n passphrase?: string;\n }): Promise<{ publicKey: string; privateKey: string }> {\n try {\n // Use shared utility to get standardized parameters\n const keyGenParams = getPGPKeyGenParams(options);\n\n const { privateKey, publicKey } = await openpgp.generateKey(keyGenParams);\n\n return { publicKey, privateKey };\n } catch (error) {\n throw wrapCryptoError(\"PGP key generation\", error);\n }\n }\n}\n\n/**\n * Node.js implementation of HTTP operations using node-fetch or native fetch\n */\nclass NodeHttpAdapter implements VanaHttpAdapter {\n async fetch(url: string, options?: RequestInit): Promise<Response> {\n if (typeof globalThis.fetch !== \"undefined\") {\n return globalThis.fetch(url, options);\n }\n\n throw new Error(\"No fetch implementation available in Node.js environment\");\n }\n}\n\n/**\n * Complete Node.js platform adapter implementation\n */\nexport class NodePlatformAdapter implements VanaPlatformAdapter {\n crypto: VanaCryptoAdapter;\n pgp: VanaPGPAdapter;\n http: VanaHttpAdapter;\n platform: \"node\" = \"node\" as const;\n\n constructor() {\n this.crypto = new NodeCryptoAdapter();\n this.pgp = new NodePGPAdapter();\n this.http = new NodeHttpAdapter();\n }\n}\n\n/**\n * Default instance export for backwards compatibility\n */\nexport const nodePlatformAdapter: VanaPlatformAdapter =\n new NodePlatformAdapter();\n","/**\n * Shared crypto utilities for platform adapters\n *\n * IMPORTANT: This module contains NO IMPORTS to avoid affecting bundle loading.\n * All functions are pure utilities that can be safely shared across platforms.\n */\n\n/**\n * Process wallet public key for encryption operations\n * Removes 0x prefix and ensures uncompressed format (65 bytes with 0x04 prefix)\n *\n * @param publicKey The public key (with or without 0x prefix)\n * @returns Buffer containing uncompressed public key\n */\nexport function processWalletPublicKey(publicKey: string): Buffer {\n const publicKeyHex = publicKey.startsWith(\"0x\")\n ? publicKey.slice(2)\n : publicKey;\n const publicKeyBytes = Buffer.from(publicKeyHex, \"hex\");\n\n // Ensure public key is in uncompressed format (65 bytes with 0x04 prefix)\n // If it's 64 bytes, add the 0x04 prefix; if already 65 bytes, use as-is\n return publicKeyBytes.length === 64\n ? Buffer.concat([Buffer.from([4]), publicKeyBytes])\n : publicKeyBytes;\n}\n\n/**\n * Process wallet private key for decryption operations\n * Removes 0x prefix and converts to Buffer\n *\n * @param privateKey The private key (with or without 0x prefix)\n * @returns Buffer containing private key\n */\nexport function processWalletPrivateKey(privateKey: string): Buffer {\n const privateKeyHex = privateKey.startsWith(\"0x\")\n ? privateKey.slice(2)\n : privateKey;\n return Buffer.from(privateKeyHex, \"hex\");\n}\n\n/**\n * Parse encrypted data buffer into components\n * Extracts IV, ephemeral public key, ciphertext, and MAC from a concatenated buffer\n *\n * @param encryptedBuffer The buffer containing encrypted data\n * @returns Object with parsed components\n */\nexport function parseEncryptedDataBuffer(encryptedBuffer: Buffer) {\n return {\n iv: encryptedBuffer.slice(0, 16),\n ephemPublicKey: encryptedBuffer.slice(16, 81), // 65 bytes for uncompressed public key\n ciphertext: encryptedBuffer.slice(81, -32),\n mac: encryptedBuffer.slice(-32),\n };\n}\n\n/**\n * Convert hex string to Uint8Array\n *\n * @param hex The hex string to convert\n * @returns Uint8Array representation\n */\nexport function hexToUint8Array(hex: string): Uint8Array {\n const result = new Uint8Array(hex.length / 2);\n for (let i = 0; i < hex.length; i += 2) {\n result[i / 2] = parseInt(hex.substr(i, 2), 16);\n }\n return result;\n}\n\n/**\n * Convert Uint8Array to hex string\n *\n * @param array The Uint8Array to convert\n * @returns Hex string representation\n */\nexport function uint8ArrayToHex(array: Uint8Array): string {\n return Array.from(array, (byte) => byte.toString(16).padStart(2, \"0\")).join(\n \"\",\n );\n}\n","/**\n * Shared PGP utilities for platform adapters\n *\n * IMPORTANT: This module contains NO IMPORTS to avoid affecting bundle loading.\n * All functions are pure utilities that can be safely shared across platforms.\n */\n\n/**\n * Standard OpenPGP configuration for consistent behavior across platforms\n * Uses enum values instead of importing openpgp to avoid loading issues\n */\nexport const STANDARD_PGP_CONFIG = {\n preferredCompressionAlgorithm: 2, // zlib (openpgp.enums.compression.zlib)\n preferredSymmetricAlgorithm: 7, // aes256 (openpgp.enums.symmetric.aes256)\n} as const;\n\n/**\n * Process PGP key generation options with sensible defaults\n *\n * @param options - Optional key generation parameters\n * @param options.name - The name for the PGP key (defaults to \"Vana User\")\n * @param options.email - The email for the PGP key (defaults to \"user@vana.org\")\n * @param options.passphrase - Optional passphrase to protect the private key\n * @returns Processed options with defaults applied\n */\nexport function processPGPKeyOptions(options?: {\n name?: string;\n email?: string;\n passphrase?: string;\n}) {\n return {\n name: options?.name || \"Vana User\",\n email: options?.email || \"user@vana.org\",\n passphrase: options?.passphrase,\n };\n}\n\n/**\n * Get standard PGP key generation parameters\n * Combines default values with standard configuration\n *\n * @param options - Optional key generation parameters\n * @param options.name - The name for the PGP key (defaults to \"Vana User\")\n * @param options.email - The email for the PGP key (defaults to \"user@vana.org\")\n * @param options.passphrase - Optional passphrase to protect the private key\n * @returns Complete key generation parameters object\n */\nexport function getPGPKeyGenParams(options?: {\n name?: string;\n email?: string;\n passphrase?: string;\n}) {\n const { name, email, passphrase } = processPGPKeyOptions(options);\n\n return {\n type: \"rsa\" as const,\n rsaBits: 2048,\n userIDs: [{ name, email }],\n passphrase,\n config: STANDARD_PGP_CONFIG,\n };\n}\n","/**\n * Shared error utilities for platform adapters\n *\n * IMPORTANT: This module contains NO IMPORTS to avoid affecting bundle loading.\n * All functions are pure utilities that can be safely shared across platforms.\n */\n\n/**\n * Wrap platform-specific errors with consistent messaging\n * Provides consistent error formatting across all crypto operations\n *\n * @param operation The operation that failed (e.g., \"encryption\", \"decryption\")\n * @param error The original error that occurred\n * @returns Wrapped error with consistent format\n */\nexport function wrapCryptoError(operation: string, error: unknown): Error {\n const message = error instanceof Error ? error.message : \"Unknown error\";\n return new Error(`${operation} failed: ${message}`);\n}\n\n/**\n * Validate encrypted data structure has required fields\n * Ensures encrypted data objects contain the expected properties\n *\n * @param data The data structure to validate\n * @throws Error if data structure is invalid\n */\nexport function validateEncryptedDataStructure(data: unknown): void {\n if (!data || typeof data !== \"object\") {\n throw new Error(\"Invalid encrypted data format\");\n }\n\n const obj = data as Record<string, unknown>;\n if (!obj.encrypted || !obj.iv || !obj.ephemeralPublicKey) {\n throw new Error(\"Invalid encrypted data format\");\n }\n}\n","/**\n * Shared stream utilities for platform adapters\n *\n * IMPORTANT: This module contains NO IMPORTS to avoid affecting bundle loading.\n * All functions are pure utilities that can be safely shared across platforms.\n */\n\n/**\n * Convert ReadableStream to Uint8Array\n * Used primarily in Node.js environment where OpenPGP may return streams\n *\n * @param stream The ReadableStream to convert\n * @returns Promise resolving to Uint8Array containing all stream data\n */\nexport async function streamToUint8Array(\n stream: ReadableStream<Uint8Array>,\n): Promise<Uint8Array> {\n const reader = stream.getReader();\n const chunks: Uint8Array[] = [];\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n chunks.push(value);\n }\n } finally {\n reader.releaseLock();\n }\n\n // Concatenate all chunks\n const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);\n const result = new Uint8Array(totalLength);\n let offset = 0;\n for (const chunk of chunks) {\n result.set(chunk, offset);\n offset += chunk.length;\n }\n\n return result;\n}\n","import type { WalletClient, Account, Hash } from \"viem\";\nimport type { VanaChainId, VanaChain } from \"./chains\";\nimport type { StorageProvider } from \"./storage\";\nimport type {\n PermissionGrantTypedData,\n TrustServerTypedData,\n UntrustServerTypedData,\n GenericTypedData,\n GrantFile,\n} from \"./permissions\";\n\n/**\n * Configuration for storage providers used by the SDK.\n *\n * Allows you to configure multiple storage backends (IPFS, Pinata, Google Drive, etc.)\n * and specify which one to use by default for file operations.\n *\n * @category Configuration\n * @example\n * ```typescript\n * const storage: StorageConfig = {\n * providers: {\n * ipfs: new IPFSStorage({ gateway: 'https://gateway.pinata.cloud' }),\n * pinata: new PinataStorage({ apiKey: 'your-key', secretKey: 'your-secret' })\n * },\n * defaultProvider: 'ipfs'\n * };\n * ```\n */\nexport interface StorageConfig {\n /** Map of provider name to storage provider instance */\n providers: Record<string, StorageProvider>;\n /** Default provider name to use when none specified */\n defaultProvider?: string;\n}\n\n/**\n * Relayer callback functions for handling gasless transactions.\n *\n * Instead of hardcoding HTTP/REST API calls, users can provide custom callback\n * functions to handle transaction relay in any way they choose (HTTP, WebSocket,\n * direct blockchain submission, etc.).\n *\n * @category Configuration\n * @example\n * ```typescript\n * const relayerCallbacks: RelayerCallbacks = {\n * async submitPermissionGrant(typedData, signature) {\n * // Custom implementation - could be HTTP, WebSocket, etc.\n * const response = await fetch('https://my-relayer.com/api/grant', {\n * method: 'POST',\n * headers: { 'Content-Type': 'application/json' },\n * body: JSON.stringify({ typedData, signature })\n * });\n * const result = await response.json();\n * return result.transactionHash;\n * },\n *\n * async submitFileAddition(url, userAddress) {\n * // Custom relay implementation\n * return await myCustomRelayer.addFile(url, userAddress);\n * }\n * };\n * ```\n */\nexport interface RelayerCallbacks {\n /**\n * Submit a signed permission grant transaction for relay\n *\n * @param typedData - The EIP-712 typed data that was signed\n * @param signature - The user's signature\n * @returns Promise resolving to the transaction hash\n */\n submitPermissionGrant?: (\n typedData: PermissionGrantTypedData,\n signature: Hash,\n ) => Promise<Hash>;\n\n /**\n * Submit a signed permission revocation transaction for relay\n *\n * @param typedData - The EIP-712 typed data that was signed\n * @param signature - The user's signature\n * @returns Promise resolving to the transaction hash\n */\n submitPermissionRevoke?: (\n typedData: GenericTypedData,\n signature: Hash,\n ) => Promise<Hash>;\n\n /**\n * Submit a signed trust server transaction for relay\n *\n * @param typedData - The EIP-712 typed data that was signed\n * @param signature - The user's signature\n * @returns Promise resolving to the transaction hash\n */\n submitTrustServer?: (\n typedData: TrustServerTypedData,\n signature: Hash,\n ) => Promise<Hash>;\n\n /**\n * Submit a signed untrust server transaction for relay\n *\n * @param typedData - The EIP-712 typed data that was signed\n * @param signature - The user's signature\n * @returns Promise resolving to the transaction hash\n */\n submitUntrustServer?: (\n typedData: UntrustServerTypedData,\n signature: Hash,\n ) => Promise<Hash>;\n\n /**\n * Submit a file addition for relay\n *\n * @param url - The file URL to register\n * @param userAddress - The user's address\n * @returns Promise resolving to object with fileId and transactionHash\n */\n submitFileAddition?: (\n url: string,\n userAddress: string,\n ) => Promise<{ fileId: number; transactionHash: Hash }>;\n\n /**\n * Submit a file addition with permissions for relay\n *\n * @param url - The file URL to register\n * @param userAddress - The user's address\n * @param permissions - Array of encrypted permissions\n * @returns Promise resolving to object with fileId and transactionHash\n */\n submitFileAdditionWithPermissions?: (\n url: string,\n userAddress: string,\n permissions: Array<{ account: string; key: string }>,\n ) => Promise<{ fileId: number; transactionHash: Hash }>;\n\n /**\n * Store a grant file for relay (e.g., upload to IPFS)\n *\n * @param grantData - The grant file data\n * @returns Promise resolving to the storage URL\n */\n storeGrantFile?: (grantData: GrantFile) => Promise<string>;\n}\n\n/**\n * Base configuration interface\n *\n * @category Configuration\n */\nexport interface BaseConfig {\n /**\n * Optional relayer callback functions for handling gasless transactions.\n * Provides flexible relay mechanism - can use HTTP, WebSocket, or any custom implementation.\n */\n relayerCallbacks?: RelayerCallbacks;\n\n /** Optional storage providers configuration for file upload/download */\n storage?: StorageConfig;\n /**\n * Optional subgraph URL for querying user files and permissions.\n * If not provided, defaults to the built-in subgraph URL for the current chain.\n * Can be overridden per method call if needed.\n */\n subgraphUrl?: string;\n}\n\n/**\n * Configuration with wallet client\n *\n * @category Configuration\n */\nexport interface WalletConfig extends BaseConfig {\n /** The viem WalletClient instance used for signing transactions */\n walletClient: WalletClient & {\n chain: VanaChain;\n };\n}\n\n/**\n * Configuration with chain and account details\n *\n * @category Configuration\n */\nexport interface ChainConfig extends BaseConfig {\n /** The chain ID for Vana network */\n chainId: VanaChainId;\n /** RPC URL for the chain (optional, will use default for the chain if not provided) */\n rpcUrl?: string;\n /** Optional account for signing transactions */\n account?: Account;\n}\n\n/**\n * Main configuration interface for initializing the Vana SDK.\n *\n * You can configure the SDK using either a pre-configured wallet client\n * (WalletConfig) or by providing chain and account details (ChainConfig).\n * Both approaches support optional storage providers and relayer configuration.\n *\n * @category Configuration\n * @example\n * ```typescript\n * // Using WalletConfig with pre-configured client\n * const config: VanaConfig = {\n * walletClient: createWalletClient({\n * account: privateKeyToAccount('0x...'),\n * chain: moksha,\n * transport: http()\n * }),\n * relayerCallbacks: {\n * submitPermissionGrant: async (typedData, signature) => {\n * // Custom relay implementation\n * return await myRelayer.submit(typedData, signature);\n * }\n * }\n * };\n *\n * // Using ChainConfig with chain ID and account\n * const config: VanaConfig = {\n * chainId: 14800,\n * account: privateKeyToAccount('0x...'),\n * relayerCallbacks: {\n * submitPermissionGrant: async (typedData, signature) => {\n * // Custom relay implementation\n * return await myRelayer.submit(typedData, signature);\n * }\n * }\n * };\n * ```\n */\nexport type VanaConfig = WalletConfig | ChainConfig;\n\n/**\n * Runtime configuration information\n *\n * @category Configuration\n */\nexport interface RuntimeConfig {\n /** Current chain ID */\n chainId: VanaChainId;\n /** Current chain name */\n chainName: string;\n /** Available storage providers */\n storageProviders: string[];\n /** Default storage provider */\n defaultStorageProvider?: string;\n /** Current relayer callbacks configuration */\n relayerCallbacks?: RelayerCallbacks;\n}\n\n/**\n * Validates whether a configuration object is a WalletConfig.\n *\n * @param config - The configuration object to check\n * @returns True if the config is a WalletConfig (contains walletClient)\n * @example\n * ```typescript\n * if (isWalletConfig(config)) {\n * console.log('Using wallet client:', config.walletClient.account?.address);\n * } else {\n * console.log('Using chain config with chain ID:', config.chainId);\n * }\n * ```\n */\nexport function isWalletConfig(config: VanaConfig): config is WalletConfig {\n return \"walletClient\" in config;\n}\n\n/**\n * Validates whether a configuration object is a ChainConfig.\n *\n * @param config - The configuration object to check\n * @returns True if the config is a ChainConfig (contains chainId but not walletClient)\n * @example\n * ```typescript\n * if (isChainConfig(config)) {\n * console.log('Chain ID:', config.chainId);\n * console.log('RPC URL:', config.rpcUrl);\n * } else {\n * console.log('Using pre-configured wallet client');\n * }\n * ```\n */\nexport function isChainConfig(config: VanaConfig): config is ChainConfig {\n return \"chainId\" in config && !(\"walletClient\" in config);\n}\n\n/**\n * Configuration validation options\n *\n * @category Configuration\n */\nexport interface ConfigValidationOptions {\n /** Whether to validate storage providers */\n validateStorage?: boolean;\n /** Whether to validate relayer URL */\n validateRelayer?: boolean;\n /** Whether to validate chain configuration */\n validateChain?: boolean;\n}\n\n/**\n * Configuration validation result\n *\n * @category Configuration\n */\nexport interface ConfigValidationResult {\n /** Whether the configuration is valid */\n valid: boolean;\n /** List of validation errors */\n errors: string[];\n /** List of validation warnings */\n warnings: string[];\n}\n","import type { Chain } from \"viem\";\n\n/**\n * Supported Vana chain IDs\n */\nexport type VanaChainId = 14800 | 1480;\n\n/**\n * Supported Vana chains\n */\nexport type VanaChain = Chain & {\n id: VanaChainId;\n};\n\n/**\n * Chain configuration mapping\n */\nexport type ChainConfig = {\n [K in VanaChainId]: VanaChain;\n};\n\n/**\n * Type guard to check if a chain ID is supported\n *\n * @param chainId - The chain ID to validate\n * @returns True if the chain ID is a supported Vana chain ID\n */\nexport function isVanaChainId(chainId: number): chainId is VanaChainId {\n return chainId === 14800 || chainId === 1480;\n}\n\n/**\n * Type guard to check if a chain is a Vana chain\n *\n * @param chain - The chain object to validate\n * @returns True if the chain is a supported Vana chain\n */\nexport function isVanaChain(chain: Chain): chain is VanaChain {\n return isVanaChainId(chain.id);\n}\n","/**\n * Interface for storage providers that handle file upload, download, and management operations.\n *\n * Storage providers abstract different storage backends (IPFS, Google Drive, Pinata, etc.)\n * behind a common interface. The SDK uses these providers to store encrypted user data\n * and permission grants in a decentralized manner.\n *\n * @category Storage\n * @example\n * ```typescript\n * // Implement a custom storage provider\n * class MyStorageProvider implements StorageProvider {\n * async upload(file: Blob, filename?: string): Promise<StorageUploadResult> {\n * // Custom upload logic\n * return { url: 'https://my-storage.com/file.dat', size: file.size };\n * }\n *\n * async download(url: string): Promise<Blob> {\n * // Custom download logic\n * const response = await fetch(url);\n * return response.blob();\n * }\n *\n * async list(options?: StorageListOptions): Promise<StorageFile[]> {\n * // Return list of files\n * return [];\n * }\n *\n * async delete(url: string): Promise<void> {\n * // Delete file implementation\n * }\n * }\n * ```\n */\nexport interface StorageProvider {\n /**\n * Upload a file to the storage provider\n *\n * @param file - The file to upload\n * @param filename - Optional custom filename\n * @returns Promise with storage URL and metadata\n */\n upload(file: Blob, filename?: string): Promise<StorageUploadResult>;\n\n /**\n * Download a file from the storage provider\n *\n * @param url - The storage URL\n * @returns Promise with file blob\n */\n download(url: string): Promise<Blob>;\n\n /**\n * List files from the storage provider\n *\n * @param options - Optional filtering and pagination\n * @returns Promise with file list\n */\n list(options?: StorageListOptions): Promise<StorageFile[]>;\n\n /**\n * Delete a file from the storage provider\n *\n * @param url - The storage URL\n * @returns Promise with success status\n */\n delete(url: string): Promise<boolean>;\n\n /**\n * Get provider-specific configuration\n *\n * @returns Provider configuration object\n */\n getConfig(): StorageProviderConfig;\n}\n\nexport interface StorageUploadResult {\n /** Public URL to access the file */\n url: string;\n /** File size in bytes */\n size: number;\n /** Content type/MIME type */\n contentType: string;\n /** Provider-specific metadata */\n metadata?: Record<string, unknown>;\n}\n\nexport interface StorageFile {\n /** File identifier */\n id: string;\n /** File name */\n name: string;\n /** Public URL to access the file */\n url: string;\n /** File size in bytes */\n size: number;\n /** Content type/MIME type */\n contentType: string;\n /** Upload timestamp */\n createdAt: Date;\n /** Provider-specific metadata */\n metadata?: Record<string, unknown>;\n}\n\nexport interface StorageListOptions {\n /** Maximum number of files to return */\n limit?: number;\n /** Pagination cursor/offset */\n offset?: string | number;\n /** Filter by file name pattern */\n namePattern?: string;\n /** Filter by content type */\n contentType?: string;\n}\n\nexport interface StorageProviderConfig {\n /** Provider name */\n name: string;\n /** Provider type */\n type: string;\n /** Whether authentication is required */\n requiresAuth: boolean;\n /** Supported features */\n features: {\n upload: boolean;\n download: boolean;\n list: boolean;\n delete: boolean;\n };\n}\n\nexport class StorageError extends Error {\n public readonly code: string;\n public readonly provider: string;\n // The 'cause' property is now inherited from the base Error class\n\n constructor(\n message: string,\n code: string,\n provider: string,\n options?: { cause?: Error },\n ) {\n // Pass the options object with 'cause' to the super constructor\n super(message, options);\n this.name = \"StorageError\";\n this.code = code;\n this.provider = provider;\n }\n}\n","import Ajv, { type ValidateFunction } from \"ajv\";\nimport addFormats from \"ajv-formats\";\nimport dataSchemaSchema from \"../schemas/dataContract.schema.json\";\n\n/**\n * Data schema interface following the Vana schema specification\n *\n * @category Configuration\n */\nexport interface DataSchema {\n /** The name of the data schema */\n name: string;\n /** The version of the data schema */\n version: string;\n /** Optional description of the data schema */\n description?: string;\n /** The dialect type - either SQLite or JSON */\n dialect: \"sqlite\" | \"json\";\n /** Optional version of the dialect */\n dialectVersion?: string;\n /** The actual schema definition as string or object */\n schema: string | object;\n}\n\n/**\n * Error thrown when schema validation fails\n */\nexport class SchemaValidationError extends Error {\n constructor(\n message: string,\n public errors: Array<{\n instancePath: string;\n schemaPath: string;\n keyword: string;\n params: Record<string, unknown>;\n message?: string;\n }>,\n ) {\n super(message);\n this.name = \"SchemaValidationError\";\n }\n}\n\n/**\n * Schema validation utility class\n */\nexport class SchemaValidator {\n private ajv: Ajv;\n private dataSchemaValidator: ValidateFunction;\n\n constructor() {\n this.ajv = new Ajv({\n allErrors: true,\n verbose: true,\n strict: false,\n });\n\n // Add format validation (e.g., date, email, uri)\n addFormats(this.ajv);\n\n // Compile the data schema meta-schema validator\n this.dataSchemaValidator = this.ajv.compile(dataSchemaSchema);\n }\n\n /**\n * Validates a data schema against the Vana meta-schema\n *\n * @param schema - The data schema to validate\n * @throws SchemaValidationError if invalid\n * @example\n * ```typescript\n * const validator = new SchemaValidator();\n *\n * const schema = {\n * name: \"User Profile\",\n * version: \"1.0.0\",\n * dialect: \"json\",\n * schema: {\n * type: \"object\",\n * properties: {\n * name: { type: \"string\" },\n * age: { type: \"number\" }\n * }\n * }\n * };\n *\n * validator.validateDataSchema(schema); // throws if invalid\n * ```\n */\n validateDataSchema(schema: unknown): asserts schema is DataSchema {\n const isValid = this.dataSchemaValidator(schema);\n\n if (!isValid) {\n const errors = this.dataSchemaValidator.errors || [];\n const errorMessage = `Data schema validation failed: ${errors.map((e) => `${e.instancePath} ${e.message}`).join(\", \")}`;\n throw new SchemaValidationError(errorMessage, errors);\n }\n\n // Additional validation based on dialect\n const typedSchema = schema as DataSchema;\n if (\n typedSchema.dialect === \"json\" &&\n typeof typedSchema.schema === \"object\"\n ) {\n // Validate that the embedded JSON Schema is actually valid\n try {\n this.ajv.compile(typedSchema.schema);\n } catch (error) {\n const errorMessage = `Invalid JSON Schema in data schema: ${error instanceof Error ? error.message : \"Unknown schema compilation error\"}`;\n throw new SchemaValidationError(errorMessage, []);\n }\n } else if (\n typedSchema.dialect === \"sqlite\" &&\n typeof typedSchema.schema === \"string\"\n ) {\n // Validate SQLite DDL syntax\n this.validateSQLiteDDL(typedSchema.schema, typedSchema.dialectVersion);\n }\n }\n\n /**\n * Validates data against a JSON Schema from a data schema\n *\n * @param data - The data to validate\n * @param schema - The data schema containing the schema\n * @throws SchemaValidationError if invalid\n * @example\n * ```typescript\n * const validator = new SchemaValidator();\n *\n * const schema = {\n * name: \"User Profile\",\n * version: \"1.0.0\",\n * dialect: \"json\",\n * schema: {\n * type: \"object\",\n * properties: {\n * name: { type: \"string\" },\n * age: { type: \"number\" }\n * },\n * required: [\"name\"]\n * }\n * };\n *\n * const userData = { name: \"Alice\", age: 30 };\n * validator.validateDataAgainstSchema(userData, schema);\n * ```\n */\n validateDataAgainstSchema(data: unknown, schema: DataSchema): void {\n // First validate the schema itself\n this.validateDataSchema(schema);\n\n if (schema.dialect !== \"json\") {\n throw new SchemaValidationError(\n `Data validation only supported for JSON dialect, got: ${schema.dialect}`,\n [],\n );\n }\n\n if (typeof schema.schema !== \"object\") {\n throw new SchemaValidationError(\n \"JSON dialect schemas must have an object schema\",\n [],\n );\n }\n\n // Compile and validate against the data schema\n const dataValidator = this.ajv.compile(schema.schema);\n const isValid = dataValidator(data);\n\n if (!isValid) {\n const errors = dataValidator.errors || [];\n const errorMessage = `Data validation failed: ${errors.map((e) => `${e.instancePath} ${e.message}`).join(\", \")}`;\n throw new SchemaValidationError(errorMessage, errors);\n }\n }\n\n /**\n * Validates a SQLite DDL string for basic syntax\n * Note: This is a basic validation, full SQL parsing would require a proper SQL parser\n *\n * @param ddl - The DDL string to validate\n * @param dialectVersion - Optional SQLite version (e.g., \"3\" for SQLite v3)\n * @throws SchemaValidationError if invalid\n */\n validateSQLiteDDL(ddl: string, dialectVersion?: string): void {\n if (typeof ddl !== \"string\" || ddl.trim().length === 0) {\n throw new SchemaValidationError(\n \"SQLite DDL must be a non-empty string\",\n [],\n );\n }