3
3.2
Create PDF Download Composable
ready-for-dev
2025-12-01
BMAD Story Context Workflow
docs/sprint-artifacts/3-2-create-pdf-download-composable.md
developer
a composable that handles PDF download logic
the download button can trigger downloads easily
- Create composable file at app/composables/useResumePdf.ts
- Implement download logic with blob handling
- Add error handling with toast notifications
- Test composable functionality
Given I call downloadPdf() from the composable, when the function executes, then it fetches /api/resume/pdf as blob
The composable creates object URL from blob and triggers browser download
Download filename is from getPdfFilename() (e.g., "Ali_Arghyani_Resume.pdf")
The composable returns isGenerating: Ref<boolean> for loading state
The composable returns downloadPdf: () => Promise<void> function
Given an error occurs, when caught, then it shows toast notification with error message
After error, isGenerating is set back to false
Object URL is revoked after download to prevent memory leaks
docs/architecture.md
Resume Export Feature - Architecture Document
Composable pattern with isGenerating ref, try-catch, toast notifications, and finally block for cleanup
docs/sprint-artifacts/tech-spec-epic-3.md
Epic Technical Specification: PDF Export
useResumePdf.ts: Set isGenerating true, fetch blob, create object URL, trigger download, revoke URL, set isGenerating false
app/composables/useResumeData.ts
Existing composable that provides getPdfFilename() helper
getPdfFilename(): string
function getPdfFilename(): string {
const name = resumeData.basics.name
const filename = name.replace(/\s+/g, '_')
return `${filename}_Resume.pdf`
}
server/api/resume/pdf.get.ts
API endpoint that returns PDF blob (Story 3.1)
GET /api/resume/pdf
- File location must be app/composables/useResumePdf.ts
- Must use $fetch with responseType: 'blob'
- Must use useToast() from Nuxt UI for error notifications
- Must use getPdfFilename() from useResumeData() for filename
- Must revoke object URL after download to prevent memory leaks
- Must set isGenerating = false in finally block (not just catch)
- Must handle both success and error cases
useResumePdf
Vue composable
function useResumePdf(): { isGenerating: Ref<boolean>, downloadPdf: () => Promise<void> }
app/composables/useResumePdf.ts
$fetch
Nuxt utility
$fetch<T>(url: string, options?: { responseType: 'blob' }): Promise<T>
nuxt/app
useToast
Nuxt UI composable
useToast(): { add: (options: ToastOptions) => void }
@nuxt/ui
URL.createObjectURL
Web API
URL.createObjectURL(blob: Blob): string
global
URL.revokeObjectURL
Web API
URL.revokeObjectURL(url: string): void
global
Vue composable testing with Vitest. Mock $fetch and useToast.
app/composables/**/*.spec.ts, tests/
Mock $fetch, call downloadPdf(), verify fetch called with correct URL and responseType
Verify URL.createObjectURL called with blob, anchor element created and clicked
Verify anchor download attribute matches getPdfFilename() output
Verify isGenerating.value is Ref<boolean> and starts as false
Verify downloadPdf is async function returning Promise<void>
Mock $fetch to throw, verify toast.add called with error message
Mock $fetch to throw, verify isGenerating.value is false after error
Verify URL.revokeObjectURL called after download