@happyvertical/documents 0.74.10 → 0.75.0
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.
- package/README.md +2 -2
- package/dist/factory.d.ts.map +1 -1
- package/dist/index.js +6 -4
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +7 -5
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -85,8 +85,8 @@ Main factory function. Detects document format, selects the appropriate processo
|
|
|
85
85
|
| `cacheDir` | `string` | OS temp dir | Directory for caching downloaded files |
|
|
86
86
|
| `cache` | `boolean` | `true` | Enable/disable spider fetch caching |
|
|
87
87
|
| `cacheExpiry` | `number` | `300000` | Cache expiry in milliseconds |
|
|
88
|
-
| `scraper` | `'basic' \| 'crawlee'` | `'basic'` | Scraper
|
|
89
|
-
| `spider` | `'simple' \| 'dom' \| 'crawlee'` | `'dom'` | Spider adapter for fetching web pages |
|
|
88
|
+
| `scraper` | `'basic' \| 'tree' \| 'crawlee'` | `'basic'` | Scraper strategy for content extraction. `crawlee` is a deprecated alias for `spider: 'crawlee'` with basic scraping. |
|
|
89
|
+
| `spider` | `'simple' \| 'dom' \| 'crawlee' \| 'crawl4ai'` | `'dom'` | Spider adapter for fetching web pages |
|
|
90
90
|
| `headers` | `Record<string, string>` | — | Custom HTTP headers for spider requests |
|
|
91
91
|
| `timeout` | `number` | `30000` | Request timeout in milliseconds |
|
|
92
92
|
| `maxDuration` | `number` | — | Max scraping time in milliseconds |
|
package/dist/factory.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../src/factory.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAO9D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,QAAQ,CAAC,
|
|
1
|
+
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../src/factory.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAO9D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,QAAQ,CAAC,CAgFnB;AAED,eAAe,aAAa,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -270,9 +270,11 @@ async function fetchDocument(url, options = {}) {
|
|
|
270
270
|
const isWebUrl = url.startsWith("http://") || url.startsWith("https://");
|
|
271
271
|
if (isWebUrl && !options.type) {
|
|
272
272
|
try {
|
|
273
|
+
const scraper = options.scraper === "crawlee" ? "basic" : options.scraper ?? "basic";
|
|
274
|
+
const spider = options.spider ?? options.spiderAdapter ?? (options.scraper === "crawlee" ? "crawlee" : "dom");
|
|
273
275
|
const scraped = await scrapeDocument(url, {
|
|
274
|
-
scraper
|
|
275
|
-
spider
|
|
276
|
+
scraper,
|
|
277
|
+
spider,
|
|
276
278
|
cache: options.cache,
|
|
277
279
|
cacheExpiry: options.cacheExpiry,
|
|
278
280
|
headers: options.headers,
|
|
@@ -292,13 +294,13 @@ async function fetchDocument(url, options = {}) {
|
|
|
292
294
|
);
|
|
293
295
|
}
|
|
294
296
|
}
|
|
295
|
-
let type = options.type;
|
|
297
|
+
let type = options.type ?? "";
|
|
296
298
|
if (!type) {
|
|
297
299
|
const urlLower = url.toLowerCase();
|
|
298
300
|
if (urlLower.endsWith(".pdf") || urlLower.includes(".pdf?") || urlLower.includes(".pdf#")) {
|
|
299
301
|
type = "application/pdf";
|
|
300
302
|
} else {
|
|
301
|
-
type = getMimeType(url)
|
|
303
|
+
type = getMimeType(url) ?? "";
|
|
302
304
|
}
|
|
303
305
|
}
|
|
304
306
|
const processor = processors.find((p) => p.supports(type));
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/document.ts","../src/utils.ts","../src/processors/pdf.ts","../src/factory.ts","../src/index.ts"],"sourcesContent":["import os from 'node:os';\nimport path from 'node:path';\nimport { URL } from 'node:url';\nimport { downloadFileWithCache, getMimeType } from '@happyvertical/files';\nimport { makeSlug } from '@happyvertical/utils';\nimport type {\n DocumentPart,\n Document as DocumentType,\n FetchDocumentOptions,\n} from './types';\n\n/**\n * Base document handler with multi-part support\n *\n * Provides functionality for downloading, caching, and structuring documents\n * into hierarchical parts. Specific format processing (PDF, HTML, Markdown)\n * is handled by specialized processors.\n */\nexport class Document {\n /**\n * Flag indicating if document is from a remote source\n */\n protected isRemote = false;\n\n /**\n * Configuration options\n */\n protected options: FetchDocumentOptions;\n\n /**\n * Local file path where document is stored\n */\n private _localPath = '';\n\n /**\n * Directory used for caching files\n */\n private _cacheDir = '';\n\n /**\n * Document URL\n */\n public url: URL;\n\n /**\n * Document MIME type\n */\n public type: string;\n\n /**\n * Document parts (hierarchical structure)\n */\n public parts: DocumentPart[] = [];\n\n /**\n * Document-level metadata\n */\n public metadata: Record<string, any> = {};\n\n /**\n * Get the local file path where document is stored\n */\n public get localPath(): string {\n return this._localPath;\n }\n\n /**\n * Get the directory used for caching files\n */\n public get cacheDir(): string {\n return this._cacheDir;\n }\n\n /**\n * Creates a new Document instance\n *\n * @param url - Document URL or file path\n * @param options - Document configuration options\n */\n constructor(url: string, options: FetchDocumentOptions = {}) {\n this.url = new URL(url);\n this.options = options;\n this.type =\n options.type || getMimeType(this.url.toString()) || 'text/plain';\n\n this._cacheDir =\n options.cacheDir ||\n path.resolve(os.tmpdir(), '.cache', 'have-sdk', 'documents');\n\n if (this.url.protocol.startsWith('file')) {\n // Decode URL-encoded characters in the pathname only (e.g., %20 -> space).\n // Note: Query parameters and hash fragments are not decoded here.\n this._localPath = decodeURIComponent(this.url.pathname);\n this.isRemote = false;\n } else if (this.url.protocol.startsWith('http')) {\n // Generate cache path from URL pathname\n // Query parameters (?) and fragments (#) are automatically excluded from url.pathname\n let pathname = this.url.pathname;\n\n // Remove trailing slash (directory-style URLs)\n if (pathname.endsWith('/')) {\n pathname = pathname.slice(0, -1);\n }\n\n // Add file extension if missing and we know the type\n // This is crucial for URLs like /download/file/?wpdmdl=123 which have no extension\n if (!pathname.match(/\\.[a-z0-9]+$/i)) {\n // Add appropriate extension based on MIME type\n if (\n this.type === 'application/pdf' ||\n options.type === 'application/pdf'\n ) {\n pathname += '.pdf';\n }\n // Future: Add other common extensions (html, json, etc.)\n }\n\n this._localPath = path.join(\n this._cacheDir,\n makeSlug(this.url.hostname),\n pathname,\n );\n this.isRemote = true;\n }\n }\n\n /**\n * Creates and initializes a Document instance\n *\n * Downloads remote files and prepares the document for processing.\n *\n * @param url - Document URL or file path\n * @param options - Document configuration options\n * @returns Promise resolving to the initialized Document\n */\n static async create(\n url: string,\n options: FetchDocumentOptions = {},\n ): Promise<Document> {\n const document = new Document(url, options);\n await document.initialize();\n return document;\n }\n\n /**\n * Initializes the document, downloading it if it's remote\n *\n * @returns Promise that resolves when initialization is complete\n */\n async initialize(): Promise<void> {\n if (this.isRemote) {\n if (!this.url) {\n throw new Error('Cannot initialize remote document: URL is required');\n }\n await downloadFileWithCache(this.url.toString(), this._localPath);\n }\n }\n\n /**\n * Checks if the document is a text-based file that can be read directly\n *\n * @returns Boolean indicating if the file is text-based\n */\n public isTextFile(): boolean {\n if (!this.type) return false;\n\n return (\n this.type.startsWith('text/') ||\n this.type === 'application/json' ||\n this.type === 'application/xml' ||\n this.type === 'application/javascript' ||\n this.type === 'application/typescript' ||\n [\n '.txt',\n '.md',\n '.json',\n '.xml',\n '.html',\n '.css',\n '.js',\n '.ts',\n '.yaml',\n '.yml',\n ].some((ext) => this.localPath.toLowerCase().endsWith(ext))\n );\n }\n\n /**\n * Converts the document to the standard Document interface\n *\n * @returns Document object with URL, type, parts, and metadata\n */\n public toDocument(): DocumentType {\n return {\n url: this.url.toString(),\n type: this.type,\n parts: this.parts,\n metadata: this.metadata,\n };\n }\n}\n\nexport default Document;\n","/**\n * Utility functions for document processing\n */\n\n/**\n * Extract a human-readable title from a URL\n *\n * Takes a URL and extracts the filename from the pathname, then formats it\n * into a readable title by removing the extension and converting separators\n * to spaces. Also decodes URL-encoded characters like %20.\n *\n * @param url - URL string to extract title from\n * @param defaultTitle - Default title to use if extraction fails\n * @returns Formatted title string\n *\n * @example\n * ```typescript\n * getTitleFromUrl('file:///path/to/My%20Document.pdf')\n * // Returns: 'My Document'\n *\n * getTitleFromUrl('https://example.com/research_paper.pdf')\n * // Returns: 'research paper'\n * ```\n */\nexport function getTitleFromUrl(\n url: string,\n defaultTitle = 'Document',\n): string {\n try {\n const urlObj = new URL(url);\n const pathname = urlObj.pathname;\n const filename = pathname.split('/').pop() || defaultTitle;\n\n // Decode URL-encoded characters (e.g., %20 -> space)\n const decodedFilename = decodeURIComponent(filename);\n\n // Remove extension and convert separators to spaces\n return decodedFilename\n .replace(/\\.(pdf|html?|md|txt)$/i, '')\n .replace(/[-_]/g, ' ')\n .trim();\n } catch {\n return defaultTitle;\n }\n}\n","import { promises as fs } from 'node:fs';\nimport { getCached, setCached } from '@happyvertical/files';\nimport { getPDFReader } from '@happyvertical/pdf';\nimport { v4 as uuidv4 } from 'uuid';\nimport { Document as BaseDocument } from '../document';\nimport type {\n Document,\n DocumentImage,\n DocumentPart,\n DocumentProcessor,\n FetchDocumentOptions,\n} from '../types';\nimport { getTitleFromUrl } from '../utils';\n\n/**\n * PDF Document Processor\n *\n * Handles PDF documents with support for:\n * - Text extraction from PDF content via `@happyvertical/pdf`\n * - PDF header validation (detects HTML cache poisoning from document management systems)\n * - Processed document caching via `@happyvertical/files`\n *\n * Image extraction and OCR are stubbed for future implementation.\n */\nexport class PDFProcessor implements DocumentProcessor {\n /**\n * Check if this processor supports the given MIME type or extension.\n * Accepts `'application/pdf'`, `'.pdf'`, or `'pdf'` (case-insensitive).\n *\n * @param type - MIME type or file extension to check\n * @returns `true` if this processor can handle the given type\n */\n supports(type: string): boolean {\n return (\n type === 'application/pdf' ||\n type.endsWith('.pdf') ||\n type.toLowerCase() === 'pdf'\n );\n }\n\n /**\n * Process a PDF document\n *\n * Extracts text and optionally images/OCR from the PDF, structuring\n * it into hierarchical document parts.\n *\n * @param url - PDF URL or file path\n * @param options - Processing options\n * @returns Promise resolving to structured Document\n */\n async process(\n url: string,\n options: FetchDocumentOptions = {},\n ): Promise<Document> {\n // Create and initialize base document\n const baseDoc = await BaseDocument.create(url, options);\n\n // Check cache for processed document\n const cacheKey = `${baseDoc.localPath}.processed_pdf`;\n const cached = await getCached(cacheKey);\n if (cached) {\n try {\n const parsed = JSON.parse(cached);\n return {\n url: baseDoc.url.toString(),\n type: baseDoc.type,\n parts: parsed.parts,\n metadata: parsed.metadata || {},\n };\n } catch (error) {\n // Cache corrupted, continue with fresh processing\n console.warn('Cached PDF data corrupted, reprocessing', error);\n }\n }\n\n // Validate that the downloaded file is actually a PDF (issue #460, #463)\n // WordPress Download Manager and some other servers may return HTML\n // with Content-Type: application/pdf, causing PDF extraction to fail\n const fileBuffer = await fs.readFile(baseDoc.localPath);\n const header = fileBuffer.subarray(0, 5).toString('utf-8');\n\n if (header !== '%PDF-') {\n // File is not a valid PDF - delete poisoned cache file (issue #463)\n try {\n await fs.unlink(baseDoc.localPath);\n } catch (unlinkError) {\n console.warn(\n `Failed to delete poisoned cache file: ${baseDoc.localPath}`,\n unlinkError,\n );\n }\n\n // Check if it's HTML to provide helpful error message\n const content = fileBuffer.toString(\n 'utf-8',\n 0,\n Math.min(1000, fileBuffer.length),\n );\n if (content.includes('<!DOCTYPE html>') || content.includes('<html')) {\n throw new Error(\n `Downloaded file is HTML, not PDF. The server returned HTML content for ${url}. ` +\n 'This commonly occurs with WordPress Download Manager URLs that return tracking pages. ' +\n `Expected PDF magic bytes (%PDF-) but got: ${header}. ` +\n 'The poisoned cache file has been removed - please try again.',\n );\n } else {\n throw new Error(\n `Downloaded file is not a valid PDF. Expected %PDF- magic bytes but got: ${header}. ` +\n 'The invalid cache file has been removed - please try again.',\n );\n }\n }\n\n // Get PDF reader and extract content\n const reader = await getPDFReader();\n const extractedText = await reader.extractText(baseDoc.localPath);\n\n // Create main document part\n const mainPart: DocumentPart = {\n id: uuidv4(),\n title: getTitleFromUrl(url, 'PDF Document'),\n content: extractedText || '',\n type: 'text',\n metadata: {\n source: 'pdf',\n filePath: baseDoc.localPath,\n },\n };\n\n // Extract images if enabled\n if (options.extractImages === true) {\n mainPart.images = await this.extractImages(\n baseDoc.localPath,\n options.runOcr !== false,\n );\n }\n\n const document: Document = {\n url: baseDoc.url.toString(),\n type: baseDoc.type,\n parts: [mainPart],\n metadata: {\n processor: 'pdf',\n extractedAt: new Date().toISOString(),\n hasImages: (mainPart.images?.length || 0) > 0,\n },\n };\n\n // Cache the processed document\n await setCached(cacheKey, JSON.stringify(document));\n\n return document;\n }\n\n /**\n * Extract images from PDF\n *\n * This is a placeholder for future image extraction functionality.\n * Will use @happyvertical/pdf's image extraction capabilities when available.\n *\n * @param filePath - Local PDF file path\n * @param runOcr - Whether to run OCR on extracted images\n * @returns Promise resolving to array of DocumentImages\n */\n private async extractImages(\n _filePath: string,\n _runOcr: boolean,\n ): Promise<DocumentImage[]> {\n // TODO: Implement image extraction using @happyvertical/pdf\n // For now, return empty array as placeholder\n\n // Future implementation will:\n // 1. Use getPDFReader() to extract images from PDF\n // 2. Save images to cache directory\n // 3. If runOcr is true, use @happyvertical/ocr to extract text from images\n // 4. Return array of DocumentImage objects with metadata\n\n return [];\n }\n}\n\nexport default PDFProcessor;\n","import { getMimeType } from '@happyvertical/files';\nimport { scrapeDocument } from '@happyvertical/spider';\nimport { PDFProcessor } from './processors/pdf';\nimport type { Document, FetchDocumentOptions } from './types';\n\n/**\n * Available document processors\n */\nconst processors = [new PDFProcessor()];\n\n/**\n * Fetch a document from a URL with automatic format detection\n *\n * This factory function:\n * 1. Detects the document format (PDF, HTML, Markdown, etc.)\n * 2. Selects the appropriate processor\n * 3. Processes the document into structured parts\n * 4. Returns a Document object with hierarchical content\n *\n * @param url - Document URL or file path (file://, http://, https://)\n * @param options - Fetch and processing options\n * @returns Promise resolving to structured Document\n *\n * @example\n * ```typescript\n * // Fetch a PDF with image extraction and OCR\n * const doc = await fetchDocument('https://example.com/report.pdf', {\n * extractImages: true,\n * runOcr: true\n * });\n *\n * // Access document parts\n * for (const part of doc.parts) {\n * console.log(part.title);\n * console.log(part.content);\n *\n * // Check for images\n * if (part.images) {\n * for (const image of part.images) {\n * console.log(image.url);\n * console.log(image.ocrText); // Text extracted via OCR\n * }\n * }\n * }\n * ```\n */\nexport async function fetchDocument(\n url: string,\n options: FetchDocumentOptions = {},\n): Promise<Document> {\n // For web URLs (http/https), use spider package to detect special cases\n // (WordPress Download Manager, CivicWeb, DocuShare, etc.)\n const isWebUrl = url.startsWith('http://') || url.startsWith('https://');\n\n if (isWebUrl && !options.type) {\n try {\n // Use spider to detect WordPress, CivicWeb, DocuShare, and other document management systems\n const scraped = await scrapeDocument(url, {\n scraper: options.scraper || 'basic',\n spider: options.spider || 'dom',\n cache: options.cache,\n cacheExpiry: options.cacheExpiry,\n headers: options.headers,\n timeout: options.timeout,\n maxDuration: options.maxDuration,\n maxInteractions: options.maxInteractions,\n });\n\n // Check if spider detected a document management system with PDF link\n const hasDocLink =\n scraped.metadata.strategy === 'wordpress-pdf-link' ||\n scraped.metadata.strategy === 'civicweb-pdf-link' ||\n scraped.metadata.strategy === 'docushare-pdf-link';\n\n if (hasDocLink && scraped.metadata.isPdf && !scraped.metadata.complete) {\n // Spider detected a document management page and extracted the PDF URL\n // Use the extracted URL for PDF processing\n url = scraped.url;\n options.type = 'application/pdf';\n }\n } catch (error) {\n // If spider fails, continue with direct download\n // This ensures backward compatibility\n console.warn(\n `Spider detection failed for ${url}, falling back to direct download:`,\n error,\n );\n }\n }\n\n // Determine type - check URL extension first, then MIME type\n // This handles servers that return incorrect Content-Type headers (e.g., application/octet-stream for PDFs)\n let type = options.type;\n\n if (!type) {\n // Extract file extension from URL\n const urlLower = url.toLowerCase();\n\n // Check for common document extensions in URL\n if (\n urlLower.endsWith('.pdf') ||\n urlLower.includes('.pdf?') ||\n urlLower.includes('.pdf#')\n ) {\n type = 'application/pdf';\n } else {\n // Fall back to MIME type detection\n type = getMimeType(url) || '';\n }\n }\n\n // Find appropriate processor\n const processor = processors.find((p) => p.supports(type));\n\n if (!processor) {\n throw new Error(\n `No processor available for document type: ${type}. Supported types: PDF (.pdf, application/pdf)`,\n );\n }\n\n // Process document\n return processor.process(url, options);\n}\n\nexport default fetchDocument;\n","/**\n * @happyvertical/documents - Document processing with multi-part structure\n *\n * Provides document processing for PDFs with support for:\n * - Hierarchical document parts\n * - Automatic format detection from URL or MIME type\n * - Document management system detection (WordPress, CivicWeb, DocuShare)\n * - File caching for performance\n *\n * @example\n * ```typescript\n * import { fetchDocument } from '@happyvertical/documents';\n *\n * const doc = await fetchDocument('https://example.com/report.pdf');\n *\n * for (const part of doc.parts) {\n * console.log(part.title);\n * console.log(part.content);\n * }\n * ```\n */\n\n// Base classes\nexport { Document } from './document';\n// Main factory function\nexport { fetchDocument } from './factory';\n\n// Processors\nexport { PDFProcessor } from './processors/pdf';\n// Types\nexport type {\n Document as DocumentType,\n DocumentImage,\n DocumentPart,\n DocumentProcessor,\n FetchDocumentOptions,\n} from './types';\n// Utilities\nexport { getTitleFromUrl } from './utils';\n\n/** @internal */\nexport const PACKAGE_VERSION_INITIALIZED = true;\n"],"names":["URL","BaseDocument","fs","uuidv4"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/document.ts","../src/utils.ts","../src/processors/pdf.ts","../src/factory.ts","../src/index.ts"],"sourcesContent":["import os from 'node:os';\nimport path from 'node:path';\nimport { URL } from 'node:url';\nimport { downloadFileWithCache, getMimeType } from '@happyvertical/files';\nimport { makeSlug } from '@happyvertical/utils';\nimport type {\n DocumentPart,\n Document as DocumentType,\n FetchDocumentOptions,\n} from './types';\n\n/**\n * Base document handler with multi-part support\n *\n * Provides functionality for downloading, caching, and structuring documents\n * into hierarchical parts. Specific format processing (PDF, HTML, Markdown)\n * is handled by specialized processors.\n */\nexport class Document {\n /**\n * Flag indicating if document is from a remote source\n */\n protected isRemote = false;\n\n /**\n * Configuration options\n */\n protected options: FetchDocumentOptions;\n\n /**\n * Local file path where document is stored\n */\n private _localPath = '';\n\n /**\n * Directory used for caching files\n */\n private _cacheDir = '';\n\n /**\n * Document URL\n */\n public url: URL;\n\n /**\n * Document MIME type\n */\n public type: string;\n\n /**\n * Document parts (hierarchical structure)\n */\n public parts: DocumentPart[] = [];\n\n /**\n * Document-level metadata\n */\n public metadata: Record<string, any> = {};\n\n /**\n * Get the local file path where document is stored\n */\n public get localPath(): string {\n return this._localPath;\n }\n\n /**\n * Get the directory used for caching files\n */\n public get cacheDir(): string {\n return this._cacheDir;\n }\n\n /**\n * Creates a new Document instance\n *\n * @param url - Document URL or file path\n * @param options - Document configuration options\n */\n constructor(url: string, options: FetchDocumentOptions = {}) {\n this.url = new URL(url);\n this.options = options;\n this.type =\n options.type || getMimeType(this.url.toString()) || 'text/plain';\n\n this._cacheDir =\n options.cacheDir ||\n path.resolve(os.tmpdir(), '.cache', 'have-sdk', 'documents');\n\n if (this.url.protocol.startsWith('file')) {\n // Decode URL-encoded characters in the pathname only (e.g., %20 -> space).\n // Note: Query parameters and hash fragments are not decoded here.\n this._localPath = decodeURIComponent(this.url.pathname);\n this.isRemote = false;\n } else if (this.url.protocol.startsWith('http')) {\n // Generate cache path from URL pathname\n // Query parameters (?) and fragments (#) are automatically excluded from url.pathname\n let pathname = this.url.pathname;\n\n // Remove trailing slash (directory-style URLs)\n if (pathname.endsWith('/')) {\n pathname = pathname.slice(0, -1);\n }\n\n // Add file extension if missing and we know the type\n // This is crucial for URLs like /download/file/?wpdmdl=123 which have no extension\n if (!pathname.match(/\\.[a-z0-9]+$/i)) {\n // Add appropriate extension based on MIME type\n if (\n this.type === 'application/pdf' ||\n options.type === 'application/pdf'\n ) {\n pathname += '.pdf';\n }\n // Future: Add other common extensions (html, json, etc.)\n }\n\n this._localPath = path.join(\n this._cacheDir,\n makeSlug(this.url.hostname),\n pathname,\n );\n this.isRemote = true;\n }\n }\n\n /**\n * Creates and initializes a Document instance\n *\n * Downloads remote files and prepares the document for processing.\n *\n * @param url - Document URL or file path\n * @param options - Document configuration options\n * @returns Promise resolving to the initialized Document\n */\n static async create(\n url: string,\n options: FetchDocumentOptions = {},\n ): Promise<Document> {\n const document = new Document(url, options);\n await document.initialize();\n return document;\n }\n\n /**\n * Initializes the document, downloading it if it's remote\n *\n * @returns Promise that resolves when initialization is complete\n */\n async initialize(): Promise<void> {\n if (this.isRemote) {\n if (!this.url) {\n throw new Error('Cannot initialize remote document: URL is required');\n }\n await downloadFileWithCache(this.url.toString(), this._localPath);\n }\n }\n\n /**\n * Checks if the document is a text-based file that can be read directly\n *\n * @returns Boolean indicating if the file is text-based\n */\n public isTextFile(): boolean {\n if (!this.type) return false;\n\n return (\n this.type.startsWith('text/') ||\n this.type === 'application/json' ||\n this.type === 'application/xml' ||\n this.type === 'application/javascript' ||\n this.type === 'application/typescript' ||\n [\n '.txt',\n '.md',\n '.json',\n '.xml',\n '.html',\n '.css',\n '.js',\n '.ts',\n '.yaml',\n '.yml',\n ].some((ext) => this.localPath.toLowerCase().endsWith(ext))\n );\n }\n\n /**\n * Converts the document to the standard Document interface\n *\n * @returns Document object with URL, type, parts, and metadata\n */\n public toDocument(): DocumentType {\n return {\n url: this.url.toString(),\n type: this.type,\n parts: this.parts,\n metadata: this.metadata,\n };\n }\n}\n\nexport default Document;\n","/**\n * Utility functions for document processing\n */\n\n/**\n * Extract a human-readable title from a URL\n *\n * Takes a URL and extracts the filename from the pathname, then formats it\n * into a readable title by removing the extension and converting separators\n * to spaces. Also decodes URL-encoded characters like %20.\n *\n * @param url - URL string to extract title from\n * @param defaultTitle - Default title to use if extraction fails\n * @returns Formatted title string\n *\n * @example\n * ```typescript\n * getTitleFromUrl('file:///path/to/My%20Document.pdf')\n * // Returns: 'My Document'\n *\n * getTitleFromUrl('https://example.com/research_paper.pdf')\n * // Returns: 'research paper'\n * ```\n */\nexport function getTitleFromUrl(\n url: string,\n defaultTitle = 'Document',\n): string {\n try {\n const urlObj = new URL(url);\n const pathname = urlObj.pathname;\n const filename = pathname.split('/').pop() || defaultTitle;\n\n // Decode URL-encoded characters (e.g., %20 -> space)\n const decodedFilename = decodeURIComponent(filename);\n\n // Remove extension and convert separators to spaces\n return decodedFilename\n .replace(/\\.(pdf|html?|md|txt)$/i, '')\n .replace(/[-_]/g, ' ')\n .trim();\n } catch {\n return defaultTitle;\n }\n}\n","import { promises as fs } from 'node:fs';\nimport { getCached, setCached } from '@happyvertical/files';\nimport { getPDFReader } from '@happyvertical/pdf';\nimport { v4 as uuidv4 } from 'uuid';\nimport { Document as BaseDocument } from '../document';\nimport type {\n Document,\n DocumentImage,\n DocumentPart,\n DocumentProcessor,\n FetchDocumentOptions,\n} from '../types';\nimport { getTitleFromUrl } from '../utils';\n\n/**\n * PDF Document Processor\n *\n * Handles PDF documents with support for:\n * - Text extraction from PDF content via `@happyvertical/pdf`\n * - PDF header validation (detects HTML cache poisoning from document management systems)\n * - Processed document caching via `@happyvertical/files`\n *\n * Image extraction and OCR are stubbed for future implementation.\n */\nexport class PDFProcessor implements DocumentProcessor {\n /**\n * Check if this processor supports the given MIME type or extension.\n * Accepts `'application/pdf'`, `'.pdf'`, or `'pdf'` (case-insensitive).\n *\n * @param type - MIME type or file extension to check\n * @returns `true` if this processor can handle the given type\n */\n supports(type: string): boolean {\n return (\n type === 'application/pdf' ||\n type.endsWith('.pdf') ||\n type.toLowerCase() === 'pdf'\n );\n }\n\n /**\n * Process a PDF document\n *\n * Extracts text and optionally images/OCR from the PDF, structuring\n * it into hierarchical document parts.\n *\n * @param url - PDF URL or file path\n * @param options - Processing options\n * @returns Promise resolving to structured Document\n */\n async process(\n url: string,\n options: FetchDocumentOptions = {},\n ): Promise<Document> {\n // Create and initialize base document\n const baseDoc = await BaseDocument.create(url, options);\n\n // Check cache for processed document\n const cacheKey = `${baseDoc.localPath}.processed_pdf`;\n const cached = await getCached(cacheKey);\n if (cached) {\n try {\n const parsed = JSON.parse(cached);\n return {\n url: baseDoc.url.toString(),\n type: baseDoc.type,\n parts: parsed.parts,\n metadata: parsed.metadata || {},\n };\n } catch (error) {\n // Cache corrupted, continue with fresh processing\n console.warn('Cached PDF data corrupted, reprocessing', error);\n }\n }\n\n // Validate that the downloaded file is actually a PDF (issue #460, #463)\n // WordPress Download Manager and some other servers may return HTML\n // with Content-Type: application/pdf, causing PDF extraction to fail\n const fileBuffer = await fs.readFile(baseDoc.localPath);\n const header = fileBuffer.subarray(0, 5).toString('utf-8');\n\n if (header !== '%PDF-') {\n // File is not a valid PDF - delete poisoned cache file (issue #463)\n try {\n await fs.unlink(baseDoc.localPath);\n } catch (unlinkError) {\n console.warn(\n `Failed to delete poisoned cache file: ${baseDoc.localPath}`,\n unlinkError,\n );\n }\n\n // Check if it's HTML to provide helpful error message\n const content = fileBuffer.toString(\n 'utf-8',\n 0,\n Math.min(1000, fileBuffer.length),\n );\n if (content.includes('<!DOCTYPE html>') || content.includes('<html')) {\n throw new Error(\n `Downloaded file is HTML, not PDF. The server returned HTML content for ${url}. ` +\n 'This commonly occurs with WordPress Download Manager URLs that return tracking pages. ' +\n `Expected PDF magic bytes (%PDF-) but got: ${header}. ` +\n 'The poisoned cache file has been removed - please try again.',\n );\n } else {\n throw new Error(\n `Downloaded file is not a valid PDF. Expected %PDF- magic bytes but got: ${header}. ` +\n 'The invalid cache file has been removed - please try again.',\n );\n }\n }\n\n // Get PDF reader and extract content\n const reader = await getPDFReader();\n const extractedText = await reader.extractText(baseDoc.localPath);\n\n // Create main document part\n const mainPart: DocumentPart = {\n id: uuidv4(),\n title: getTitleFromUrl(url, 'PDF Document'),\n content: extractedText || '',\n type: 'text',\n metadata: {\n source: 'pdf',\n filePath: baseDoc.localPath,\n },\n };\n\n // Extract images if enabled\n if (options.extractImages === true) {\n mainPart.images = await this.extractImages(\n baseDoc.localPath,\n options.runOcr !== false,\n );\n }\n\n const document: Document = {\n url: baseDoc.url.toString(),\n type: baseDoc.type,\n parts: [mainPart],\n metadata: {\n processor: 'pdf',\n extractedAt: new Date().toISOString(),\n hasImages: (mainPart.images?.length || 0) > 0,\n },\n };\n\n // Cache the processed document\n await setCached(cacheKey, JSON.stringify(document));\n\n return document;\n }\n\n /**\n * Extract images from PDF\n *\n * This is a placeholder for future image extraction functionality.\n * Will use @happyvertical/pdf's image extraction capabilities when available.\n *\n * @param filePath - Local PDF file path\n * @param runOcr - Whether to run OCR on extracted images\n * @returns Promise resolving to array of DocumentImages\n */\n private async extractImages(\n _filePath: string,\n _runOcr: boolean,\n ): Promise<DocumentImage[]> {\n // TODO: Implement image extraction using @happyvertical/pdf\n // For now, return empty array as placeholder\n\n // Future implementation will:\n // 1. Use getPDFReader() to extract images from PDF\n // 2. Save images to cache directory\n // 3. If runOcr is true, use @happyvertical/ocr to extract text from images\n // 4. Return array of DocumentImage objects with metadata\n\n return [];\n }\n}\n\nexport default PDFProcessor;\n","import { getMimeType } from '@happyvertical/files';\nimport { scrapeDocument } from '@happyvertical/spider';\nimport { PDFProcessor } from './processors/pdf';\nimport type { Document, FetchDocumentOptions } from './types';\n\n/**\n * Available document processors\n */\nconst processors = [new PDFProcessor()];\n\n/**\n * Fetch a document from a URL with automatic format detection\n *\n * This factory function:\n * 1. Detects the document format (PDF, HTML, Markdown, etc.)\n * 2. Selects the appropriate processor\n * 3. Processes the document into structured parts\n * 4. Returns a Document object with hierarchical content\n *\n * @param url - Document URL or file path (file://, http://, https://)\n * @param options - Fetch and processing options\n * @returns Promise resolving to structured Document\n *\n * @example\n * ```typescript\n * // Fetch a PDF with image extraction and OCR\n * const doc = await fetchDocument('https://example.com/report.pdf', {\n * extractImages: true,\n * runOcr: true\n * });\n *\n * // Access document parts\n * for (const part of doc.parts) {\n * console.log(part.title);\n * console.log(part.content);\n *\n * // Check for images\n * if (part.images) {\n * for (const image of part.images) {\n * console.log(image.url);\n * console.log(image.ocrText); // Text extracted via OCR\n * }\n * }\n * }\n * ```\n */\nexport async function fetchDocument(\n url: string,\n options: FetchDocumentOptions = {},\n): Promise<Document> {\n // For web URLs (http/https), use spider package to detect special cases\n // (WordPress Download Manager, CivicWeb, DocuShare, etc.)\n const isWebUrl = url.startsWith('http://') || url.startsWith('https://');\n\n if (isWebUrl && !options.type) {\n try {\n const scraper =\n options.scraper === 'crawlee' ? 'basic' : (options.scraper ?? 'basic');\n const spider =\n options.spider ??\n options.spiderAdapter ??\n (options.scraper === 'crawlee' ? 'crawlee' : 'dom');\n\n // Use spider to detect WordPress, CivicWeb, DocuShare, and other document management systems\n const scraped = await scrapeDocument(url, {\n scraper,\n spider,\n cache: options.cache,\n cacheExpiry: options.cacheExpiry,\n headers: options.headers,\n timeout: options.timeout,\n maxDuration: options.maxDuration,\n maxInteractions: options.maxInteractions,\n });\n\n // Check if spider detected a document management system with PDF link\n const hasDocLink =\n scraped.metadata.strategy === 'wordpress-pdf-link' ||\n scraped.metadata.strategy === 'civicweb-pdf-link' ||\n scraped.metadata.strategy === 'docushare-pdf-link';\n\n if (hasDocLink && scraped.metadata.isPdf && !scraped.metadata.complete) {\n // Spider detected a document management page and extracted the PDF URL\n // Use the extracted URL for PDF processing\n url = scraped.url;\n options.type = 'application/pdf';\n }\n } catch (error) {\n // If spider fails, continue with direct download\n // This ensures backward compatibility\n console.warn(\n `Spider detection failed for ${url}, falling back to direct download:`,\n error,\n );\n }\n }\n\n // Determine type - check URL extension first, then MIME type\n // This handles servers that return incorrect Content-Type headers (e.g., application/octet-stream for PDFs)\n let type = options.type ?? '';\n\n if (!type) {\n // Extract file extension from URL\n const urlLower = url.toLowerCase();\n\n // Check for common document extensions in URL\n if (\n urlLower.endsWith('.pdf') ||\n urlLower.includes('.pdf?') ||\n urlLower.includes('.pdf#')\n ) {\n type = 'application/pdf';\n } else {\n // Fall back to MIME type detection\n type = getMimeType(url) ?? '';\n }\n }\n\n // Find appropriate processor\n const processor = processors.find((p) => p.supports(type));\n\n if (!processor) {\n throw new Error(\n `No processor available for document type: ${type}. Supported types: PDF (.pdf, application/pdf)`,\n );\n }\n\n // Process document\n return processor.process(url, options);\n}\n\nexport default fetchDocument;\n","/**\n * @happyvertical/documents - Document processing with multi-part structure\n *\n * Provides document processing for PDFs with support for:\n * - Hierarchical document parts\n * - Automatic format detection from URL or MIME type\n * - Document management system detection (WordPress, CivicWeb, DocuShare)\n * - File caching for performance\n *\n * @example\n * ```typescript\n * import { fetchDocument } from '@happyvertical/documents';\n *\n * const doc = await fetchDocument('https://example.com/report.pdf');\n *\n * for (const part of doc.parts) {\n * console.log(part.title);\n * console.log(part.content);\n * }\n * ```\n */\n\n// Base classes\nexport { Document } from './document';\n// Main factory function\nexport { fetchDocument } from './factory';\n\n// Processors\nexport { PDFProcessor } from './processors/pdf';\n// Types\nexport type {\n Document as DocumentType,\n DocumentImage,\n DocumentPart,\n DocumentProcessor,\n FetchDocumentOptions,\n} from './types';\n// Utilities\nexport { getTitleFromUrl } from './utils';\n\n/** @internal */\nexport const PACKAGE_VERSION_INITIALIZED = true;\n"],"names":["URL","BaseDocument","fs","uuidv4"],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -122,24 +122,26 @@ export interface FetchDocumentOptions {
|
|
|
122
122
|
/**
|
|
123
123
|
* Scraper type to use for content extraction
|
|
124
124
|
* - 'basic': Fast, static HTML scraping (default)
|
|
125
|
-
* - '
|
|
125
|
+
* - 'tree': Browser-backed expansion of trees and accordions
|
|
126
|
+
* - 'crawlee': Deprecated alias for spider: 'crawlee' with basic scraping
|
|
126
127
|
* @default 'basic'
|
|
127
128
|
*/
|
|
128
|
-
scraper?: 'basic' | 'crawlee';
|
|
129
|
+
scraper?: 'basic' | 'tree' | 'crawlee';
|
|
129
130
|
/**
|
|
130
131
|
* Spider adapter to use for fetching web pages
|
|
131
132
|
* - 'simple': Basic HTTP fetch
|
|
132
133
|
* - 'dom': HTML parsing with happy-dom
|
|
133
|
-
* - 'crawlee': Headless browser
|
|
134
|
+
* - 'crawlee': Headless browser through Crawlee
|
|
135
|
+
* - 'crawl4ai': Remote crawl4ai server
|
|
134
136
|
* @default 'dom'
|
|
135
137
|
*/
|
|
136
|
-
spider?: 'simple' | 'dom' | 'crawlee';
|
|
138
|
+
spider?: 'simple' | 'dom' | 'crawlee' | 'crawl4ai';
|
|
137
139
|
/**
|
|
138
140
|
* Spider adapter to use for HTML fetching (deprecated, use 'spider' instead)
|
|
139
141
|
* @default 'simple'
|
|
140
142
|
* @deprecated Use 'spider' instead
|
|
141
143
|
*/
|
|
142
|
-
spiderAdapter?: 'simple' | 'dom' | 'crawlee';
|
|
144
|
+
spiderAdapter?: 'simple' | 'dom' | 'crawlee' | 'crawl4ai';
|
|
143
145
|
/**
|
|
144
146
|
* Whether to use cache for spider fetching
|
|
145
147
|
* @default true
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B;;OAEG;IACH,EAAE,EAAE,MAAM,CAAC;IAEX;;OAEG;IACH,GAAG,EAAE,MAAM,CAAC;IAEZ;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,QAAQ,CAAC,EAAE;QACT,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;KACpB,CAAC;CACH;AAED;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B;;OAEG;IACH,EAAE,EAAE,MAAM,CAAC;IAEX;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,UAAU,CAAC;IAEnC;;OAEG;IACH,MAAM,CAAC,EAAE,aAAa,EAAE,CAAC;IAEzB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAE/B;;OAEG;IACH,KAAK,CAAC,EAAE,YAAY,EAAE,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB;;OAEG;IACH,GAAG,EAAE,MAAM,CAAC;IAEZ;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,KAAK,EAAE,YAAY,EAAE,CAAC;IAEtB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAChC;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IAExB;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IAEjB
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B;;OAEG;IACH,EAAE,EAAE,MAAM,CAAC;IAEX;;OAEG;IACH,GAAG,EAAE,MAAM,CAAC;IAEZ;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,QAAQ,CAAC,EAAE;QACT,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;KACpB,CAAC;CACH;AAED;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B;;OAEG;IACH,EAAE,EAAE,MAAM,CAAC;IAEX;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,UAAU,CAAC;IAEnC;;OAEG;IACH,MAAM,CAAC,EAAE,aAAa,EAAE,CAAC;IAEzB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAE/B;;OAEG;IACH,KAAK,CAAC,EAAE,YAAY,EAAE,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB;;OAEG;IACH,GAAG,EAAE,MAAM,CAAC;IAEZ;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,KAAK,EAAE,YAAY,EAAE,CAAC;IAEtB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAChC;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IAExB;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IAEjB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC;IAEvC;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,QAAQ,GAAG,KAAK,GAAG,SAAS,GAAG,UAAU,CAAC;IAEnD;;;;OAIG;IACH,aAAa,CAAC,EAAE,QAAQ,GAAG,KAAK,GAAG,SAAS,GAAG,UAAU,CAAC;IAE1D;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAEhB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEjC;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;;;;OAMG;IACH,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IAExE;;;;;OAKG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@happyvertical/documents",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.75.0",
|
|
4
4
|
"description": "Multi-part document processing with support for PDF, HTML, and Markdown",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -37,11 +37,11 @@
|
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"@happyvertical/ocr": "^0.60.39",
|
|
40
|
-
"@happyvertical/pdf": "^0.
|
|
41
|
-
"@happyvertical/spider": "^
|
|
40
|
+
"@happyvertical/pdf": "^0.65",
|
|
41
|
+
"@happyvertical/spider": "^1.1",
|
|
42
42
|
"uuid": "^13.0.0",
|
|
43
|
-
"@happyvertical/files": "0.
|
|
44
|
-
"@happyvertical/utils": "0.
|
|
43
|
+
"@happyvertical/files": "0.75.0",
|
|
44
|
+
"@happyvertical/utils": "0.75.0"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@types/node": "25.0.10",
|