PDF tárhely
Számla kiállításakor a kassza alapból letölti a PDF-et is, és a válasz pdf mezőjében Uint8Array-ként adja vissza. Ha ezt elmented egy tárhelyre, a vevőnek letöltési linket adhatsz, és nem kell minden letöltésnél a Számlázz.hu-t hívnod. A kassza/storage modul ehhez ad egy közös felületet és kész adaptereket. Egyiknek sincs függősége: a szolgáltató SDK-ját, ha kell, te adod át.
A kulcs ebben a példában szamlak/2026/09/E-WEB-2026-12.pdf alakú lesz. Az adatbázisba a kulcsot mentsd, ne a PDF-et base64-ként és ne egy lejáró URL-t.
PDF mentése tárhelyre
A számla PDF-je dátum szerinti kulccsal egy memóriás tárhelyre.
A storePdf#
A storePdf(storage, key, pdf) ellenőrzi, hogy a tartalom valóban PDF-e (a %PDF fejléccel kezdődik), majd application/pdf típussal elmenti. Ha a tartalom nem PDF, StorageError-t dob, és nem ír semmit. Az eredmény egy StoredFile:
| Mező | Típus | Leírás |
|---|---|---|
key | string | Az általad megadott kulcs, az adapter prefix opciója nélkül. |
url | string | undefined | Letöltési cím, ha az adapter a mentéskor meg tudja adni. |
size | number | A fájl mérete bájtban. |
contentType | string | application/pdf |
Ha a számlát downloadPdf: false beállítással állítottad ki, vagy a mentés nem sikerült, a PDF-et később az invoices.getPdf() metódussal kérheted le.
Kulcsok#
Az invoicePdfKey() és a receiptPdfKey() év és hónap szerinti mappába rendezett, ékezet nélküli kulcsot készít a bizonylatszámból:
| Változó | Kulcs |
|---|---|
szamla | szamlak/2026/09/E-WEB-2026-12.pdf (a mai nap szerint) |
dijbekero | szamlak/dijbekero/2026/01/D-WEB-2026-3.pdf |
nyugta | nyugtak/2026/09/NYGT-2026-45.pdf |
ugyfelenkent | ugyfelek/Arvizturo-Kft/2026/09/E-WEB-2026-12.pdf |
| Paraméter | Alapérték | Leírás |
|---|---|---|
number | – | A bizonylatszám, ebből lesz a fájlnév. Kötelező. |
type | 'invoice' | Csak az invoicePdfKey-nél. Almappát ad: proforma → dijbekero, advance → elolegszamla, final → vegszamla, corrective → helyesbito, storno → sztorno, deliveryNote → szallitolevel. |
prefix | szamlak vagy nyugtak | Az első mappa, perjellel tagolva több szint is lehet. |
date | ma, budapesti idő szerint | Date vagy YYYY-MM-DD. Ebből lesz az év és a hónap. |
Minden szakaszból eltűnnek az ékezetek, és ami nem betű, szám, pont, aláhúzás vagy kötőjel, az kötőjel lesz. Ugyanezt a sanitizeKeySegment() függvény külön is elvégzi.
Az adapterek minden kulcsot ellenőriznek: nem lehet üres vagy 1024 karakternél hosszabb, nem kezdődhet perjellel, és nem lehet benne visszaperjel, vezérlőkarakter, üres, . vagy .. szakasz. Hibás kulcsnál StorageError jön operation: 'key' értékkel. Saját adapterben ugyanezt az assertStorageKey() végzi.
Minden adapternek van prefix opciója. Ez a tárhelyen a kulcs elé kerül, például prefix: 'eles' mellett a fájl az eles/szamlak/2026/09/… útvonalra kerül, a StoredFile.key viszont prefix nélkül marad. Így ugyanazzal a kulccsal dolgozhatsz fejlesztői és éles tárhelyen is.
Adapterek#
Minden adapter tud menteni (put). A többi művelet adapterenként eltér:
| Adapter | Import | get | delete | getUrl lejárat nélkül | getUrl lejáró linkkel |
|---|---|---|---|---|---|
s3FetchStorage | kassza/storage | igen | igen | publicBaseUrl esetén | aláírt URL, legfeljebb 7 nap |
s3Storage | kassza/storage | GetObjectCommand-dal | DeleteObjectCommand-dal | publicBaseUrl esetén | getSignedUrl-lel, legfeljebb 7 nap |
r2BindingStorage | kassza/storage | igen | igen | publicBaseUrl esetén | nem |
vercelBlobStorage | kassza/storage | nem | del-lel | head-del | nem |
uploadthingStorage | kassza/storage | nem | igen | nem, mindig aláírt | aláírt URL, legfeljebb 7 nap |
supabaseStorage | kassza/storage | igen | igen | public: true esetén | aláírt URL, legfeljebb 1 év |
fsStorage | kassza/storage/fs | igen | igen | publicBaseUrl esetén | nem |
memoryStorage | kassza/storage | igen | igen | mindig | nem |
Ahol van aláírt URL, a lejárat alapból 1 óra, és a megengedettnél hosszabb lejárat StorageError-t ad. Az R2 binding, a Vercel Blob és a fájlrendszer adapternél már az expiresInSeconds megadása is StorageError-t ad.
Aláírt S3 kéréseket küld a beépített fetch-csel, AWS SDK nélkül. Az aláíráshoz a Web Crypto API-t használja, ezért edge környezetben is fut. Amazon S3, Cloudflare R2, MinIO, Backblaze és más S3-kompatibilis tárhelyek mellett is működik.
| Opció | Leírás |
|---|---|
bucket, region, accessKeyId, secretAccessKey | Kötelező. R2-nél a régió auto. Ha bármelyik üres, a létrehozás configuration hibát dob. |
sessionToken | Ideiglenes AWS hitelesítéshez. |
endpoint | Nem AWS tárhelynél, például https://<account-id>.r2.cloudflarestorage.com. |
forcePathStyle | Endpoint nélkül alapból https://<bucket>.s3.<region>.amazonaws.com a cím, pontot tartalmazó bucketnél vagy true esetén https://s3.<region>.amazonaws.com/<bucket>. Endpointtal alapból <endpoint>/<bucket>, false esetén <bucket>.<endpoint>. |
publicBaseUrl | Nyilvános cím, például egy CDN vagy az R2 egyedi domainje. |
prefix, fetch | Kulcselőtag és saját fetch implementáció. |
Ha a projektben már van @aws-sdk/client-s3, add át a kliensét és a parancsosztályait. A GetObjectCommand és a DeleteObjectCommand csak a get, a delete és az aláírt URL-hez kell.
| Opció | Leírás |
|---|---|
client, bucket | Kötelező. |
commands | Kötelező. PutObjectCommand kötelező, a másik kettő opcionális. |
getSignedUrl | A @aws-sdk/s3-request-presigner függvénye, lejáró linkhez. |
publicBaseUrl, prefix | Nyilvános cím és kulcselőtag. |
Cloudflare Workers alatt a bucket bindingot közvetlenül is használhatod, hozzáférési kulcs nélkül:
Az első paraméter a binding, a második opcionális: publicBaseUrl és prefix. Lejáró linket a binding nem tud készíteni. Ha a fájlok nem nyilvánosak, a Workerből szolgáld ki őket a get() eredményével.
Add át a @vercel/blob függvényeit. A put kötelező, a del a törléshez, a head a getUrl()-hez kell.
| Opció | Leírás |
|---|---|
access | Kötelező, 'public' vagy 'private'. |
token | Blob token, ha nem a @vercel/blob alapértelmezését használod. |
allowOverwrite | Felülírhatja-e a meglévő fájlt, alapból true. |
cacheControlMaxAge | Gyorsítótárazási idő másodpercben. |
prefix | Kulcselőtag. |
Az adapter véletlen utótag nélkül ment, így a fájl útvonala pontosan a kulcs. A StoredFile.url a blob URL-je. Olvasni (get) és lejáró linket készíteni nem tud.
Add át az UploadThing szerveroldali UTApi példányát:
| Opció | Leírás |
|---|---|
utapi | Kötelező. |
acl | 'public-read' vagy 'private'. |
contentDisposition | 'inline' vagy 'attachment'. |
prefix | Kulcselőtag. |
A teljes kulcs az UploadThing customId azonosítója lesz, ezért előtaggal együtt legfeljebb 128 karakter lehet. A fájl neve a kulcs utolsó szakasza. A getUrl() mindig aláírt linket ad a generateSignedURL függvénnyel, ehhez uploadthing v7 kell.
Add át a Supabase klienst és a bucket nevét. A kliens szerveroldalon készüljön, olyan kulccsal, amely írhat a bucketbe.
| Opció | Leírás |
|---|---|
client, bucket | Kötelező. |
public | Nyilvános bucketnél true: ekkor a getUrl() lejárat nélkül a nyilvános címet adja. |
upsert | Felülírhatja-e a meglévő fájlt, alapból true. |
cacheControl | A Supabase cacheControl beállítása. |
prefix | Kulcselőtag. |
Saját szerveren a lemezre is menthetsz. Ez az adapter külön útvonalon érhető el, mert a node:fs modult használja:
| Opció | Leírás |
|---|---|
directory | Kötelező. Relatív útvonalnál a munkakönyvtárhoz képest értendő. Hiányában a létrehozás configuration hibát dob. |
publicBaseUrl | Ha a könyvtárat egy webszerver kiszolgálja, annak címe. Nélküle a getUrl() hibát dob. |
prefix | Kulcselőtag. |
Az adapter létrehozza a hiányzó mappákat, és előbb ideiglenes fájlba ír, majd átnevezi, így félig megírt PDF nem marad a lemezen. Olyan kulcsot, amely a könyvtáron kívülre mutatna, szimbolikus linken keresztül sem enged. A pathFor(key) a fájl abszolút útvonalát adja, a directory a gyökérkönyvtárat.
Edge környezetben nem fut, és serverless függvényben sem érdemes használni, mert ott a fájlrendszer nem tartós.
Tesztekhez és a sandboxhoz. A fájlok a folyamat memóriájában vannak:
A publicBaseUrl alapból memory://storage, a prefix itt is megadható. A files mező a mentett fájlokat tartalmazza teljes kulcs szerint (body és contentType), a clear() mindet törli. A getUrl() nem létező fájlra StorageError-t dob 404-es status értékkel.
Letöltési link#
A vevő a PDF-et egy saját végponton keresztül kapja meg: a végpont ellenőrzi, hogy a bejelentkezett felhasználó láthatja-e a számlát, és rövid lejáratú linkre irányít át.
Hibakezelés#
A tárhely hibáit a kassza StorageError-ba csomagolja:
| Mező | Leírás |
|---|---|
message | Magyar hibaüzenet, benne a szolgáltató hibájával. |
operation | 'put', 'get', 'delete', 'getUrl' vagy 'key'. |
key | Az érintett kulcs. |
status | HTTP státusz, ha ismert. |
cause | Az eredeti hiba. |
Az isStorageError() típusőrrel szűkítheted. A hiányzó fájl nem hiba: a get() ilyenkor undefined-ot ad. A hiányzó kötelező opció (például a bucket vagy a directory) viszont már az adapter létrehozásakor configuration kategóriájú SzamlazzError-t dob.
A számla a mentés előtt már elkészült, ezért tárhelyhiba miatt soha ne állítsd ki újra. Naplózd a hibát, és a PDF-et később a számlaszám alapján kérd le újra:
A pdfMentesKesobb egy háttérfeladatban a kassza.invoices.getPdf(szamlaszam) hívással tölti le újra a PDF-et, és megismétli a mentést.
Tesztelés#
A memoryStorage és a mock kliens együtt hálózat nélkül teszteli a teljes folyamatot. A mock minimális, de érvényes PDF-et ad vissza, amit a storePdf elfogad:
Saját adapter#
Ha a tárhelyedhez nincs adapter, írd meg a StorageAdapter felületet. Csak a put kötelező, a get, a delete és a getUrl opcionális. A guardStorageCall() a szolgáltató hibáját StorageError-ba csomagolja: