@uploadista/flow-documents-pdflib 0.0.16-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,16 @@
1
+
2
+ 
3
+ > @uploadista/flow-documents-pdflib@0.0.1 build /Users/denislaboureyras/Documents/uploadista/dev/uploadista-workspace/uploadista-sdk/packages/flow/documents/pdflib
4
+ > tsdown
5
+
6
+ ℹ tsdown v0.16.5 powered by rolldown v1.0.0-beta.50
7
+ ℹ entry: src/index.ts
8
+ ℹ tsconfig: tsconfig.json
9
+ ℹ Build start
10
+ ℹ Cleaning 4 files
11
+ ℹ dist/index.mjs  8.20 kB │ gzip: 1.87 kB
12
+ ℹ dist/index.mjs.map 16.18 kB │ gzip: 3.54 kB
13
+ ℹ dist/index.d.mts.map  0.33 kB │ gzip: 0.23 kB
14
+ ℹ dist/index.d.mts  0.83 kB │ gzip: 0.37 kB
15
+ ℹ 4 files, total: 25.54 kB
16
+ ✔ Build complete in 7949ms
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 uploadista
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,43 @@
1
+ # @uploadista/flow-documents-pdflib
2
+
3
+ pdf-lib-based DocumentPlugin implementation for Uploadista Flow.
4
+
5
+ ## Features
6
+
7
+ - Pure JavaScript implementation (no native dependencies)
8
+ - PDF splitting and merging
9
+ - Metadata extraction
10
+ - Works in both Node.js and browsers
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ pnpm add @uploadista/flow-documents-pdflib
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ ```typescript
21
+ import { PdfLibDocumentPluginLive } from "@uploadista/flow-documents-pdflib";
22
+ import { Effect, Layer } from "effect";
23
+
24
+ // Provide the plugin to your flow execution
25
+ const program = Effect.gen(function* () {
26
+ // Your flow logic here
27
+ }).pipe(Effect.provide(PdfLibDocumentPluginLive));
28
+ ```
29
+
30
+ ## Why pdf-lib?
31
+
32
+ - **Pure JavaScript**: No native binaries or external dependencies
33
+ - **Cross-platform**: Works in Node.js, browsers, and edge runtimes
34
+ - **Actively maintained**: MIT licensed with regular updates
35
+ - **Comprehensive**: Supports PDF creation, modification, and reading
36
+
37
+ ## Limitations
38
+
39
+ pdf-lib has limited text extraction capabilities. For text extraction from searchable PDFs, use the unpdf plugin instead.
40
+
41
+ ## License
42
+
43
+ MIT
@@ -0,0 +1,27 @@
1
+ import { DocumentPlugin } from "@uploadista/core/flow";
2
+ import { Layer } from "effect";
3
+
4
+ //#region src/document-plugin.d.ts
5
+ declare const pdfLibDocumentPlugin: Layer.Layer<DocumentPlugin, never, never>;
6
+ declare const PdfLibDocumentPluginLive: Layer.Layer<DocumentPlugin, never, never>;
7
+ //#endregion
8
+ //#region src/utils/format-mappings.d.ts
9
+ /**
10
+ * PDF MIME type constant
11
+ */
12
+ declare const PDF_MIME_TYPE = "application/pdf";
13
+ /**
14
+ * PDF file extension
15
+ */
16
+ declare const PDF_EXTENSION = ".pdf";
17
+ /**
18
+ * Get MIME type for PDF format
19
+ */
20
+ declare function getPdfMimeType(): string;
21
+ /**
22
+ * Get file extension for PDF format
23
+ */
24
+ declare function getPdfExtension(): string;
25
+ //#endregion
26
+ export { PDF_EXTENSION, PDF_MIME_TYPE, PdfLibDocumentPluginLive, getPdfExtension, getPdfMimeType, pdfLibDocumentPlugin };
27
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/document-plugin.ts","../src/utils/format-mappings.ts"],"sourcesContent":[],"mappings":";;;;cAiCa,sBAAoB,KAAA,CAAA,MAAA;cA4RpB,0BAAwB,KAAA,CAAA,MAAA;;;;;;cC1TxB,aAAA;AD8Bb;AA4RA;;cCrTa,aAAA;;AALb;AAKA;AAKgB,iBAAA,cAAA,CAAA,CAAc,EAAA,MAAA;AAO9B;;;iBAAgB,eAAA,CAAA"}
package/dist/index.mjs ADDED
@@ -0,0 +1,195 @@
1
+ import { UploadistaError } from "@uploadista/core/errors";
2
+ import { DocumentPlugin } from "@uploadista/core/flow";
3
+ import { Effect, Layer } from "effect";
4
+ import { PDFDocument } from "pdf-lib";
5
+
6
+ //#region src/document-plugin.ts
7
+ /**
8
+ * Helper to parse date from PDF date string format
9
+ */
10
+ function parsePdfDate(dateStr) {
11
+ if (!dateStr) return null;
12
+ try {
13
+ const match = dateStr.match(/D:(\d{4})(\d{2})(\d{2})(\d{2})?(\d{2})?(\d{2})?/);
14
+ if (!match) return null;
15
+ const [, year, month, day, hour = "00", minute = "00", second = "00"] = match;
16
+ return (/* @__PURE__ */ new Date(`${year}-${month}-${day}T${hour}:${minute}:${second}Z`)).toISOString();
17
+ } catch {
18
+ return null;
19
+ }
20
+ }
21
+ const pdfLibDocumentPlugin = Layer.succeed(DocumentPlugin, DocumentPlugin.of({
22
+ extractText: (_input) => {
23
+ return Effect.gen(function* () {
24
+ return yield* UploadistaError.fromCode("DOCUMENT_PROCESSING_FAILED", { cause: "pdf-lib does not support text extraction. Use @uploadista/flow-documents-unpdf instead." }).toEffect();
25
+ });
26
+ },
27
+ getMetadata: (input) => {
28
+ return Effect.gen(function* () {
29
+ const pdfDoc = yield* Effect.tryPromise({
30
+ try: async () => await PDFDocument.load(input, { ignoreEncryption: false }),
31
+ catch: (error) => {
32
+ const errorMessage = error instanceof Error ? error.message : String(error);
33
+ if (errorMessage.toLowerCase().includes("encrypt")) return UploadistaError.fromCode("PDF_ENCRYPTED", { cause: errorMessage });
34
+ if (errorMessage.toLowerCase().includes("corrupt") || errorMessage.toLowerCase().includes("invalid")) return UploadistaError.fromCode("PDF_CORRUPTED", { cause: errorMessage });
35
+ return UploadistaError.fromCode("DOCUMENT_PROCESSING_FAILED", { cause: errorMessage });
36
+ }
37
+ });
38
+ const pageCount = pdfDoc.getPageCount();
39
+ const author = pdfDoc.getAuthor() || null;
40
+ const title = pdfDoc.getTitle() || null;
41
+ const subject = pdfDoc.getSubject() || null;
42
+ const creator = pdfDoc.getCreator() || null;
43
+ const creationDateStr = pdfDoc.getCreationDate();
44
+ const modificationDateStr = pdfDoc.getModificationDate();
45
+ return {
46
+ pageCount,
47
+ format: "pdf",
48
+ author,
49
+ title,
50
+ subject,
51
+ creator,
52
+ creationDate: creationDateStr ? parsePdfDate(creationDateStr.toString()) : null,
53
+ modifiedDate: modificationDateStr ? parsePdfDate(modificationDateStr.toString()) : null,
54
+ fileSize: input.byteLength
55
+ };
56
+ });
57
+ },
58
+ splitPdf: (input, options) => {
59
+ return Effect.gen(function* () {
60
+ const pdfDoc = yield* Effect.tryPromise({
61
+ try: async () => await PDFDocument.load(input, { ignoreEncryption: false }),
62
+ catch: (error) => {
63
+ const errorMessage = error instanceof Error ? error.message : String(error);
64
+ if (errorMessage.toLowerCase().includes("encrypt")) return UploadistaError.fromCode("PDF_ENCRYPTED", { cause: errorMessage });
65
+ return UploadistaError.fromCode("DOCUMENT_PROCESSING_FAILED", { cause: errorMessage });
66
+ }
67
+ });
68
+ const totalPages = pdfDoc.getPageCount();
69
+ if (options.mode === "individual") {
70
+ const pdfs = [];
71
+ for (let i = 0; i < totalPages; i++) {
72
+ const newPdf$1 = yield* Effect.tryPromise({
73
+ try: async () => await PDFDocument.create(),
74
+ catch: (error) => {
75
+ return UploadistaError.fromCode("DOCUMENT_PROCESSING_FAILED", { cause: error instanceof Error ? error.message : String(error) });
76
+ }
77
+ });
78
+ const [copiedPage] = yield* Effect.tryPromise({
79
+ try: async () => await newPdf$1.copyPages(pdfDoc, [i]),
80
+ catch: (error) => {
81
+ return UploadistaError.fromCode("DOCUMENT_PROCESSING_FAILED", { cause: error instanceof Error ? error.message : String(error) });
82
+ }
83
+ });
84
+ newPdf$1.addPage(copiedPage);
85
+ const pdfBytes$1 = yield* Effect.tryPromise({
86
+ try: async () => await newPdf$1.save(),
87
+ catch: (error) => {
88
+ return UploadistaError.fromCode("DOCUMENT_PROCESSING_FAILED", { cause: error instanceof Error ? error.message : String(error) });
89
+ }
90
+ });
91
+ pdfs.push(new Uint8Array(pdfBytes$1));
92
+ }
93
+ return {
94
+ mode: "individual",
95
+ pdfs
96
+ };
97
+ }
98
+ if (!options.startPage || !options.endPage) return yield* UploadistaError.fromCode("PAGE_RANGE_INVALID", { cause: "startPage and endPage are required for range mode" }).toEffect();
99
+ if (options.startPage < 1 || options.endPage > totalPages || options.startPage > options.endPage) return yield* UploadistaError.fromCode("PAGE_RANGE_INVALID", { cause: `Invalid page range: ${options.startPage}-${options.endPage}. Document has ${totalPages} pages.` }).toEffect();
100
+ const newPdf = yield* Effect.tryPromise({
101
+ try: async () => await PDFDocument.create(),
102
+ catch: (error) => {
103
+ return UploadistaError.fromCode("DOCUMENT_PROCESSING_FAILED", { cause: error instanceof Error ? error.message : String(error) });
104
+ }
105
+ });
106
+ const pageIndices = Array.from({ length: options.endPage - options.startPage + 1 }, (_, i) => (options.startPage ?? 1) - 1 + i);
107
+ const copiedPages = yield* Effect.tryPromise({
108
+ try: async () => await newPdf.copyPages(pdfDoc, pageIndices),
109
+ catch: (error) => {
110
+ return UploadistaError.fromCode("DOCUMENT_PROCESSING_FAILED", { cause: error instanceof Error ? error.message : String(error) });
111
+ }
112
+ });
113
+ for (const page of copiedPages) newPdf.addPage(page);
114
+ const pdfBytes = yield* Effect.tryPromise({
115
+ try: async () => await newPdf.save(),
116
+ catch: (error) => {
117
+ return UploadistaError.fromCode("DOCUMENT_PROCESSING_FAILED", { cause: error instanceof Error ? error.message : String(error) });
118
+ }
119
+ });
120
+ return {
121
+ mode: "range",
122
+ pdf: new Uint8Array(pdfBytes)
123
+ };
124
+ });
125
+ },
126
+ mergePdfs: (options) => {
127
+ return Effect.gen(function* () {
128
+ if (options.pdfs.length === 0) return yield* UploadistaError.fromCode("DOCUMENT_PROCESSING_FAILED", { cause: "At least one PDF is required for merging" }).toEffect();
129
+ if (options.pdfs.length === 1) {
130
+ yield* Effect.logWarning("Only one PDF provided for merging, returning original");
131
+ return options.pdfs[0];
132
+ }
133
+ const mergedPdf = yield* Effect.tryPromise({
134
+ try: async () => await PDFDocument.create(),
135
+ catch: (error) => {
136
+ return UploadistaError.fromCode("DOCUMENT_PROCESSING_FAILED", { cause: error instanceof Error ? error.message : String(error) });
137
+ }
138
+ });
139
+ for (const pdfBytes of options.pdfs) {
140
+ const pdf = yield* Effect.tryPromise({
141
+ try: async () => await PDFDocument.load(pdfBytes, { ignoreEncryption: false }),
142
+ catch: (error) => {
143
+ const errorMessage = error instanceof Error ? error.message : String(error);
144
+ if (errorMessage.toLowerCase().includes("encrypt")) return UploadistaError.fromCode("PDF_ENCRYPTED", { cause: errorMessage });
145
+ return UploadistaError.fromCode("DOCUMENT_PROCESSING_FAILED", { cause: errorMessage });
146
+ }
147
+ });
148
+ const pageCount = pdf.getPageCount();
149
+ const pageIndices = Array.from({ length: pageCount }, (_, i) => i);
150
+ const copiedPages = yield* Effect.tryPromise({
151
+ try: async () => await mergedPdf.copyPages(pdf, pageIndices),
152
+ catch: (error) => {
153
+ return UploadistaError.fromCode("DOCUMENT_PROCESSING_FAILED", { cause: error instanceof Error ? error.message : String(error) });
154
+ }
155
+ });
156
+ for (const page of copiedPages) mergedPdf.addPage(page);
157
+ }
158
+ const mergedBytes = yield* Effect.tryPromise({
159
+ try: async () => await mergedPdf.save(),
160
+ catch: (error) => {
161
+ return UploadistaError.fromCode("DOCUMENT_PROCESSING_FAILED", { cause: error instanceof Error ? error.message : String(error) });
162
+ }
163
+ });
164
+ return new Uint8Array(mergedBytes);
165
+ });
166
+ }
167
+ }));
168
+ const PdfLibDocumentPluginLive = pdfLibDocumentPlugin;
169
+
170
+ //#endregion
171
+ //#region src/utils/format-mappings.ts
172
+ /**
173
+ * PDF MIME type constant
174
+ */
175
+ const PDF_MIME_TYPE = "application/pdf";
176
+ /**
177
+ * PDF file extension
178
+ */
179
+ const PDF_EXTENSION = ".pdf";
180
+ /**
181
+ * Get MIME type for PDF format
182
+ */
183
+ function getPdfMimeType() {
184
+ return PDF_MIME_TYPE;
185
+ }
186
+ /**
187
+ * Get file extension for PDF format
188
+ */
189
+ function getPdfExtension() {
190
+ return PDF_EXTENSION;
191
+ }
192
+
193
+ //#endregion
194
+ export { PDF_EXTENSION, PDF_MIME_TYPE, PdfLibDocumentPluginLive, getPdfExtension, getPdfMimeType, pdfLibDocumentPlugin };
195
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":["pdfs: Uint8Array[]","newPdf","pdfBytes"],"sources":["../src/document-plugin.ts","../src/utils/format-mappings.ts"],"sourcesContent":["import { UploadistaError } from \"@uploadista/core/errors\";\nimport {\n type DocumentMetadata,\n DocumentPlugin,\n type SplitPdfResult,\n} from \"@uploadista/core/flow\";\nimport { Effect, Layer } from \"effect\";\nimport { PDFDocument } from \"pdf-lib\";\n\n/**\n * Helper to parse date from PDF date string format\n */\nfunction parsePdfDate(dateStr: string | undefined): string | null {\n if (!dateStr) return null;\n try {\n // PDF date format: D:YYYYMMDDHHmmSSOHH'mm'\n // Example: D:20230101120000Z\n const match = dateStr.match(\n /D:(\\d{4})(\\d{2})(\\d{2})(\\d{2})?(\\d{2})?(\\d{2})?/,\n );\n if (!match) return null;\n\n const [, year, month, day, hour = \"00\", minute = \"00\", second = \"00\"] =\n match;\n const date = new Date(\n `${year}-${month}-${day}T${hour}:${minute}:${second}Z`,\n );\n return date.toISOString();\n } catch {\n return null;\n }\n}\n\nexport const pdfLibDocumentPlugin = Layer.succeed(\n DocumentPlugin,\n DocumentPlugin.of({\n extractText: (_input) => {\n return Effect.gen(function* () {\n // pdf-lib has very limited text extraction capabilities\n // Return an error indicating that unpdf should be used instead\n return yield* UploadistaError.fromCode(\"DOCUMENT_PROCESSING_FAILED\", {\n cause:\n \"pdf-lib does not support text extraction. Use @uploadista/flow-documents-unpdf instead.\",\n }).toEffect();\n });\n },\n\n getMetadata: (input) => {\n return Effect.gen(function* () {\n const pdfDoc = yield* Effect.tryPromise({\n try: async () =>\n await PDFDocument.load(input, { ignoreEncryption: false }),\n catch: (error) => {\n const errorMessage =\n error instanceof Error ? error.message : String(error);\n\n if (errorMessage.toLowerCase().includes(\"encrypt\")) {\n return UploadistaError.fromCode(\"PDF_ENCRYPTED\", {\n cause: errorMessage,\n });\n }\n\n if (\n errorMessage.toLowerCase().includes(\"corrupt\") ||\n errorMessage.toLowerCase().includes(\"invalid\")\n ) {\n return UploadistaError.fromCode(\"PDF_CORRUPTED\", {\n cause: errorMessage,\n });\n }\n\n return UploadistaError.fromCode(\"DOCUMENT_PROCESSING_FAILED\", {\n cause: errorMessage,\n });\n },\n });\n\n const pageCount = pdfDoc.getPageCount();\n const author = pdfDoc.getAuthor() || null;\n const title = pdfDoc.getTitle() || null;\n const subject = pdfDoc.getSubject() || null;\n const creator = pdfDoc.getCreator() || null;\n const creationDateStr = pdfDoc.getCreationDate();\n const modificationDateStr = pdfDoc.getModificationDate();\n\n const creationDate = creationDateStr\n ? parsePdfDate(creationDateStr.toString())\n : null;\n const modifiedDate = modificationDateStr\n ? parsePdfDate(modificationDateStr.toString())\n : null;\n\n const metadata: DocumentMetadata = {\n pageCount,\n format: \"pdf\",\n author,\n title,\n subject,\n creator,\n creationDate,\n modifiedDate,\n fileSize: input.byteLength,\n };\n\n return metadata;\n });\n },\n\n splitPdf: (input, options) => {\n return Effect.gen(function* () {\n const pdfDoc = yield* Effect.tryPromise({\n try: async () =>\n await PDFDocument.load(input, { ignoreEncryption: false }),\n catch: (error) => {\n const errorMessage =\n error instanceof Error ? error.message : String(error);\n\n if (errorMessage.toLowerCase().includes(\"encrypt\")) {\n return UploadistaError.fromCode(\"PDF_ENCRYPTED\", {\n cause: errorMessage,\n });\n }\n\n return UploadistaError.fromCode(\"DOCUMENT_PROCESSING_FAILED\", {\n cause: errorMessage,\n });\n },\n });\n\n const totalPages = pdfDoc.getPageCount();\n\n if (options.mode === \"individual\") {\n // Split into individual pages\n const pdfs: Uint8Array[] = [];\n\n for (let i = 0; i < totalPages; i++) {\n const newPdf = yield* Effect.tryPromise({\n try: async () => await PDFDocument.create(),\n catch: (error) => {\n return UploadistaError.fromCode(\"DOCUMENT_PROCESSING_FAILED\", {\n cause: error instanceof Error ? error.message : String(error),\n });\n },\n });\n\n const [copiedPage] = yield* Effect.tryPromise({\n try: async () => await newPdf.copyPages(pdfDoc, [i]),\n catch: (error) => {\n return UploadistaError.fromCode(\"DOCUMENT_PROCESSING_FAILED\", {\n cause: error instanceof Error ? error.message : String(error),\n });\n },\n });\n\n newPdf.addPage(copiedPage);\n\n const pdfBytes = yield* Effect.tryPromise({\n try: async () => await newPdf.save(),\n catch: (error) => {\n return UploadistaError.fromCode(\"DOCUMENT_PROCESSING_FAILED\", {\n cause: error instanceof Error ? error.message : String(error),\n });\n },\n });\n\n pdfs.push(new Uint8Array(pdfBytes));\n }\n\n const result: SplitPdfResult = {\n mode: \"individual\",\n pdfs,\n };\n\n return result;\n }\n\n // Range mode\n if (!options.startPage || !options.endPage) {\n return yield* UploadistaError.fromCode(\"PAGE_RANGE_INVALID\", {\n cause: \"startPage and endPage are required for range mode\",\n }).toEffect();\n }\n\n // Validate page range (1-indexed)\n if (\n options.startPage < 1 ||\n options.endPage > totalPages ||\n options.startPage > options.endPage\n ) {\n return yield* UploadistaError.fromCode(\"PAGE_RANGE_INVALID\", {\n cause: `Invalid page range: ${options.startPage}-${options.endPage}. Document has ${totalPages} pages.`,\n }).toEffect();\n }\n\n const newPdf = yield* Effect.tryPromise({\n try: async () => await PDFDocument.create(),\n catch: (error) => {\n return UploadistaError.fromCode(\"DOCUMENT_PROCESSING_FAILED\", {\n cause: error instanceof Error ? error.message : String(error),\n });\n },\n });\n\n // Convert from 1-indexed to 0-indexed\n const pageIndices = Array.from(\n { length: options.endPage - options.startPage + 1 },\n (_, i) => (options.startPage ?? 1) - 1 + i,\n );\n\n const copiedPages = yield* Effect.tryPromise({\n try: async () => await newPdf.copyPages(pdfDoc, pageIndices),\n catch: (error) => {\n return UploadistaError.fromCode(\"DOCUMENT_PROCESSING_FAILED\", {\n cause: error instanceof Error ? error.message : String(error),\n });\n },\n });\n\n for (const page of copiedPages) {\n newPdf.addPage(page);\n }\n\n const pdfBytes = yield* Effect.tryPromise({\n try: async () => await newPdf.save(),\n catch: (error) => {\n return UploadistaError.fromCode(\"DOCUMENT_PROCESSING_FAILED\", {\n cause: error instanceof Error ? error.message : String(error),\n });\n },\n });\n\n const result: SplitPdfResult = {\n mode: \"range\",\n pdf: new Uint8Array(pdfBytes),\n };\n\n return result;\n });\n },\n\n mergePdfs: (options) => {\n return Effect.gen(function* () {\n if (options.pdfs.length === 0) {\n return yield* UploadistaError.fromCode(\"DOCUMENT_PROCESSING_FAILED\", {\n cause: \"At least one PDF is required for merging\",\n }).toEffect();\n }\n\n if (options.pdfs.length === 1) {\n // Single PDF, just return it\n yield* Effect.logWarning(\n \"Only one PDF provided for merging, returning original\",\n );\n return options.pdfs[0];\n }\n\n const mergedPdf = yield* Effect.tryPromise({\n try: async () => await PDFDocument.create(),\n catch: (error) => {\n return UploadistaError.fromCode(\"DOCUMENT_PROCESSING_FAILED\", {\n cause: error instanceof Error ? error.message : String(error),\n });\n },\n });\n\n for (const pdfBytes of options.pdfs) {\n const pdf = yield* Effect.tryPromise({\n try: async () =>\n await PDFDocument.load(pdfBytes, { ignoreEncryption: false }),\n catch: (error) => {\n const errorMessage =\n error instanceof Error ? error.message : String(error);\n\n if (errorMessage.toLowerCase().includes(\"encrypt\")) {\n return UploadistaError.fromCode(\"PDF_ENCRYPTED\", {\n cause: errorMessage,\n });\n }\n\n return UploadistaError.fromCode(\"DOCUMENT_PROCESSING_FAILED\", {\n cause: errorMessage,\n });\n },\n });\n\n const pageCount = pdf.getPageCount();\n const pageIndices = Array.from({ length: pageCount }, (_, i) => i);\n\n const copiedPages = yield* Effect.tryPromise({\n try: async () => await mergedPdf.copyPages(pdf, pageIndices),\n catch: (error) => {\n return UploadistaError.fromCode(\"DOCUMENT_PROCESSING_FAILED\", {\n cause: error instanceof Error ? error.message : String(error),\n });\n },\n });\n\n for (const page of copiedPages) {\n mergedPdf.addPage(page);\n }\n }\n\n const mergedBytes = yield* Effect.tryPromise({\n try: async () => await mergedPdf.save(),\n catch: (error) => {\n return UploadistaError.fromCode(\"DOCUMENT_PROCESSING_FAILED\", {\n cause: error instanceof Error ? error.message : String(error),\n });\n },\n });\n\n return new Uint8Array(mergedBytes);\n });\n },\n }),\n);\n\nexport const PdfLibDocumentPluginLive = pdfLibDocumentPlugin;\n","/**\n * PDF MIME type constant\n */\nexport const PDF_MIME_TYPE = \"application/pdf\";\n\n/**\n * PDF file extension\n */\nexport const PDF_EXTENSION = \".pdf\";\n\n/**\n * Get MIME type for PDF format\n */\nexport function getPdfMimeType(): string {\n return PDF_MIME_TYPE;\n}\n\n/**\n * Get file extension for PDF format\n */\nexport function getPdfExtension(): string {\n return PDF_EXTENSION;\n}\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@uploadista/flow-documents-pdflib",
3
+ "type": "module",
4
+ "version": "0.0.16-beta.2",
5
+ "description": "pdf-lib plugin for Uploadista document processing",
6
+ "license": "MIT",
7
+ "author": "Uploadista",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.mts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.cjs",
13
+ "default": "./dist/index.mjs"
14
+ }
15
+ },
16
+ "dependencies": {
17
+ "effect": "3.19.4",
18
+ "pdf-lib": "^1.17.1",
19
+ "@uploadista/core": "0.0.16-beta.2"
20
+ },
21
+ "devDependencies": {
22
+ "@types/node": "24.10.1",
23
+ "tsdown": "0.16.5",
24
+ "@uploadista/typescript-config": "0.0.16-beta.2"
25
+ },
26
+ "scripts": {
27
+ "build": "tsdown",
28
+ "format": "biome format --write ./src",
29
+ "lint": "biome lint --write ./src",
30
+ "check": "biome check --write ./src"
31
+ }
32
+ }
@@ -0,0 +1,318 @@
1
+ import { UploadistaError } from "@uploadista/core/errors";
2
+ import {
3
+ type DocumentMetadata,
4
+ DocumentPlugin,
5
+ type SplitPdfResult,
6
+ } from "@uploadista/core/flow";
7
+ import { Effect, Layer } from "effect";
8
+ import { PDFDocument } from "pdf-lib";
9
+
10
+ /**
11
+ * Helper to parse date from PDF date string format
12
+ */
13
+ function parsePdfDate(dateStr: string | undefined): string | null {
14
+ if (!dateStr) return null;
15
+ try {
16
+ // PDF date format: D:YYYYMMDDHHmmSSOHH'mm'
17
+ // Example: D:20230101120000Z
18
+ const match = dateStr.match(
19
+ /D:(\d{4})(\d{2})(\d{2})(\d{2})?(\d{2})?(\d{2})?/,
20
+ );
21
+ if (!match) return null;
22
+
23
+ const [, year, month, day, hour = "00", minute = "00", second = "00"] =
24
+ match;
25
+ const date = new Date(
26
+ `${year}-${month}-${day}T${hour}:${minute}:${second}Z`,
27
+ );
28
+ return date.toISOString();
29
+ } catch {
30
+ return null;
31
+ }
32
+ }
33
+
34
+ export const pdfLibDocumentPlugin = Layer.succeed(
35
+ DocumentPlugin,
36
+ DocumentPlugin.of({
37
+ extractText: (_input) => {
38
+ return Effect.gen(function* () {
39
+ // pdf-lib has very limited text extraction capabilities
40
+ // Return an error indicating that unpdf should be used instead
41
+ return yield* UploadistaError.fromCode("DOCUMENT_PROCESSING_FAILED", {
42
+ cause:
43
+ "pdf-lib does not support text extraction. Use @uploadista/flow-documents-unpdf instead.",
44
+ }).toEffect();
45
+ });
46
+ },
47
+
48
+ getMetadata: (input) => {
49
+ return Effect.gen(function* () {
50
+ const pdfDoc = yield* Effect.tryPromise({
51
+ try: async () =>
52
+ await PDFDocument.load(input, { ignoreEncryption: false }),
53
+ catch: (error) => {
54
+ const errorMessage =
55
+ error instanceof Error ? error.message : String(error);
56
+
57
+ if (errorMessage.toLowerCase().includes("encrypt")) {
58
+ return UploadistaError.fromCode("PDF_ENCRYPTED", {
59
+ cause: errorMessage,
60
+ });
61
+ }
62
+
63
+ if (
64
+ errorMessage.toLowerCase().includes("corrupt") ||
65
+ errorMessage.toLowerCase().includes("invalid")
66
+ ) {
67
+ return UploadistaError.fromCode("PDF_CORRUPTED", {
68
+ cause: errorMessage,
69
+ });
70
+ }
71
+
72
+ return UploadistaError.fromCode("DOCUMENT_PROCESSING_FAILED", {
73
+ cause: errorMessage,
74
+ });
75
+ },
76
+ });
77
+
78
+ const pageCount = pdfDoc.getPageCount();
79
+ const author = pdfDoc.getAuthor() || null;
80
+ const title = pdfDoc.getTitle() || null;
81
+ const subject = pdfDoc.getSubject() || null;
82
+ const creator = pdfDoc.getCreator() || null;
83
+ const creationDateStr = pdfDoc.getCreationDate();
84
+ const modificationDateStr = pdfDoc.getModificationDate();
85
+
86
+ const creationDate = creationDateStr
87
+ ? parsePdfDate(creationDateStr.toString())
88
+ : null;
89
+ const modifiedDate = modificationDateStr
90
+ ? parsePdfDate(modificationDateStr.toString())
91
+ : null;
92
+
93
+ const metadata: DocumentMetadata = {
94
+ pageCount,
95
+ format: "pdf",
96
+ author,
97
+ title,
98
+ subject,
99
+ creator,
100
+ creationDate,
101
+ modifiedDate,
102
+ fileSize: input.byteLength,
103
+ };
104
+
105
+ return metadata;
106
+ });
107
+ },
108
+
109
+ splitPdf: (input, options) => {
110
+ return Effect.gen(function* () {
111
+ const pdfDoc = yield* Effect.tryPromise({
112
+ try: async () =>
113
+ await PDFDocument.load(input, { ignoreEncryption: false }),
114
+ catch: (error) => {
115
+ const errorMessage =
116
+ error instanceof Error ? error.message : String(error);
117
+
118
+ if (errorMessage.toLowerCase().includes("encrypt")) {
119
+ return UploadistaError.fromCode("PDF_ENCRYPTED", {
120
+ cause: errorMessage,
121
+ });
122
+ }
123
+
124
+ return UploadistaError.fromCode("DOCUMENT_PROCESSING_FAILED", {
125
+ cause: errorMessage,
126
+ });
127
+ },
128
+ });
129
+
130
+ const totalPages = pdfDoc.getPageCount();
131
+
132
+ if (options.mode === "individual") {
133
+ // Split into individual pages
134
+ const pdfs: Uint8Array[] = [];
135
+
136
+ for (let i = 0; i < totalPages; i++) {
137
+ const newPdf = yield* Effect.tryPromise({
138
+ try: async () => await PDFDocument.create(),
139
+ catch: (error) => {
140
+ return UploadistaError.fromCode("DOCUMENT_PROCESSING_FAILED", {
141
+ cause: error instanceof Error ? error.message : String(error),
142
+ });
143
+ },
144
+ });
145
+
146
+ const [copiedPage] = yield* Effect.tryPromise({
147
+ try: async () => await newPdf.copyPages(pdfDoc, [i]),
148
+ catch: (error) => {
149
+ return UploadistaError.fromCode("DOCUMENT_PROCESSING_FAILED", {
150
+ cause: error instanceof Error ? error.message : String(error),
151
+ });
152
+ },
153
+ });
154
+
155
+ newPdf.addPage(copiedPage);
156
+
157
+ const pdfBytes = yield* Effect.tryPromise({
158
+ try: async () => await newPdf.save(),
159
+ catch: (error) => {
160
+ return UploadistaError.fromCode("DOCUMENT_PROCESSING_FAILED", {
161
+ cause: error instanceof Error ? error.message : String(error),
162
+ });
163
+ },
164
+ });
165
+
166
+ pdfs.push(new Uint8Array(pdfBytes));
167
+ }
168
+
169
+ const result: SplitPdfResult = {
170
+ mode: "individual",
171
+ pdfs,
172
+ };
173
+
174
+ return result;
175
+ }
176
+
177
+ // Range mode
178
+ if (!options.startPage || !options.endPage) {
179
+ return yield* UploadistaError.fromCode("PAGE_RANGE_INVALID", {
180
+ cause: "startPage and endPage are required for range mode",
181
+ }).toEffect();
182
+ }
183
+
184
+ // Validate page range (1-indexed)
185
+ if (
186
+ options.startPage < 1 ||
187
+ options.endPage > totalPages ||
188
+ options.startPage > options.endPage
189
+ ) {
190
+ return yield* UploadistaError.fromCode("PAGE_RANGE_INVALID", {
191
+ cause: `Invalid page range: ${options.startPage}-${options.endPage}. Document has ${totalPages} pages.`,
192
+ }).toEffect();
193
+ }
194
+
195
+ const newPdf = yield* Effect.tryPromise({
196
+ try: async () => await PDFDocument.create(),
197
+ catch: (error) => {
198
+ return UploadistaError.fromCode("DOCUMENT_PROCESSING_FAILED", {
199
+ cause: error instanceof Error ? error.message : String(error),
200
+ });
201
+ },
202
+ });
203
+
204
+ // Convert from 1-indexed to 0-indexed
205
+ const pageIndices = Array.from(
206
+ { length: options.endPage - options.startPage + 1 },
207
+ (_, i) => (options.startPage ?? 1) - 1 + i,
208
+ );
209
+
210
+ const copiedPages = yield* Effect.tryPromise({
211
+ try: async () => await newPdf.copyPages(pdfDoc, pageIndices),
212
+ catch: (error) => {
213
+ return UploadistaError.fromCode("DOCUMENT_PROCESSING_FAILED", {
214
+ cause: error instanceof Error ? error.message : String(error),
215
+ });
216
+ },
217
+ });
218
+
219
+ for (const page of copiedPages) {
220
+ newPdf.addPage(page);
221
+ }
222
+
223
+ const pdfBytes = yield* Effect.tryPromise({
224
+ try: async () => await newPdf.save(),
225
+ catch: (error) => {
226
+ return UploadistaError.fromCode("DOCUMENT_PROCESSING_FAILED", {
227
+ cause: error instanceof Error ? error.message : String(error),
228
+ });
229
+ },
230
+ });
231
+
232
+ const result: SplitPdfResult = {
233
+ mode: "range",
234
+ pdf: new Uint8Array(pdfBytes),
235
+ };
236
+
237
+ return result;
238
+ });
239
+ },
240
+
241
+ mergePdfs: (options) => {
242
+ return Effect.gen(function* () {
243
+ if (options.pdfs.length === 0) {
244
+ return yield* UploadistaError.fromCode("DOCUMENT_PROCESSING_FAILED", {
245
+ cause: "At least one PDF is required for merging",
246
+ }).toEffect();
247
+ }
248
+
249
+ if (options.pdfs.length === 1) {
250
+ // Single PDF, just return it
251
+ yield* Effect.logWarning(
252
+ "Only one PDF provided for merging, returning original",
253
+ );
254
+ return options.pdfs[0];
255
+ }
256
+
257
+ const mergedPdf = yield* Effect.tryPromise({
258
+ try: async () => await PDFDocument.create(),
259
+ catch: (error) => {
260
+ return UploadistaError.fromCode("DOCUMENT_PROCESSING_FAILED", {
261
+ cause: error instanceof Error ? error.message : String(error),
262
+ });
263
+ },
264
+ });
265
+
266
+ for (const pdfBytes of options.pdfs) {
267
+ const pdf = yield* Effect.tryPromise({
268
+ try: async () =>
269
+ await PDFDocument.load(pdfBytes, { ignoreEncryption: false }),
270
+ catch: (error) => {
271
+ const errorMessage =
272
+ error instanceof Error ? error.message : String(error);
273
+
274
+ if (errorMessage.toLowerCase().includes("encrypt")) {
275
+ return UploadistaError.fromCode("PDF_ENCRYPTED", {
276
+ cause: errorMessage,
277
+ });
278
+ }
279
+
280
+ return UploadistaError.fromCode("DOCUMENT_PROCESSING_FAILED", {
281
+ cause: errorMessage,
282
+ });
283
+ },
284
+ });
285
+
286
+ const pageCount = pdf.getPageCount();
287
+ const pageIndices = Array.from({ length: pageCount }, (_, i) => i);
288
+
289
+ const copiedPages = yield* Effect.tryPromise({
290
+ try: async () => await mergedPdf.copyPages(pdf, pageIndices),
291
+ catch: (error) => {
292
+ return UploadistaError.fromCode("DOCUMENT_PROCESSING_FAILED", {
293
+ cause: error instanceof Error ? error.message : String(error),
294
+ });
295
+ },
296
+ });
297
+
298
+ for (const page of copiedPages) {
299
+ mergedPdf.addPage(page);
300
+ }
301
+ }
302
+
303
+ const mergedBytes = yield* Effect.tryPromise({
304
+ try: async () => await mergedPdf.save(),
305
+ catch: (error) => {
306
+ return UploadistaError.fromCode("DOCUMENT_PROCESSING_FAILED", {
307
+ cause: error instanceof Error ? error.message : String(error),
308
+ });
309
+ },
310
+ });
311
+
312
+ return new Uint8Array(mergedBytes);
313
+ });
314
+ },
315
+ }),
316
+ );
317
+
318
+ export const PdfLibDocumentPluginLive = pdfLibDocumentPlugin;
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { pdfLibDocumentPlugin, PdfLibDocumentPluginLive } from "./document-plugin";
2
+ export * from "./utils/format-mappings";
@@ -0,0 +1,23 @@
1
+ /**
2
+ * PDF MIME type constant
3
+ */
4
+ export const PDF_MIME_TYPE = "application/pdf";
5
+
6
+ /**
7
+ * PDF file extension
8
+ */
9
+ export const PDF_EXTENSION = ".pdf";
10
+
11
+ /**
12
+ * Get MIME type for PDF format
13
+ */
14
+ export function getPdfMimeType(): string {
15
+ return PDF_MIME_TYPE;
16
+ }
17
+
18
+ /**
19
+ * Get file extension for PDF format
20
+ */
21
+ export function getPdfExtension(): string {
22
+ return PDF_EXTENSION;
23
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "extends": "@uploadista/typescript-config/server.json",
3
+ "compilerOptions": {
4
+ "baseUrl": "./",
5
+ "paths": {
6
+ "@/*": ["./src/*"]
7
+ },
8
+ "outDir": "./dist",
9
+ "rootDir": "./src",
10
+ "lib": ["ESNext", "DOM", "DOM.Iterable"],
11
+ "types": []
12
+ },
13
+ "include": ["src"]
14
+ }