SDK TypeScript για το API PDF: πέντε λεπτά ως την πρώτη κλήση και τι αφήνει σε εσάς
Χρησιμοποιήστε το @pdf123/sdk από TypeScript για να συγχωνεύσετε αρχεία, να προσθέσετε υδατογραφήματα, να διαβάσετε πληροφορίες αρχείου και να αλυσοδέσετε πολλά εργαλεία σε ένα αίτημα. Εντοπίζει λανθασμένα γραμμένο όνομα εργαλείου ή παραμέτρου πριν ανέβει οτιδήποτε· οι απορρίψεις του διακομιστή φέρουν αιτία και υπόδειξη, ενώ οι επαναλήψεις, η ακύρωση και οι μερικές αποτυχίες σε μια παρτίδα είναι δική σας δουλειά.

Όταν καλείτε το API PDF από το Node, το κιτ ανάπτυξης λογισμικού (SDK) @pdf123/sdk εντοπίζει ένα λανθασμένα γραμμένο όνομα εργαλείου ή παραμέτρου πριν ανέβει οποιοδήποτε αρχείο. Δεν κάνει επαναλήψεις, δεν ακυρώνει για λογαριασμό σας και δεν στέλνει τα αποτελέσματα ως ροή. Το new Pdf123Client() δείχνει από προεπιλογή, ανώνυμα, στο https://pdf123.xyz· τα αρχεία σας ανεβαίνουν εκεί και επεξεργάζονται εκεί, γιατί δεν υπάρχει τοπική μηχανή.
Δύο PDF σε έναν φάκελο αρκούν για συγχώνευση
Χρειάζεστε Node 20.3 ή νεότερο, ή Bun. Το πακέτο είναι ενότητα ES: βάλτε τον κώδικα σε αρχείο .mjs ή εκτελέστε πρώτα npm pkg set type=module. Βάλτε τα a.pdf και b.pdf στον τρέχοντα κατάλογο.
npm install @pdf123/sdk
// merge.mjs
import { Pdf123Client } from "@pdf123/sdk";
import { readFileInput, saveResult } from "@pdf123/sdk/node";
const client = new Pdf123Client();
const merged = await client.run("merge", {
input: [await readFileInput("a.pdf"), await readFileInput("b.pdf")],
});
console.log(await saveResult(merged, { output: "merged.pdf" }));
Μετά το node merge.mjs το τερματικό τυπώνει τη διαδρομή εξόδου που δώσατε, merged.pdf, και το αρχείο βρίσκεται στον τρέχοντα κατάλογο. Τα readFileInput και saveResult βρίσκονται στο @pdf123/sdk/node. Το βασικό σημείο εισόδου εξαρτάται μόνο από το fetch και δέχεται εισόδους ως απλό { name, data }, οπότε ένα περιβάλλον χωρίς σύστημα αρχείων μπορεί να χρησιμοποιήσει τον ίδιο πελάτη. Ένα αποτέλεσμα είναι { data, contentType, filename, json }· το data είναι ένα ολόκληρο Uint8Array, και το Buffer.from(result.data) σας δίνει ένα Buffer.
Η Συγχώνευση PDF είναι ένα μόνο από τα εργαλεία.
Τα αρχεία επιστρέφουν στο data, οι αναφορές στο json
Κάθε εργαλείο καλείται ως client.run(toolName, { input, params }). Ο παρακάτω κώδικας συνεχίζει το merge.mjs της προηγούμενης ενότητας, με τα client και saveResult ήδη διαθέσιμα. Το πού θα διαβάσετε το αποτέλεσμα εξαρτάται από το πεδίο returns του getTool, που είναι είτε "file" είτε "json": χρησιμοποιήστε το data για το πρώτο και το json για το δεύτερο.
import { getTool } from "@pdf123/sdk";
import { readFileInput, saveResult } from "@pdf123/sdk/node";
// client comes from merge.mjs in the previous section
const marked = await client.run("watermark", {
input: await readFileInput("a.pdf"),
params: { watermarkText: "DRAFT", fontSize: 40 },
});
console.log(await saveResult(marked, { output: "marked.pdf" }));
const info = await client.run("get-info", { input: await readFileInput("a.pdf") });
console.log(info.json.FileSize, info.json.Encrypted);
console.log(getTool("get-info")?.returns); // "json"
Το πρώτο τμήμα γράφει το αρχείο με υδατογράφημα στο marked.pdf· στο δεύτερο, το info.json είναι η αναφορά. Δεν χρειάζεται να απομνημονεύσετε ονόματα πεδίων, προεπιλογές ή επιτρεπόμενες τιμές: το getTool("watermark")?.fields είναι αυτός ο κατάλογος, και το pdfx describe watermark στη γραμμή εντολών διαβάζει τον ίδιο. Το TOOLS περιέχει και τα 95 εργαλεία. Η πλήρης αναφορά βρίσκεται στον οδηγό SDK στη σελίδα για προγραμματιστές.
Για να συγχωνεύσετε, μετά να βάλετε υδατογράφημα και μετά να συμπιέσετε, μπορείτε να χρησιμοποιήσετε το pipeline ή τρεις διαδοχικές κλήσεις run· ποιο θα διαλέξετε εξαρτάται από το αν θέλετε τα ενδιάμεσα αρχεία. Και πάλι αυτό συνεχίζει με το client παραπάνω. Το pipeline συγκεντρώνει τα τρία βήματα σε ένα αίτημα, τα ενδιάμεσα αποτελέσματα μένουν στον διακομιστή, και ο καλών παίρνει μόνο το τελευταίο βήμα, που ο παρακάτω κώδικας γράφει στο out.pdf. Αν θέλετε το αρχείο κάθε βήματος, καλέστε τα χωριστά.
const result = await client.pipeline(
[{ tool: "merge" }, { tool: "watermark", params: { watermarkText: "DRAFT" } }, { tool: "compress" }],
[await readFileInput("a.pdf"), await readFileInput("b.pdf")],
);
console.log(await saveResult(result, { output: "out.pdf" }));
Ένας αγωγός έχει το πολύ 8 βήματα. Ένα 9ο βήμα παίρνει HTTP 400: at most 8 pipeline steps allowed.
Ανώνυμα, με κλειδί ή στραμμένο στον δικό σας διακομιστή
Αν ορίσατε το PDFX_API_BASE και παρ' όλα αυτά φτάνετε στο https://pdf123.xyz, αυτό συμβαίνει επειδή το new Pdf123Client() δεν διαβάζει μεταβλητές περιβάλλοντος· κοιτάζει μόνο τις επιλογές του κατασκευαστή του.
Για ανώνυμες κλήσεις, χρησιμοποιήστε τον κατασκευαστή ως έχει. Με κλειδί, γράψτε new Pdf123Client({ apiKey: process.env.PDFX_API_KEY })· η κεφαλίδα του αιτήματος είναι X-API-KEY.
Για να δείξετε στον δικό σας διακομιστή, χρησιμοποιήστε το clientFromEnv() από το @pdf123/sdk/node. Διαβάζει τα PDFX_API_BASE και PDFX_API_KEY. Μπορείτε επίσης να γράψετε απευθείας new Pdf123Client({ baseUrl: "http://localhost:8080" }). Αν η διεύθυνση δεν είναι προσβάσιμη, η κλήση αποτυγχάνει· δεν υπάρχει λειτουργία εκτός σύνδεσης. Τι κερδίζετε και τι κοστίζει όταν τα αρχεία μένουν στο δικό σας δίκτυο, εξηγείται στο Τι σας αγοράζει στην πραγματικότητα η αυτο-φιλοξενία (και τι κοστίζει).
Μια λάθος παράμετρος εντοπίζεται πριν από τη μεταφόρτωση
Χωρίς αυτό, το αρχείο θα ανέβαινε πρώτο και ένα λανθασμένα γραμμένο όνομα πεδίου θα φαινόταν μόνο ως 400 από τον διακομιστή. Οι τύποι παραμέτρων κάθε εργαλείου παράγονται από τον κατάλογο, ώστε τρία συνηθισμένα λάθη να αποτυγχάνουν ήδη στο στάδιο του tsc:
| Τι γράφετε | Σφάλμα μεταγλωττιστή (απόσπασμα) |
|---|---|
client.run("rotat", { input }) |
Argument of type '"rotat"' is not assignable to parameter of type 'ToolId' |
params: { watermarkTxt: "DRAFT" } |
Object literal may only specify known properties, but 'watermarkTxt' does not exist, με κατάληξη Did you mean to write 'watermarkText'? |
params: { angle: 45 } (το rotate δέχεται μόνο 90, 180, 270) |
Type '45' is not assignable to type …, ακολουθούμενο από τις επιτρεπόμενες τιμές |
Οι σωστές κλήσεις όπως { angle: 90 } ή { watermarkText: "DRAFT", fontSize: 30 } μεταγλωττίζονται. Τα αριθμητικά πεδία δέχονται αριθμούς και συμβολοσειρές, άρα τα fontSize: 30 και fontSize: "30" είναι ισοδύναμα. Ένα έργο που δεν χρησιμοποιεί TypeScript παίρνει τα ίδια σφάλματα κατά την εκτέλεση, και το αίτημα δεν στέλνεται ποτέ: η τρίτη περίπτωση δίνει Field "angle" of "rotate" must be one of: 90, 180, 270, και ένα λανθασμένα γραμμένο όνομα εργαλείου δίνει unknown_tool, με παρόμοια ονόματα στο μήνυμα.
Όταν ο διακομιστής απορρίπτει, διακλαδώστε με βάση το reason
Όταν ο διακομιστής απορρίπτει ένα αίτημα, το SDK ρίχνει Pdf123Error με status, code, reason και problem.hint. Ένα code μπορεί να έχει πολλές τιμές reason, γι' αυτό ελέγξτε πρώτα το reason και πέστε πίσω στο code. Τρεις συνηθισμένες αποτυχίες, εκτελεσμένες τοπικά με την ίδια είσοδο, μοιάζουν έτσι:
| Είσοδος | status | code | reason | hint (το πρωτότυπο είναι στα αγγλικά) |
|---|---|---|---|---|
| Κρυπτογραφημένο PDF, χωρίς κωδικό | 400 | bad_request |
password_required |
Provide the document password, or unlock the PDF first |
| Λάθος κωδικός | 400 | bad_request |
wrong_password |
Check the password and try again |
| Δεν είναι PDF ή είναι κατεστραμμένο αρχείο | 400 | invalid_document |
invalid_pdf |
Upload a valid, undamaged PDF; you can try repairing it first |
import { Pdf123Client, Pdf123Error } from "@pdf123/sdk";
import { readFileInput } from "@pdf123/sdk/node";
const client = new Pdf123Client();
const input = await readFileInput("locked.pdf");
try {
await client.run("compress", { input });
} catch (error) {
if (!(error instanceof Pdf123Error)) throw error;
console.error(error.status, error.code, error.reason, error.problem?.hint);
if (error.reason === "password_required") {
await client.run("compress", { input, password: "secret" });
}
}
Όταν γνωρίζετε τον κωδικό, περάστε password για ένα μεμονωμένο αρχείο· η αποκρυπτογράφηση και το εργαλείο-στόχος γίνονται στο ίδιο αίτημα. Για κατεστραμμένο αρχείο, δοκιμάστε πρώτα την Επισκευή PDF.
Το «κανένα αποτέλεσμα» πρέπει να το ξεχωρίζετε. Όταν η PDF σε CSV δεν βρίσκει πίνακα στο PDF, ο διακομιστής επιστρέφει 204 και το SDK ρίχνει code: "no_content"· ο λόγος εξηγείται στο Κενή εξαγωγή πίνακα (204): το PDF σας μάλλον δεν έχει στήλες. Τα εργαλεία υπό συνθήκη φιλτραρίσματος αντιμετωπίζουν μια ψευδή συνθήκη ως φυσιολογικό αποτέλεσμα: δεν ρίχνουν σφάλμα και επιστρέφουν matched: false με κενό data.
Επαναλήψεις, μνήμη και ακύρωση είναι δουλειά του καλούντος
Τα σφάλματα δικτύου και οι απαντήσεις 5xx ρίχνονται ως έχουν· το SDK δεν επαναλαμβάνει ποτέ από μόνο του. Για ένα αίτημα που αλλάζει κατάσταση, περάστε δικό σας idempotencyKey: η επανάληψη με το ίδιο κλειδί επιστρέφει το πρώτο αποτέλεσμα αντί να επεξεργαστεί ξανά, και ο μηχανισμός και τα όρια περιγράφονται στο Idempotency-Key: ασφαλείς επαναλήψεις για εργασίες PDF. Τα αποτελέσματα διαβάζονται ολόκληρα στη μνήμη, χωρίς ροή. Όταν και η είσοδος και η έξοδος είναι μεγάλες, υπολογίστε τη μνήμη που καταλαμβάνουν ταυτόχρονα.
Η ακύρωση και το χρονικό όριο είναι δύο διαφορετικές τιμές του code. Μία κλήση αναφέρει μόνο εκείνο που συμβαίνει πρώτο. Το παρακάτω παράδειγμα τα δοκιμάζει σε δύο ξεχωριστές κλήσεις, ώστε να μπορούν πράγματι να εκτελεστούν και οι δύο κλάδοι:
import { Pdf123Client, Pdf123Error } from "@pdf123/sdk";
import { readFileInput } from "@pdf123/sdk/node";
const client = new Pdf123Client();
const input = await readFileInput("a.pdf");
const controller = new AbortController();
setTimeout(() => controller.abort(), 10_000);
try {
await client.run("compress", { input, signal: controller.signal });
} catch (error) {
if (error instanceof Pdf123Error && error.code === "cancelled") { /* το ακυρώσατε εσείς */ }
}
try {
await client.run("compress", { input, timeoutMs: 60_000 });
} catch (error) {
if (error instanceof Pdf123Error && error.code === "timeout") { /* ξεπεράστηκε το timeoutMs· αυτή τη φορά χωρίς signal */ }
}
Μόλις ενεργοποιηθεί το σήμα ακύρωσης (AbortSignal), το αίτημα απορρίπτεται με code: "cancelled"· η υπέρβαση του χρονικού ορίου δίνει code: "timeout". Κάθε αίτημα έχει προεπιλεγμένο χρονικό όριο 5 λεπτών, που μπορείτε να αλλάξετε όταν δημιουργείτε τον πελάτη ή να παρακάμψετε ανά κλήση με timeoutMs. Σε μεταφόρτωση σε τμήματα, κάθε αίτημα φέρει αυτό το χρονικό όριο ξεχωριστά. Η ακύρωση κλείνει μόνο αυτή τη σύνδεση· δεν υπάρχει εγγύηση ότι ο διακομιστής σταματά την επεξεργασία.
Ένα αρχείο αποτυγχάνει, τα υπόλοιπα συνεχίζουν, και οι επανακλήσεις φτάνουν με σειρά ολοκλήρωσης
Για μια παρτίδα εισόδων σε εργαλείο ενός αρχείου, χρησιμοποιήστε το runBatch. Αν ένα αρχείο αποτύχει, τα άλλα συνεχίζουν. Το onResult καλείται τη στιγμή που ολοκληρώνεται κάθε αρχείο, άρα εκτελείται με σειρά ολοκλήρωσης, και το index είναι η θέση του αρχείου στον πίνακα εισόδου· ο πίνακας που επιστρέφεται είναι πάντα με τη σειρά της εισόδου. Γράψτε τα bytes στον δίσκο μέσα σε αυτή την επανάκληση: το retainData: false αδειάζει το data μόλις επιστρέψει η επανάκληση, οπότε ένα αποτέλεσμα που δεν αποθηκεύετε εδώ με saveResult χάνεται. Αν η εκτέλεση διακοπεί, τα αρχεία που έχουν ήδη γραφτεί μένουν.
Υποθέστε ότι ο φάκελος έχει τρία καλά PDF, τα a.pdf, b.pdf και ένα κρυπτογραφημένο locked.pdf (κωδικός secret), καθώς και ένα χαλασμένο αρχείο broken.pdf που περιέχει μόνο μερικούς πληκτρολογημένους χαρακτήρες:
import { Pdf123Client } from "@pdf123/sdk";
import { fileSource, saveResult } from "@pdf123/sdk/node";
const client = new Pdf123Client();
const results = await client.runBatch("compress", [
fileSource("a.pdf"), fileSource("broken.pdf"), fileSource("b.pdf"), fileSource("locked.pdf"),
], {
concurrency: 2,
passwordFor: (file) => (file.name === "locked.pdf" ? "secret" : undefined),
onResult: async (entry, index) => {
console.log(index, entry.input.name, entry.ok ? "ok" : entry.error.reason);
if (entry.ok) await saveResult(entry.result, { output: "out/" });
},
retainData: false,
});
Από προεπιλογή επεξεργάζονται δύο αρχεία ταυτόχρονα· αλλάξτε το με το concurrency. Το fileSource(path) διαβάζει ένα αρχείο από τον δίσκο μόνο όταν έρθει η σειρά του, ώστε μια μεγάλη παρτίδα μεγάλων αρχείων να μη βρίσκεται ποτέ ολόκληρη στη μνήμη. Το passwordFor επιτρέπει σε μια παρτίδα που ανακατεύει κλειδωμένα και ξεκλείδωτα αρχεία να τρέξει μονομιάς. Εκτελώντας τοπικά τα τέσσερα παραπάνω αρχεία, η επανάκληση έλαβε:
1 broken.pdf invalid_pdf
0 a.pdf ok
2 b.pdf ok
3 locked.pdf ok
Η σειρά ολοκλήρωσης μπορεί να διαφέρει σε κάθε εκτέλεση· έτσι φάνηκε μία εκτέλεση. Το retainData: false κάνει κενό το data στον πίνακα που επιστρέφεται· τα επιτυχημένα αρχεία είχαν ήδη γραφτεί στο out/ από το saveResult παραπάνω. Όταν περνάτε idempotencyKey στο runBatch, το κλειδί που στέλνεται πραγματικά για κάθε αρχείο είναι <key>:<index>.
Η μεταφόρτωση σε τμήματα ξεκινά μόνο πάνω από 95 MiB συνολικά
Το σώμα ενός άμεσου αιτήματος περιορίζεται στα 100 MiB, και ό,τι είναι μεγαλύτερο απορρίπτεται· δείτε τις λεπτομέρειες στο Όταν απορρίπτεται μια μεγάλη μεταφόρτωση PDF: το όριο 100 MiB του σώματος αίτησης και το σφάλμα που σε οδηγεί σε λάθος κατεύθυνση. Όταν όλα τα αρχεία ενός αιτήματος αθροίζουν πάνω από 95 MiB, το SDK περνά σε μεταφόρτωση σε τμήματα, και η κλήση σας client.run δεν αλλάζει. Το προεπιλεγμένο μέγιστο του διακομιστή για μία μεταφόρτωση είναι 500 MiB· όταν φιλοξενείτε μόνοι σας, ρυθμίστε το με το PDFX_UPLOAD_MAX_BYTES.
Για την ίδια λειτουργία γραμμένη σε curl, MCP και γραμμή εντολών, δείτε το Ίδια λειτουργία, τέσσερις πελάτες: φυλλομετρητής, curl, MCP, pdfx: εκείνο το άρθρο βάζει τις τέσσερις κλήσεις δίπλα δίπλα, και αυτό αναπτύσσει μόνο το SDK. Η σελίδα του πακέτου στο npm είναι το @pdf123/sdk.