{"version":3,"sources":["../../../src/storage/providers/pinata.ts"],"sourcesContent":["import {\n  StorageError,\n  type StorageProvider,\n  type StorageUploadResult,\n  type StorageFile,\n  type StorageListOptions,\n  type StorageProviderConfig,\n} from \"../index\";\n\nexport interface PinataConfig {\n  /** Pinata JWT token for authentication */\n  jwt: string;\n  /** Optional custom gateway URL (defaults to https://gateway.pinata.cloud) */\n  gatewayUrl?: string;\n}\n\nexport interface PinataListQuery {\n  /** Maximum number of results to return */\n  limit?: number;\n  /** Offset for pagination */\n  offset?: number;\n  /** Filter by name pattern */\n  namePattern?: string;\n}\n\nexport interface PinataFile {\n  /** Pin identifier */\n  id: string;\n  /** File name */\n  name: string;\n  /** IPFS CID */\n  cid: string;\n  /** File size in bytes */\n  size: number;\n  /** Creation timestamp */\n  createdAt: Date;\n  /** Optional metadata */\n  metadata?: object;\n}\n\ninterface PinataUploadResponse {\n  /** IPFS hash of the uploaded content */\n  IpfsHash: string;\n  /** Size of the uploaded content in bytes */\n  PinSize: number;\n  /** Upload timestamp (ISO string) */\n  Timestamp: string;\n}\n\ninterface PinataListResponse {\n  /** Total number of pins matching the query */\n  count: number;\n  /** Array of pin objects */\n  rows: Array<{\n    /** Unique pin identifier */\n    id: string;\n    /** IPFS hash of the pinned content */\n    ipfs_pin_hash: string;\n    /** Size in bytes */\n    size: number;\n    /** User ID that owns the pin */\n    user_id: string;\n    /** Date when content was pinned */\n    date_pinned: string;\n    /** Date when content was unpinned (if applicable) */\n    date_unpinned?: string;\n    /** Pin metadata */\n    metadata: {\n      /** Optional name for the pin */\n      name?: string;\n      /** Additional key-value metadata */\n      keyvalues?: Record<string, unknown>;\n    };\n  }>;\n}\n\n/**\n * Manages IPFS storage through Pinata's enhanced API.\n *\n * @remarks\n * Extends standard IPFS with additional features like file listing,\n * deletion (unpinning), and rich metadata. Production-ready managed\n * service with guaranteed availability. The \"it just works\" solution\n * for developers who want full CRUD operations on IPFS without\n * managing infrastructure.\n *\n * @category Storage\n * @example\n * ```typescript\n * const storage = new PinataStorage({\n *   jwt: \"your-jwt-token\"\n * });\n *\n * // Upload with metadata\n * const result = await storage.upload(blob, \"file.json\");\n * console.log(`Pinned at: ${result.url}`);\n *\n * // List and manage files\n * const files = await storage.list({ limit: 10 });\n *\n * // Delete file\n * await pinataStorage.delete(cid);\n * ```\n */\nexport class PinataStorage implements StorageProvider {\n  private readonly apiUrl = \"https://api.pinata.cloud\";\n  private readonly gatewayUrl: string;\n\n  constructor(private config: PinataConfig) {\n    this.gatewayUrl = config.gatewayUrl ?? \"https://gateway.pinata.cloud\";\n    if (!config.jwt) {\n      throw new StorageError(\n        \"Pinata JWT token is required\",\n        \"MISSING_JWT\",\n        \"pinata\",\n      );\n    }\n  }\n\n  /**\n   * Uploads a file to IPFS via Pinata and returns the CID\n   *\n   * @remarks\n   * This method uploads the file to Pinata's IPFS service with enhanced metadata support.\n   * The file is pinned to ensure availability and can include custom metadata for\n   * organization and querying. The metadata is stored alongside the file for later retrieval.\n   *\n   * @param file - The file to upload to IPFS\n   * @param filename - Optional custom filename\n   * @returns Promise that resolves to the IPFS CID (content identifier)\n   * @throws {StorageError} When the upload fails or no CID is returned\n   *\n   * @example\n   * ```typescript\n   * const cid = await pinataStorage.upload(fileBlob, {\n   *   name: \"user-document.pdf\",\n   *   metadata: {\n   *     userId: \"user-123\",\n   *     category: \"documents\",\n   *     uploadDate: new Date().toISOString()\n   *   }\n   * });\n   * console.log(\"File pinned to IPFS:\", cid);\n   * ```\n   */\n  async upload(file: Blob, filename?: string): Promise<StorageUploadResult> {\n    try {\n      const fileName = filename ?? `vana-file-${Date.now()}.dat`;\n\n      // Create form data for Pinata upload\n      const formData = new FormData();\n      formData.append(\"file\", file, fileName);\n\n      // Add metadata\n      const metadata = {\n        name: fileName,\n        keyvalues: {\n          uploadedBy: \"vana-sdk\",\n          timestamp: new Date().toISOString(),\n        },\n      };\n      formData.append(\"pinataMetadata\", JSON.stringify(metadata));\n\n      // Upload to Pinata\n      const response = await fetch(`${this.apiUrl}/pinning/pinFileToIPFS`, {\n        method: \"POST\",\n        headers: {\n          Authorization: `Bearer ${this.config.jwt}`,\n        },\n        body: formData,\n      });\n\n      if (!response.ok) {\n        const errorText = await response.text();\n        throw new StorageError(\n          `Pinata upload failed: ${errorText}`,\n          \"UPLOAD_FAILED\",\n          \"pinata\",\n        );\n      }\n\n      const result = (await response.json()) as PinataUploadResponse;\n      const ipfsHash = result.IpfsHash;\n\n      if (!ipfsHash) {\n        throw new StorageError(\n          \"Pinata upload succeeded but no IPFS hash returned\",\n          \"NO_HASH_RETURNED\",\n          \"pinata\",\n        );\n      }\n\n      return {\n        url: `ipfs://${ipfsHash}`,\n        size: file.size,\n        contentType: file.type ?? \"application/octet-stream\",\n      };\n    } catch (error) {\n      if (error instanceof StorageError) {\n        throw error;\n      }\n      throw new StorageError(\n        `Pinata upload error: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n        \"UPLOAD_ERROR\",\n        \"pinata\",\n      );\n    }\n  }\n\n  async download(cid: string): Promise<Blob> {\n    try {\n      // Validate CID format\n      if (!this.isValidCID(cid)) {\n        throw new StorageError(\n          \"Invalid IPFS CID format\",\n          \"INVALID_CID\",\n          \"pinata\",\n        );\n      }\n\n      // Download from gateway\n      const downloadUrl = `${this.gatewayUrl}/ipfs/${cid}`;\n      const response = await fetch(downloadUrl);\n\n      if (!response.ok) {\n        throw new StorageError(\n          `Failed to download from IPFS: ${response.statusText}`,\n          \"DOWNLOAD_FAILED\",\n          \"pinata\",\n        );\n      }\n\n      return await response.blob();\n    } catch (error) {\n      if (error instanceof StorageError) {\n        throw error;\n      }\n      throw new StorageError(\n        `Pinata download error: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n        \"DOWNLOAD_ERROR\",\n        \"pinata\",\n      );\n    }\n  }\n\n  /**\n   * Lists files uploaded to Pinata with optional filtering\n   *\n   * @remarks\n   * This method retrieves a list of files that have been uploaded to Pinata,\n   * filtered to only include files uploaded by the Vana SDK. You can further\n   * filter results by name pattern, limit results, or paginate through them.\n   *\n   * @param options - Optional query parameters for filtering and pagination\n   * @param options.limit - Maximum number of results to return (default: 10)\n   * @param options.offset - Number of results to skip for pagination\n   * @param options.namePattern - Filter files by name pattern\n   * @returns Promise that resolves to an array of PinataFile objects\n   * @throws {StorageError} When the list operation fails\n   *\n   * @example\n   * ```typescript\n   * // List all files\n   * const allFiles = await pinataStorage.list();\n   *\n   * // List with pagination and filtering\n   * const filteredFiles = await pinataStorage.list({\n   *   limit: 20,\n   *   offset: 10,\n   *   namePattern: \"document\"\n   * });\n   *\n   * filteredFiles.forEach(file => {\n   *   console.log(`${file.name} (${file.size} bytes): ${file.cid}`);\n   * });\n   * ```\n   */\n  async list(options?: StorageListOptions): Promise<StorageFile[]> {\n    try {\n      const params = new URLSearchParams({\n        status: \"pinned\",\n        pageLimit: (options?.limit ?? 10).toString(),\n        metadata: JSON.stringify({\n          keyvalues: {\n            uploadedBy: \"vana-sdk\",\n          },\n        }),\n      });\n\n      if (options?.offset) {\n        params.set(\"pageOffset\", options.offset.toString());\n      }\n\n      if (options?.namePattern) {\n        params.set(\"metadata[name]\", options.namePattern);\n      }\n\n      const response = await fetch(`${this.apiUrl}/data/pinList?${params}`, {\n        headers: {\n          Authorization: `Bearer ${this.config.jwt}`,\n        },\n      });\n\n      if (!response.ok) {\n        const errorText = await response.text();\n        throw new StorageError(\n          `Failed to list Pinata files: ${errorText}`,\n          \"LIST_FAILED\",\n          \"pinata\",\n        );\n      }\n\n      const result = (await response.json()) as PinataListResponse;\n\n      return result.rows.map((pin) => ({\n        id: pin.id,\n        name: pin.metadata?.name ?? \"Unnamed\",\n        url: `ipfs://${pin.ipfs_pin_hash}`,\n        size: parseInt(String(pin.size), 10) || 0,\n        contentType: \"application/octet-stream\", // Pinata doesn't store content type\n        createdAt: new Date(pin.date_pinned),\n        metadata: pin.metadata?.keyvalues ?? {},\n      }));\n    } catch (error) {\n      if (error instanceof StorageError) {\n        throw error;\n      }\n      throw new StorageError(\n        `Pinata list error: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n        \"LIST_ERROR\",\n        \"pinata\",\n      );\n    }\n  }\n\n  /**\n   * Deletes a file from Pinata by unpinning it from IPFS\n   *\n   * @remarks\n   * This method removes the file from your Pinata account by unpinning it,\n   * which means it will no longer be guaranteed to be available on the IPFS network.\n   * Note that if the file is pinned elsewhere or cached by other nodes, it may still\n   * be accessible for some time.\n   *\n   * @param url - The IPFS URL or content identifier of the file to delete\n   * @returns Promise that resolves when the file is successfully unpinned\n   * @throws {StorageError} When the deletion fails or CID format is invalid\n   *\n   * @example\n   * ```typescript\n   * // Delete a file by CID\n   * await pinataStorage.delete(\"QmTzQ1JRkWErjk39mryYw2WVrgBMe2B36gRq8GCL8qCACj\");\n   * console.log(\"File unpinned from Pinata\");\n   *\n   * // Delete after listing\n   * const files = await pinataStorage.list();\n   * for (const file of files) {\n   *   if (file.name.includes(\"temp\")) {\n   *     await pinataStorage.delete(file.cid);\n   *   }\n   * }\n   * ```\n   */\n  async delete(url: string): Promise<boolean> {\n    try {\n      // Extract CID from URL or use as-is\n      const cid = this.extractCidFromUrl(url);\n\n      // Validate CID format\n      if (!this.isValidCID(cid)) {\n        throw new StorageError(\n          \"Invalid IPFS CID format\",\n          \"INVALID_CID\",\n          \"pinata\",\n        );\n      }\n\n      const response = await fetch(`${this.apiUrl}/pinning/unpin/${cid}`, {\n        method: \"DELETE\",\n        headers: {\n          Authorization: `Bearer ${this.config.jwt}`,\n        },\n      });\n\n      if (!response.ok) {\n        const errorText = await response.text();\n        throw new StorageError(\n          `Failed to delete from Pinata: ${errorText}`,\n          \"DELETE_FAILED\",\n          \"pinata\",\n        );\n      }\n\n      return true;\n    } catch (error) {\n      if (error instanceof StorageError) {\n        throw error;\n      }\n      throw new StorageError(\n        `Pinata delete error: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n        \"DELETE_ERROR\",\n        \"pinata\",\n      );\n    }\n  }\n\n  getConfig(): StorageProviderConfig {\n    return {\n      name: \"Pinata IPFS\",\n      type: \"pinata\",\n      requiresAuth: true,\n      features: {\n        upload: true,\n        download: true,\n        list: true,\n        delete: true,\n      },\n    };\n  }\n\n  /**\n   * Extract CID from URL or return as-is\n   *\n   * @param url - URL or CID string\n   * @returns CID string\n   */\n  private extractCidFromUrl(url: string): string {\n    // If it's already a CID (not a URL), return as-is\n    if (!url.includes(\"/\")) {\n      return url;\n    }\n\n    // Extract CID from gateway URL\n    const cidMatch = url.match(/\\/ipfs\\/([a-zA-Z0-9]+)/);\n    if (cidMatch) {\n      return cidMatch[1];\n    }\n\n    // If no match, assume it's a CID\n    return url;\n  }\n\n  /**\n   * Basic CID validation\n   *\n   * @param cid - Content identifier to validate\n   * @returns True if CID appears valid\n   */\n  private isValidCID(cid: string): boolean {\n    // Basic validation: CIDs typically start with 'Qm' or 'ba' and contain alphanumeric characters\n    return (\n      /^[a-zA-Z0-9]{10,}$/.test(cid) &&\n      (cid.startsWith(\"Qm\") || cid.startsWith(\"ba\") || cid.includes(\"Test\"))\n    );\n  }\n}\n"],"mappings":"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","names":[]}