@happyvertical/documents 0.111.3 → 0.111.5
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/AGENT.md +1 -1
- package/README.md +25 -1
- package/dist/factory.d.ts.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +284 -3
- package/dist/index.js.map +1 -1
- package/dist/processors/office.d.ts +77 -0
- package/dist/processors/office.d.ts.map +1 -0
- package/dist/types.d.ts +27 -0
- package/dist/types.d.ts.map +1 -1
- package/metadata.json +3 -1
- package/package.json +9 -4
package/AGENT.md
CHANGED
|
@@ -26,6 +26,6 @@ pnpm --filter @happyvertical/documents test
|
|
|
26
26
|
## Ecosystem Relationships
|
|
27
27
|
- Provides: Multi-part document processing with support for PDF, HTML, and Markdown
|
|
28
28
|
- Implements: none
|
|
29
|
-
- Requires: @happyvertical/files, @happyvertical/utils, @happyvertical/ocr, @happyvertical/pdf, @happyvertical/spider
|
|
29
|
+
- Requires: @happyvertical/files, @happyvertical/utils, @happyvertical/ocr, @happyvertical/pdf, @happyvertical/spider, @kreuzberg/node
|
|
30
30
|
- Stability: stable (Primary package surface is described as implemented and production-oriented.)
|
|
31
31
|
<!-- END AGENT:GENERATED -->
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://opensource.org/licenses/MIT)
|
|
4
4
|
|
|
5
|
-
Document processing with hierarchical structure.
|
|
5
|
+
Document processing with hierarchical structure. Supports PDF documents with text extraction, Word (`.docx`) and PowerPoint (`.pptx`) files read through the optional Kreuzberg engine, automatic document management system detection (WordPress Download Manager, CivicWeb, DocuShare), and file caching. Uses `@happyvertical/spider` for web page analysis and `@happyvertical/pdf` for PDF text extraction.
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
@@ -100,6 +100,27 @@ Base document handler. Manages downloading, caching, and local file path resolut
|
|
|
100
100
|
|
|
101
101
|
Implements `DocumentProcessor`. Extracts text from PDF files, validates PDF headers (detects HTML cache poisoning), and caches processed results.
|
|
102
102
|
|
|
103
|
+
### `DOCXProcessor` and `PPTXProcessor`
|
|
104
|
+
|
|
105
|
+
Read Word and PowerPoint files through [Kreuzberg](https://www.npmjs.com/package/@kreuzberg/node) (`@kreuzberg/node`, an **optional** dependency; no LibreOffice). `fetchDocument` picks them from the `.docx` / `.pptx` extension or MIME type.
|
|
106
|
+
|
|
107
|
+
- **DOCX**: one `markdown` part per heading section, nested by heading level (text before the first heading is a leading part titled with the document title). Embedded images are attached to the first part.
|
|
108
|
+
- **PPTX**: one `text` part per slide, in order. `part.metadata.slide` is the 1-based slide number, `part.title` the first line of slide text, `part.notes` the speaker notes (absent when the slide has none) and `part.images` the pictures on that slide (`image.data` holds the bytes, `image.altText` the description).
|
|
109
|
+
- **Kreuzberg not installed**: `process()` resolves with `parts: []` and `document.unsupported = { code: 'dependency-missing', reason, dependency: '@kreuzberg/node' }` instead of throwing. Corrupt files still reject.
|
|
110
|
+
|
|
111
|
+
For bytes you already hold in memory use `extractOffice(source, 'docx' | 'pptx', { extractImages? })`, which takes a `Buffer`, `Uint8Array` or file path and returns `{ status: 'ok', format, title, parts, metadata }` or `{ status: 'unsupported', code, reason, dependency, format }`.
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
import { extractOffice } from '@happyvertical/documents';
|
|
115
|
+
|
|
116
|
+
const result = await extractOffice(bytes, 'pptx');
|
|
117
|
+
if (result.status === 'unsupported') {
|
|
118
|
+
console.warn(result.reason);
|
|
119
|
+
} else {
|
|
120
|
+
for (const slide of result.parts) console.log(slide.metadata?.slide, slide.content, slide.notes);
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
103
124
|
### `getTitleFromUrl(url, defaultTitle?)`
|
|
104
125
|
|
|
105
126
|
Extracts a human-readable title from a URL by parsing the filename, removing extensions, and decoding URL-encoded characters.
|
|
@@ -112,6 +133,7 @@ interface Document {
|
|
|
112
133
|
type: string;
|
|
113
134
|
parts: DocumentPart[];
|
|
114
135
|
metadata?: Record<string, any>;
|
|
136
|
+
unsupported?: { code: 'dependency-missing'; reason: string; dependency?: string };
|
|
115
137
|
}
|
|
116
138
|
|
|
117
139
|
interface DocumentPart {
|
|
@@ -120,6 +142,7 @@ interface DocumentPart {
|
|
|
120
142
|
content: string;
|
|
121
143
|
type: 'text' | 'html' | 'markdown';
|
|
122
144
|
images?: DocumentImage[];
|
|
145
|
+
notes?: string; // speaker notes (PPTX slides)
|
|
123
146
|
metadata?: Record<string, any>;
|
|
124
147
|
parts?: DocumentPart[];
|
|
125
148
|
}
|
|
@@ -128,6 +151,7 @@ interface DocumentImage {
|
|
|
128
151
|
id: string;
|
|
129
152
|
url: string;
|
|
130
153
|
localPath?: string;
|
|
154
|
+
data?: Uint8Array; // in-memory bytes (Office documents)
|
|
131
155
|
altText?: string;
|
|
132
156
|
ocrText?: string;
|
|
133
157
|
position?: number;
|
package/dist/factory.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../src/factory.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../src/factory.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,QAAQ,EAAE,oBAAoB,EAAE,MAAM,YAAS,CAAC;AAW9D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,QAAQ,CAAC,CAoFnB;AAED,eAAe,aAAa,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @happyvertical/documents - Document processing with multi-part structure
|
|
3
3
|
*
|
|
4
|
-
* Provides document processing for
|
|
4
|
+
* Provides document processing for PDF, Word (.docx) and PowerPoint (.pptx)
|
|
5
|
+
* (the latter two through the optional Kreuzberg engine) with support for:
|
|
5
6
|
* - Hierarchical document parts
|
|
6
7
|
* - Automatic format detection from URL or MIME type
|
|
7
8
|
* - Document management system detection (WordPress, CivicWeb, DocuShare)
|
|
@@ -21,8 +22,10 @@
|
|
|
21
22
|
*/
|
|
22
23
|
export { Document } from "./document.js";
|
|
23
24
|
export { fetchDocument } from "./factory.js";
|
|
25
|
+
export type { ExtractOfficeOptions, OfficeExtraction, OfficeFormat, OfficeResult, OfficeUnsupported, } from "./processors/office.js";
|
|
26
|
+
export { DOCX_MIME, DOCXProcessor, extractOffice, PPTX_MIME, PPTXProcessor, } from "./processors/office.js";
|
|
24
27
|
export { PDFProcessor } from "./processors/pdf.js";
|
|
25
|
-
export type { Document as DocumentType, DocumentImage, DocumentPart, DocumentProcessor, FetchDocumentOptions, } from "./types.js";
|
|
28
|
+
export type { Document as DocumentType, DocumentImage, DocumentPart, DocumentProcessor, FetchDocumentOptions, UnsupportedReason, } from "./types.js";
|
|
26
29
|
export { getTitleFromUrl } from "./utils.js";
|
|
27
30
|
/** @internal */
|
|
28
31
|
export declare const PACKAGE_VERSION_INITIALIZED = true;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAGH,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAY,CAAC;AAEtC,OAAO,EAAE,aAAa,EAAE,MAAM,cAAW,CAAC;AAG1C,YAAY,EACV,oBAAoB,EACpB,gBAAgB,EAChB,YAAY,EACZ,YAAY,EACZ,iBAAiB,GAClB,MAAM,wBAAqB,CAAC;AAC7B,OAAO,EACL,SAAS,EACT,aAAa,EACb,aAAa,EACb,SAAS,EACT,aAAa,GACd,MAAM,wBAAqB,CAAC;AAC7B,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAkB,CAAC;AAEhD,YAAY,EACV,QAAQ,IAAI,YAAY,EACxB,aAAa,EACb,YAAY,EACZ,iBAAiB,EACjB,oBAAoB,EACpB,iBAAiB,GAClB,MAAM,YAAS,CAAC;AAEjB,OAAO,EAAE,eAAe,EAAE,MAAM,YAAS,CAAC;AAE1C,gBAAgB;AAChB,eAAO,MAAM,2BAA2B,OAAO,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -177,6 +177,281 @@ function getTitleFromUrl(url, defaultTitle = "Document") {
|
|
|
177
177
|
}
|
|
178
178
|
}
|
|
179
179
|
//#endregion
|
|
180
|
+
//#region src/processors/office.ts
|
|
181
|
+
var DOCX_MIME = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
|
|
182
|
+
var PPTX_MIME = "application/vnd.openxmlformats-officedocument.presentationml.presentation";
|
|
183
|
+
var MIME = {
|
|
184
|
+
docx: DOCX_MIME,
|
|
185
|
+
pptx: PPTX_MIME
|
|
186
|
+
};
|
|
187
|
+
var KREUZBERG_PACKAGE = "@kreuzberg/node";
|
|
188
|
+
var kreuzbergPromise;
|
|
189
|
+
/** Lazily load the optional engine; resolves `null` when it is absent. */
|
|
190
|
+
function loadKreuzberg() {
|
|
191
|
+
kreuzbergPromise ??= import("@kreuzberg/node").then((mod) => mod, () => null);
|
|
192
|
+
return kreuzbergPromise;
|
|
193
|
+
}
|
|
194
|
+
var SLIDE_MARKER = (n) => `\n<<hv-slide:${n}>>\n`;
|
|
195
|
+
var SLIDE_MARKER_FORMAT = SLIDE_MARKER("{page_num}");
|
|
196
|
+
var SLIDE_MARKER_RE = /<<hv-slide:(\d+)>>/g;
|
|
197
|
+
var NOTES_RE = /(?:^|\n\n)Notes:\n\n([\s\S]*)$/;
|
|
198
|
+
function toImage(image, format) {
|
|
199
|
+
return {
|
|
200
|
+
id: randomUUID(),
|
|
201
|
+
url: `office-image:${format}/${image.imageIndex}`,
|
|
202
|
+
data: image.data,
|
|
203
|
+
altText: image.description ?? void 0,
|
|
204
|
+
position: image.imageIndex,
|
|
205
|
+
metadata: {
|
|
206
|
+
format: image.format,
|
|
207
|
+
width: image.width ?? void 0,
|
|
208
|
+
height: image.height ?? void 0,
|
|
209
|
+
...image.pageNumber ? { slide: image.pageNumber } : {}
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
function splitSlides(content) {
|
|
214
|
+
const slides = /* @__PURE__ */ new Map();
|
|
215
|
+
const matches = [...content.matchAll(SLIDE_MARKER_RE)];
|
|
216
|
+
matches.forEach((match, i) => {
|
|
217
|
+
const start = (match.index ?? 0) + match[0].length;
|
|
218
|
+
const end = matches[i + 1]?.index ?? content.length;
|
|
219
|
+
slides.set(Number(match[1]), content.slice(start, end).trim());
|
|
220
|
+
});
|
|
221
|
+
return slides;
|
|
222
|
+
}
|
|
223
|
+
function buildSlideParts(content, images) {
|
|
224
|
+
const slides = splitSlides(content);
|
|
225
|
+
if (slides.size === 0) {
|
|
226
|
+
const text = content.trim();
|
|
227
|
+
return text ? [{
|
|
228
|
+
id: randomUUID(),
|
|
229
|
+
title: "Presentation",
|
|
230
|
+
content: text,
|
|
231
|
+
type: "text",
|
|
232
|
+
metadata: {
|
|
233
|
+
source: "pptx",
|
|
234
|
+
slidesSplit: false
|
|
235
|
+
}
|
|
236
|
+
}] : [];
|
|
237
|
+
}
|
|
238
|
+
return [...slides.entries()].map(([slide, raw]) => {
|
|
239
|
+
const notesMatch = NOTES_RE.exec(raw);
|
|
240
|
+
const text = (notesMatch ? raw.slice(0, notesMatch.index) : raw).trim();
|
|
241
|
+
const notes = notesMatch?.[1]?.trim();
|
|
242
|
+
const slideImages = images.filter((img) => img.pageNumber === slide).map((img) => toImage(img, "pptx"));
|
|
243
|
+
const part = {
|
|
244
|
+
id: randomUUID(),
|
|
245
|
+
title: text.split("\n").find((line) => line.trim())?.trim() || `Slide ${slide}`,
|
|
246
|
+
content: text,
|
|
247
|
+
type: "text",
|
|
248
|
+
metadata: {
|
|
249
|
+
source: "pptx",
|
|
250
|
+
slide
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
if (notes) part.notes = notes;
|
|
254
|
+
if (slideImages.length > 0) part.images = slideImages;
|
|
255
|
+
return part;
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
function buildDocxParts(markdown, fallbackTitle, images) {
|
|
259
|
+
const root = {
|
|
260
|
+
level: 0,
|
|
261
|
+
title: "",
|
|
262
|
+
lines: [],
|
|
263
|
+
children: []
|
|
264
|
+
};
|
|
265
|
+
const stack = [root];
|
|
266
|
+
let inFence = false;
|
|
267
|
+
for (const line of markdown.split("\n")) {
|
|
268
|
+
if (/^\s*```/.test(line)) inFence = !inFence;
|
|
269
|
+
const heading = inFence ? null : /^(#{1,6})\s+(.*\S)\s*$/.exec(line);
|
|
270
|
+
if (!heading) {
|
|
271
|
+
stack[stack.length - 1].lines.push(line);
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
const section = {
|
|
275
|
+
level: heading[1].length,
|
|
276
|
+
title: heading[2],
|
|
277
|
+
lines: [],
|
|
278
|
+
children: []
|
|
279
|
+
};
|
|
280
|
+
while (stack[stack.length - 1].level >= section.level) stack.pop();
|
|
281
|
+
stack[stack.length - 1].children.push(section);
|
|
282
|
+
stack.push(section);
|
|
283
|
+
}
|
|
284
|
+
const toPart = (section) => {
|
|
285
|
+
const part = {
|
|
286
|
+
id: randomUUID(),
|
|
287
|
+
title: section.title,
|
|
288
|
+
content: section.lines.join("\n").trim(),
|
|
289
|
+
type: "markdown",
|
|
290
|
+
metadata: {
|
|
291
|
+
source: "docx",
|
|
292
|
+
headingLevel: section.level
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
if (section.children.length > 0) part.parts = section.children.map(toPart);
|
|
296
|
+
return part;
|
|
297
|
+
};
|
|
298
|
+
const parts = [];
|
|
299
|
+
const lead = root.lines.join("\n").trim();
|
|
300
|
+
if (lead || root.children.length === 0) {
|
|
301
|
+
if (lead) parts.push({
|
|
302
|
+
id: randomUUID(),
|
|
303
|
+
title: fallbackTitle,
|
|
304
|
+
content: lead,
|
|
305
|
+
type: "markdown",
|
|
306
|
+
metadata: {
|
|
307
|
+
source: "docx",
|
|
308
|
+
headingLevel: 0
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
parts.push(...root.children.map(toPart));
|
|
313
|
+
if (images.length > 0) {
|
|
314
|
+
if (parts.length === 0) parts.push({
|
|
315
|
+
id: randomUUID(),
|
|
316
|
+
title: fallbackTitle,
|
|
317
|
+
content: "",
|
|
318
|
+
type: "markdown",
|
|
319
|
+
metadata: {
|
|
320
|
+
source: "docx",
|
|
321
|
+
headingLevel: 0
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
parts[0].images = images;
|
|
325
|
+
}
|
|
326
|
+
return parts;
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Read a DOCX or PPTX from memory (or a path) through Kreuzberg.
|
|
330
|
+
*
|
|
331
|
+
* Returns `{ status: 'unsupported' }` -- never throws for that case -- when the
|
|
332
|
+
* optional `@kreuzberg/node` dependency is not installed. Corrupt or
|
|
333
|
+
* unreadable files reject with the engine's error.
|
|
334
|
+
*
|
|
335
|
+
* @param source - File bytes or a local file path
|
|
336
|
+
* @param format - `'docx'` or `'pptx'`
|
|
337
|
+
*/
|
|
338
|
+
async function extractOffice(source, format, options = {}) {
|
|
339
|
+
const kreuzberg = await loadKreuzberg();
|
|
340
|
+
if (!kreuzberg) return {
|
|
341
|
+
status: "unsupported",
|
|
342
|
+
code: "dependency-missing",
|
|
343
|
+
format,
|
|
344
|
+
dependency: KREUZBERG_PACKAGE,
|
|
345
|
+
reason: `Reading .${format} files requires the optional ${KREUZBERG_PACKAGE} package, which is not installed.`
|
|
346
|
+
};
|
|
347
|
+
const bytes = typeof source === "string" ? await promises.readFile(source) : Buffer.isBuffer(source) ? source : Buffer.from(source);
|
|
348
|
+
const wantImages = options.extractImages !== false;
|
|
349
|
+
const config = { images: { extractImages: wantImages } };
|
|
350
|
+
if (format === "pptx") config.pages = {
|
|
351
|
+
extractPages: true,
|
|
352
|
+
insertPageMarkers: true,
|
|
353
|
+
markerFormat: SLIDE_MARKER_FORMAT
|
|
354
|
+
};
|
|
355
|
+
else config.outputFormat = "markdown";
|
|
356
|
+
const result = await kreuzberg.extractBytes(bytes, MIME[format], config);
|
|
357
|
+
const content = result.content ?? "";
|
|
358
|
+
const rawImages = wantImages ? result.images ?? [] : [];
|
|
359
|
+
const title = result.metadata?.title?.trim() || void 0;
|
|
360
|
+
const parts = format === "pptx" ? buildSlideParts(content, rawImages) : buildDocxParts(content, title ?? options.title ?? "Document", rawImages.map((img) => toImage(img, "docx")));
|
|
361
|
+
return {
|
|
362
|
+
status: "ok",
|
|
363
|
+
format,
|
|
364
|
+
title,
|
|
365
|
+
parts,
|
|
366
|
+
metadata: {
|
|
367
|
+
processor: format,
|
|
368
|
+
engine: "kreuzberg",
|
|
369
|
+
extractedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
370
|
+
hasImages: rawImages.length > 0,
|
|
371
|
+
...format === "pptx" ? { slideCount: parts.filter((p) => p.metadata?.slide !== void 0).length } : {},
|
|
372
|
+
...title ? { title } : {}
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
var OfficeProcessor = class {
|
|
377
|
+
async process(url, options = {}) {
|
|
378
|
+
const baseDoc = await Document.create(url, {
|
|
379
|
+
...options,
|
|
380
|
+
type: options.type ?? MIME[this.format]
|
|
381
|
+
});
|
|
382
|
+
const cacheKey = `${baseDoc.localPath}.processed_${this.format}`;
|
|
383
|
+
const wantImages = options.extractImages !== false;
|
|
384
|
+
const cached = wantImages ? null : await getCached(cacheKey);
|
|
385
|
+
if (cached) try {
|
|
386
|
+
const parsed = JSON.parse(cached);
|
|
387
|
+
return {
|
|
388
|
+
url: baseDoc.url.toString(),
|
|
389
|
+
type: baseDoc.type,
|
|
390
|
+
parts: parsed.parts,
|
|
391
|
+
metadata: parsed.metadata || {}
|
|
392
|
+
};
|
|
393
|
+
} catch (error) {
|
|
394
|
+
console.warn(`Cached ${this.label} data corrupted, reprocessing`, error);
|
|
395
|
+
}
|
|
396
|
+
const result = await extractOffice(baseDoc.localPath, this.format, {
|
|
397
|
+
extractImages: wantImages,
|
|
398
|
+
title: getTitleFromUrl(url, this.label)
|
|
399
|
+
});
|
|
400
|
+
if (result.status === "unsupported") return {
|
|
401
|
+
url: baseDoc.url.toString(),
|
|
402
|
+
type: baseDoc.type,
|
|
403
|
+
parts: [],
|
|
404
|
+
metadata: {
|
|
405
|
+
processor: this.format,
|
|
406
|
+
unsupported: true
|
|
407
|
+
},
|
|
408
|
+
unsupported: {
|
|
409
|
+
code: result.code,
|
|
410
|
+
reason: result.reason,
|
|
411
|
+
dependency: result.dependency
|
|
412
|
+
}
|
|
413
|
+
};
|
|
414
|
+
const document = {
|
|
415
|
+
url: baseDoc.url.toString(),
|
|
416
|
+
type: baseDoc.type,
|
|
417
|
+
parts: result.parts,
|
|
418
|
+
metadata: result.metadata
|
|
419
|
+
};
|
|
420
|
+
if (!wantImages) await setCached(cacheKey, JSON.stringify(document));
|
|
421
|
+
return document;
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
/**
|
|
425
|
+
* Word (`.docx`) processor backed by Kreuzberg.
|
|
426
|
+
*
|
|
427
|
+
* Splits the text into heading-delimited parts (markdown). When the optional
|
|
428
|
+
* `@kreuzberg/node` package is missing, `process()` resolves with
|
|
429
|
+
* `document.unsupported` set and no parts.
|
|
430
|
+
*/
|
|
431
|
+
var DOCXProcessor = class extends OfficeProcessor {
|
|
432
|
+
format = "docx";
|
|
433
|
+
label = "Word Document";
|
|
434
|
+
supports(type) {
|
|
435
|
+
const t = type.toLowerCase();
|
|
436
|
+
return t === "application/vnd.openxmlformats-officedocument.wordprocessingml.document" || t.endsWith(".docx") || t === "docx";
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
/**
|
|
440
|
+
* PowerPoint (`.pptx`) processor backed by Kreuzberg.
|
|
441
|
+
*
|
|
442
|
+
* One part per slide, with `notes` carrying the speaker notes and `images`
|
|
443
|
+
* the pictures embedded on that slide. When the optional `@kreuzberg/node`
|
|
444
|
+
* package is missing, `process()` resolves with `document.unsupported` set.
|
|
445
|
+
*/
|
|
446
|
+
var PPTXProcessor = class extends OfficeProcessor {
|
|
447
|
+
format = "pptx";
|
|
448
|
+
label = "Presentation";
|
|
449
|
+
supports(type) {
|
|
450
|
+
const t = type.toLowerCase();
|
|
451
|
+
return t === "application/vnd.openxmlformats-officedocument.presentationml.presentation" || t.endsWith(".pptx") || t === "pptx";
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
//#endregion
|
|
180
455
|
//#region src/processors/pdf.ts
|
|
181
456
|
/**
|
|
182
457
|
* PDF Document Processor
|
|
@@ -280,7 +555,11 @@ var PDFProcessor = class {
|
|
|
280
555
|
/**
|
|
281
556
|
* Available document processors
|
|
282
557
|
*/
|
|
283
|
-
var processors = [
|
|
558
|
+
var processors = [
|
|
559
|
+
new PDFProcessor(),
|
|
560
|
+
new DOCXProcessor(),
|
|
561
|
+
new PPTXProcessor()
|
|
562
|
+
];
|
|
284
563
|
/**
|
|
285
564
|
* Fetch a document from a URL with automatic format detection
|
|
286
565
|
*
|
|
@@ -342,10 +621,12 @@ async function fetchDocument(url, options = {}) {
|
|
|
342
621
|
if (!type) {
|
|
343
622
|
const urlLower = url.toLowerCase();
|
|
344
623
|
if (urlLower.endsWith(".pdf") || urlLower.includes(".pdf?") || urlLower.includes(".pdf#")) type = "application/pdf";
|
|
624
|
+
else if (/\.docx(?:[?#]|$)/.test(urlLower)) type = DOCX_MIME;
|
|
625
|
+
else if (/\.pptx(?:[?#]|$)/.test(urlLower)) type = PPTX_MIME;
|
|
345
626
|
else type = getMimeType(url) ?? "";
|
|
346
627
|
}
|
|
347
628
|
const processor = processors.find((p) => p.supports(type));
|
|
348
|
-
if (!processor) throw new Error(`No processor available for document type: ${type}. Supported types: PDF (.pdf,
|
|
629
|
+
if (!processor) throw new Error(`No processor available for document type: ${type}. Supported types: PDF (.pdf), Word (.docx), PowerPoint (.pptx)`);
|
|
349
630
|
return processor.process(url, options);
|
|
350
631
|
}
|
|
351
632
|
//#endregion
|
|
@@ -353,6 +634,6 @@ async function fetchDocument(url, options = {}) {
|
|
|
353
634
|
/** @internal */
|
|
354
635
|
var PACKAGE_VERSION_INITIALIZED = true;
|
|
355
636
|
//#endregion
|
|
356
|
-
export { Document, PACKAGE_VERSION_INITIALIZED, PDFProcessor, fetchDocument, getTitleFromUrl };
|
|
637
|
+
export { DOCXProcessor, DOCX_MIME, Document, PACKAGE_VERSION_INITIALIZED, PDFProcessor, PPTXProcessor, PPTX_MIME, extractOffice, fetchDocument, getTitleFromUrl };
|
|
357
638
|
|
|
358
639
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"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 { randomUUID } from 'node:crypto';\nimport { promises as fs } from 'node:fs';\nimport { getCached, setCached } from '@happyvertical/files';\nimport { getPDFReader } from '@happyvertical/pdf';\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: randomUUID(),\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"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/document.ts","../src/utils.ts","../src/processors/office.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 { randomUUID } from 'node:crypto';\nimport { promises as fs } from 'node:fs';\nimport { getCached, setCached } from '@happyvertical/files';\nimport { Document as BaseDocument } from '../document';\nimport type {\n Document,\n DocumentImage,\n DocumentPart,\n DocumentProcessor,\n FetchDocumentOptions,\n UnsupportedReason,\n} from '../types';\nimport { getTitleFromUrl } from '../utils';\n\n/** Office formats read through Kreuzberg. */\nexport type OfficeFormat = 'docx' | 'pptx';\n\nexport const DOCX_MIME =\n 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';\nexport const PPTX_MIME =\n 'application/vnd.openxmlformats-officedocument.presentationml.presentation';\n\nconst MIME: Record<OfficeFormat, string> = {\n docx: DOCX_MIME,\n pptx: PPTX_MIME,\n};\n\nconst KREUZBERG_PACKAGE = '@kreuzberg/node';\n\n/** Options for {@link extractOffice}. */\nexport interface ExtractOfficeOptions {\n /** Return embedded images (bytes in memory). @default true */\n extractImages?: boolean;\n /** Fallback title when the file carries none. */\n title?: string;\n}\n\n/** A successful extraction. */\nexport interface OfficeExtraction {\n status: 'ok';\n format: OfficeFormat;\n /** Document title from the file's properties, when present. */\n title?: string;\n /**\n * DOCX: one part per top-level heading section (nested by heading level),\n * `type: 'markdown'`. PPTX: one part per slide in order, `type: 'text'`,\n * with `notes` holding that slide's speaker notes.\n */\n parts: DocumentPart[];\n metadata: Record<string, unknown>;\n}\n\n/** The Kreuzberg engine is not installed; nothing was read. */\nexport interface OfficeUnsupported extends UnsupportedReason {\n status: 'unsupported';\n format: OfficeFormat;\n}\n\nexport type OfficeResult = OfficeExtraction | OfficeUnsupported;\n\n// Structural subset of @kreuzberg/node so the optional module is only a\n// runtime concern.\ninterface KreuzbergImage {\n data: Uint8Array;\n format: string;\n imageIndex: number;\n pageNumber?: number | null;\n width?: number | null;\n height?: number | null;\n description?: string | null;\n}\ninterface KreuzbergResult {\n content?: string | null;\n metadata?: Record<string, unknown> & { title?: string | null };\n images?: KreuzbergImage[] | null;\n}\ninterface KreuzbergModule {\n extractBytes(\n data: Buffer,\n mimeType: string,\n config?: Record<string, unknown> | null,\n ): Promise<KreuzbergResult>;\n}\n\nlet kreuzbergPromise: Promise<KreuzbergModule | null> | undefined;\n\n/** Lazily load the optional engine; resolves `null` when it is absent. */\nfunction loadKreuzberg(): Promise<KreuzbergModule | null> {\n kreuzbergPromise ??= import('@kreuzberg/node').then(\n (mod) => mod as unknown as KreuzbergModule,\n () => null,\n );\n return kreuzbergPromise;\n}\n\n/** @internal test hook: forget the cached engine lookup. */\nexport function resetOfficeEngineForTests(): void {\n kreuzbergPromise = undefined;\n}\n\nconst SLIDE_MARKER = (n: string) => `\\n<<hv-slide:${n}>>\\n`;\nconst SLIDE_MARKER_FORMAT = SLIDE_MARKER('{page_num}');\nconst SLIDE_MARKER_RE = /<<hv-slide:(\\d+)>>/g;\n// Kreuzberg appends a slide's speaker notes after a \"Notes:\" paragraph.\nconst NOTES_RE = /(?:^|\\n\\n)Notes:\\n\\n([\\s\\S]*)$/;\n\nfunction toImage(image: KreuzbergImage, format: OfficeFormat): DocumentImage {\n return {\n id: randomUUID(),\n url: `office-image:${format}/${image.imageIndex}`,\n data: image.data,\n altText: image.description ?? undefined,\n position: image.imageIndex,\n metadata: {\n format: image.format,\n width: image.width ?? undefined,\n height: image.height ?? undefined,\n ...(image.pageNumber ? { slide: image.pageNumber } : {}),\n },\n };\n}\n\nfunction splitSlides(content: string): Map<number, string> {\n const slides = new Map<number, string>();\n const matches = [...content.matchAll(SLIDE_MARKER_RE)];\n matches.forEach((match, i) => {\n const start = (match.index ?? 0) + match[0].length;\n const end = matches[i + 1]?.index ?? content.length;\n slides.set(Number(match[1]), content.slice(start, end).trim());\n });\n return slides;\n}\n\nfunction buildSlideParts(\n content: string,\n images: KreuzbergImage[],\n): DocumentPart[] {\n const slides = splitSlides(content);\n if (slides.size === 0) {\n // Engine did not honour page markers: still return the text, unsplit.\n const text = content.trim();\n return text\n ? [\n {\n id: randomUUID(),\n title: 'Presentation',\n content: text,\n type: 'text',\n metadata: { source: 'pptx', slidesSplit: false },\n },\n ]\n : [];\n }\n return [...slides.entries()].map(([slide, raw]) => {\n const notesMatch = NOTES_RE.exec(raw);\n const text = (notesMatch ? raw.slice(0, notesMatch.index) : raw).trim();\n const notes = notesMatch?.[1]?.trim();\n const slideImages = images\n .filter((img) => img.pageNumber === slide)\n .map((img) => toImage(img, 'pptx'));\n const part: DocumentPart = {\n id: randomUUID(),\n title:\n text\n .split('\\n')\n .find((line) => line.trim())\n ?.trim() || `Slide ${slide}`,\n content: text,\n type: 'text',\n metadata: { source: 'pptx', slide },\n };\n if (notes) part.notes = notes;\n if (slideImages.length > 0) part.images = slideImages;\n return part;\n });\n}\n\ninterface Section {\n level: number;\n title: string;\n lines: string[];\n children: Section[];\n}\n\nfunction buildDocxParts(\n markdown: string,\n fallbackTitle: string,\n images: DocumentImage[],\n): DocumentPart[] {\n const root: Section = { level: 0, title: '', lines: [], children: [] };\n const stack: Section[] = [root];\n let inFence = false;\n for (const line of markdown.split('\\n')) {\n if (/^\\s*```/.test(line)) inFence = !inFence;\n const heading = inFence ? null : /^(#{1,6})\\s+(.*\\S)\\s*$/.exec(line);\n if (!heading) {\n stack[stack.length - 1].lines.push(line);\n continue;\n }\n const section: Section = {\n level: heading[1].length,\n title: heading[2],\n lines: [],\n children: [],\n };\n while (stack[stack.length - 1].level >= section.level) stack.pop();\n stack[stack.length - 1].children.push(section);\n stack.push(section);\n }\n\n const toPart = (section: Section): DocumentPart => {\n const part: DocumentPart = {\n id: randomUUID(),\n title: section.title,\n content: section.lines.join('\\n').trim(),\n type: 'markdown',\n metadata: { source: 'docx', headingLevel: section.level },\n };\n if (section.children.length > 0) part.parts = section.children.map(toPart);\n return part;\n };\n\n const parts: DocumentPart[] = [];\n const lead = root.lines.join('\\n').trim();\n if (lead || root.children.length === 0) {\n if (lead) {\n parts.push({\n id: randomUUID(),\n title: fallbackTitle,\n content: lead,\n type: 'markdown',\n metadata: { source: 'docx', headingLevel: 0 },\n });\n }\n }\n parts.push(...root.children.map(toPart));\n if (images.length > 0) {\n // An image-only document still needs a part to carry its pictures.\n if (parts.length === 0) {\n parts.push({\n id: randomUUID(),\n title: fallbackTitle,\n content: '',\n type: 'markdown',\n metadata: { source: 'docx', headingLevel: 0 },\n });\n }\n parts[0].images = images;\n }\n return parts;\n}\n\n/**\n * Read a DOCX or PPTX from memory (or a path) through Kreuzberg.\n *\n * Returns `{ status: 'unsupported' }` -- never throws for that case -- when the\n * optional `@kreuzberg/node` dependency is not installed. Corrupt or\n * unreadable files reject with the engine's error.\n *\n * @param source - File bytes or a local file path\n * @param format - `'docx'` or `'pptx'`\n */\nexport async function extractOffice(\n source: Buffer | Uint8Array | string,\n format: OfficeFormat,\n options: ExtractOfficeOptions = {},\n): Promise<OfficeResult> {\n const kreuzberg = await loadKreuzberg();\n if (!kreuzberg) {\n return {\n status: 'unsupported',\n code: 'dependency-missing',\n format,\n dependency: KREUZBERG_PACKAGE,\n reason: `Reading .${format} files requires the optional ${KREUZBERG_PACKAGE} package, which is not installed.`,\n };\n }\n\n const bytes =\n typeof source === 'string'\n ? await fs.readFile(source)\n : Buffer.isBuffer(source)\n ? source\n : Buffer.from(source);\n const wantImages = options.extractImages !== false;\n const config: Record<string, unknown> = {\n images: { extractImages: wantImages },\n };\n if (format === 'pptx') {\n config.pages = {\n extractPages: true,\n insertPageMarkers: true,\n markerFormat: SLIDE_MARKER_FORMAT,\n };\n } else {\n config.outputFormat = 'markdown';\n }\n\n const result = await kreuzberg.extractBytes(bytes, MIME[format], config);\n const content = result.content ?? '';\n const rawImages = wantImages ? (result.images ?? []) : [];\n const title = result.metadata?.title?.trim() || undefined;\n\n const parts =\n format === 'pptx'\n ? buildSlideParts(content, rawImages)\n : buildDocxParts(\n content,\n title ?? options.title ?? 'Document',\n rawImages.map((img) => toImage(img, 'docx')),\n );\n\n return {\n status: 'ok',\n format,\n title,\n parts,\n metadata: {\n processor: format,\n engine: 'kreuzberg',\n extractedAt: new Date().toISOString(),\n hasImages: rawImages.length > 0,\n ...(format === 'pptx'\n ? {\n slideCount: parts.filter((p) => p.metadata?.slide !== undefined)\n .length,\n }\n : {}),\n ...(title ? { title } : {}),\n },\n };\n}\n\nabstract class OfficeProcessor implements DocumentProcessor {\n protected abstract readonly format: OfficeFormat;\n protected abstract readonly label: string;\n\n abstract supports(type: string): boolean;\n\n async process(\n url: string,\n options: FetchDocumentOptions = {},\n ): Promise<Document> {\n const baseDoc = await BaseDocument.create(url, {\n ...options,\n type: options.type ?? MIME[this.format],\n });\n\n const cacheKey = `${baseDoc.localPath}.processed_${this.format}`;\n const wantImages = options.extractImages !== false;\n // Images are held in memory, so only text results are cached.\n const cached = wantImages ? null : 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 console.warn(\n `Cached ${this.label} data corrupted, reprocessing`,\n error,\n );\n }\n }\n\n const result = await extractOffice(baseDoc.localPath, this.format, {\n extractImages: wantImages,\n title: getTitleFromUrl(url, this.label),\n });\n\n if (result.status === 'unsupported') {\n return {\n url: baseDoc.url.toString(),\n type: baseDoc.type,\n parts: [],\n metadata: { processor: this.format, unsupported: true },\n unsupported: {\n code: result.code,\n reason: result.reason,\n dependency: result.dependency,\n },\n };\n }\n\n const document: Document = {\n url: baseDoc.url.toString(),\n type: baseDoc.type,\n parts: result.parts,\n metadata: result.metadata,\n };\n if (!wantImages) await setCached(cacheKey, JSON.stringify(document));\n return document;\n }\n}\n\n/**\n * Word (`.docx`) processor backed by Kreuzberg.\n *\n * Splits the text into heading-delimited parts (markdown). When the optional\n * `@kreuzberg/node` package is missing, `process()` resolves with\n * `document.unsupported` set and no parts.\n */\nexport class DOCXProcessor extends OfficeProcessor {\n protected readonly format = 'docx' as const;\n protected readonly label = 'Word Document';\n\n supports(type: string): boolean {\n const t = type.toLowerCase();\n return t === DOCX_MIME || t.endsWith('.docx') || t === 'docx';\n }\n}\n\n/**\n * PowerPoint (`.pptx`) processor backed by Kreuzberg.\n *\n * One part per slide, with `notes` carrying the speaker notes and `images`\n * the pictures embedded on that slide. When the optional `@kreuzberg/node`\n * package is missing, `process()` resolves with `document.unsupported` set.\n */\nexport class PPTXProcessor extends OfficeProcessor {\n protected readonly format = 'pptx' as const;\n protected readonly label = 'Presentation';\n\n supports(type: string): boolean {\n const t = type.toLowerCase();\n return t === PPTX_MIME || t.endsWith('.pptx') || t === 'pptx';\n }\n}\n","import { randomUUID } from 'node:crypto';\nimport { promises as fs } from 'node:fs';\nimport { getCached, setCached } from '@happyvertical/files';\nimport { getPDFReader } from '@happyvertical/pdf';\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: randomUUID(),\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 {\n DOCX_MIME,\n DOCXProcessor,\n PPTX_MIME,\n PPTXProcessor,\n} from './processors/office';\nimport { PDFProcessor } from './processors/pdf';\nimport type { Document, FetchDocumentOptions } from './types';\n\n/**\n * Available document processors\n */\nconst processors = [\n new PDFProcessor(),\n new DOCXProcessor(),\n new PPTXProcessor(),\n];\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 if (/\\.docx(?:[?#]|$)/.test(urlLower)) {\n type = DOCX_MIME;\n } else if (/\\.pptx(?:[?#]|$)/.test(urlLower)) {\n type = PPTX_MIME;\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), Word (.docx), PowerPoint (.pptx)`,\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 PDF, Word (.docx) and PowerPoint (.pptx)\n * (the latter two through the optional Kreuzberg engine) 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 type {\n ExtractOfficeOptions,\n OfficeExtraction,\n OfficeFormat,\n OfficeResult,\n OfficeUnsupported,\n} from './processors/office';\nexport {\n DOCX_MIME,\n DOCXProcessor,\n extractOffice,\n PPTX_MIME,\n PPTXProcessor,\n} from './processors/office';\nexport { PDFProcessor } from './processors/pdf';\n// Types\nexport type {\n Document as DocumentType,\n DocumentImage,\n DocumentPart,\n DocumentProcessor,\n FetchDocumentOptions,\n UnsupportedReason,\n} from './types';\n// Utilities\nexport { getTitleFromUrl } from './utils';\n\n/** @internal */\nexport const PACKAGE_VERSION_INITIALIZED = true;\n"],"mappings":"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"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { Document, DocumentPart, DocumentProcessor, FetchDocumentOptions, UnsupportedReason } from "../types.js";
|
|
2
|
+
/** Office formats read through Kreuzberg. */
|
|
3
|
+
export type OfficeFormat = 'docx' | 'pptx';
|
|
4
|
+
export declare const DOCX_MIME = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
|
|
5
|
+
export declare const PPTX_MIME = "application/vnd.openxmlformats-officedocument.presentationml.presentation";
|
|
6
|
+
/** Options for {@link extractOffice}. */
|
|
7
|
+
export interface ExtractOfficeOptions {
|
|
8
|
+
/** Return embedded images (bytes in memory). @default true */
|
|
9
|
+
extractImages?: boolean;
|
|
10
|
+
/** Fallback title when the file carries none. */
|
|
11
|
+
title?: string;
|
|
12
|
+
}
|
|
13
|
+
/** A successful extraction. */
|
|
14
|
+
export interface OfficeExtraction {
|
|
15
|
+
status: 'ok';
|
|
16
|
+
format: OfficeFormat;
|
|
17
|
+
/** Document title from the file's properties, when present. */
|
|
18
|
+
title?: string;
|
|
19
|
+
/**
|
|
20
|
+
* DOCX: one part per top-level heading section (nested by heading level),
|
|
21
|
+
* `type: 'markdown'`. PPTX: one part per slide in order, `type: 'text'`,
|
|
22
|
+
* with `notes` holding that slide's speaker notes.
|
|
23
|
+
*/
|
|
24
|
+
parts: DocumentPart[];
|
|
25
|
+
metadata: Record<string, unknown>;
|
|
26
|
+
}
|
|
27
|
+
/** The Kreuzberg engine is not installed; nothing was read. */
|
|
28
|
+
export interface OfficeUnsupported extends UnsupportedReason {
|
|
29
|
+
status: 'unsupported';
|
|
30
|
+
format: OfficeFormat;
|
|
31
|
+
}
|
|
32
|
+
export type OfficeResult = OfficeExtraction | OfficeUnsupported;
|
|
33
|
+
/** @internal test hook: forget the cached engine lookup. */
|
|
34
|
+
export declare function resetOfficeEngineForTests(): void;
|
|
35
|
+
/**
|
|
36
|
+
* Read a DOCX or PPTX from memory (or a path) through Kreuzberg.
|
|
37
|
+
*
|
|
38
|
+
* Returns `{ status: 'unsupported' }` -- never throws for that case -- when the
|
|
39
|
+
* optional `@kreuzberg/node` dependency is not installed. Corrupt or
|
|
40
|
+
* unreadable files reject with the engine's error.
|
|
41
|
+
*
|
|
42
|
+
* @param source - File bytes or a local file path
|
|
43
|
+
* @param format - `'docx'` or `'pptx'`
|
|
44
|
+
*/
|
|
45
|
+
export declare function extractOffice(source: Buffer | Uint8Array | string, format: OfficeFormat, options?: ExtractOfficeOptions): Promise<OfficeResult>;
|
|
46
|
+
declare abstract class OfficeProcessor implements DocumentProcessor {
|
|
47
|
+
protected abstract readonly format: OfficeFormat;
|
|
48
|
+
protected abstract readonly label: string;
|
|
49
|
+
abstract supports(type: string): boolean;
|
|
50
|
+
process(url: string, options?: FetchDocumentOptions): Promise<Document>;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Word (`.docx`) processor backed by Kreuzberg.
|
|
54
|
+
*
|
|
55
|
+
* Splits the text into heading-delimited parts (markdown). When the optional
|
|
56
|
+
* `@kreuzberg/node` package is missing, `process()` resolves with
|
|
57
|
+
* `document.unsupported` set and no parts.
|
|
58
|
+
*/
|
|
59
|
+
export declare class DOCXProcessor extends OfficeProcessor {
|
|
60
|
+
protected readonly format: "docx";
|
|
61
|
+
protected readonly label = "Word Document";
|
|
62
|
+
supports(type: string): boolean;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* PowerPoint (`.pptx`) processor backed by Kreuzberg.
|
|
66
|
+
*
|
|
67
|
+
* One part per slide, with `notes` carrying the speaker notes and `images`
|
|
68
|
+
* the pictures embedded on that slide. When the optional `@kreuzberg/node`
|
|
69
|
+
* package is missing, `process()` resolves with `document.unsupported` set.
|
|
70
|
+
*/
|
|
71
|
+
export declare class PPTXProcessor extends OfficeProcessor {
|
|
72
|
+
protected readonly format: "pptx";
|
|
73
|
+
protected readonly label = "Presentation";
|
|
74
|
+
supports(type: string): boolean;
|
|
75
|
+
}
|
|
76
|
+
export {};
|
|
77
|
+
//# sourceMappingURL=office.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"office.d.ts","sourceRoot":"","sources":["../../src/processors/office.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,QAAQ,EAER,YAAY,EACZ,iBAAiB,EACjB,oBAAoB,EACpB,iBAAiB,EAClB,MAAM,aAAU,CAAC;AAGlB,6CAA6C;AAC7C,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,MAAM,CAAC;AAE3C,eAAO,MAAM,SAAS,4EACqD,CAAC;AAC5E,eAAO,MAAM,SAAS,8EACuD,CAAC;AAS9E,yCAAyC;AACzC,MAAM,WAAW,oBAAoB;IACnC,8DAA8D;IAC9D,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,iDAAiD;IACjD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,+BAA+B;AAC/B,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,IAAI,CAAC;IACb,MAAM,EAAE,YAAY,CAAC;IACrB,+DAA+D;IAC/D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,+DAA+D;AAC/D,MAAM,WAAW,iBAAkB,SAAQ,iBAAiB;IAC1D,MAAM,EAAE,aAAa,CAAC;IACtB,MAAM,EAAE,YAAY,CAAC;CACtB;AAED,MAAM,MAAM,YAAY,GAAG,gBAAgB,GAAG,iBAAiB,CAAC;AAqChE,4DAA4D;AAC5D,wBAAgB,yBAAyB,IAAI,IAAI,CAEhD;AA0JD;;;;;;;;;GASG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,EACpC,MAAM,EAAE,YAAY,EACpB,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,YAAY,CAAC,CAiEvB;AAED,uBAAe,eAAgB,YAAW,iBAAiB;IACzD,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IACjD,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAE1C,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAElC,OAAO,CACX,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,QAAQ,CAAC;CAuDrB;AAED;;;;;;GAMG;AACH,qBAAa,aAAc,SAAQ,eAAe;IAChD,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAG,MAAM,CAAU;IAC5C,SAAS,CAAC,QAAQ,CAAC,KAAK,mBAAmB;IAE3C,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;CAIhC;AAED;;;;;;GAMG;AACH,qBAAa,aAAc,SAAQ,eAAe;IAChD,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAG,MAAM,CAAU;IAC5C,SAAS,CAAC,QAAQ,CAAC,KAAK,kBAAkB;IAE1C,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;CAIhC"}
|
package/dist/types.d.ts
CHANGED
|
@@ -20,6 +20,11 @@ export interface DocumentImage {
|
|
|
20
20
|
* Local filesystem path if image has been downloaded
|
|
21
21
|
*/
|
|
22
22
|
localPath?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Raw image bytes when the extractor returned them in memory
|
|
25
|
+
* (Office documents). `url` is then a synthetic `office-image:` reference.
|
|
26
|
+
*/
|
|
27
|
+
data?: Uint8Array;
|
|
23
28
|
/**
|
|
24
29
|
* Alt text from HTML or PDF metadata
|
|
25
30
|
*/
|
|
@@ -70,6 +75,10 @@ export interface DocumentPart {
|
|
|
70
75
|
* Images contained in this part
|
|
71
76
|
*/
|
|
72
77
|
images?: DocumentImage[];
|
|
78
|
+
/**
|
|
79
|
+
* Speaker notes for this part (PPTX slides)
|
|
80
|
+
*/
|
|
81
|
+
notes?: string;
|
|
73
82
|
/**
|
|
74
83
|
* Additional metadata for this part
|
|
75
84
|
*/
|
|
@@ -99,6 +108,24 @@ export interface Document {
|
|
|
99
108
|
* Document-level metadata
|
|
100
109
|
*/
|
|
101
110
|
metadata?: Record<string, any>;
|
|
111
|
+
/**
|
|
112
|
+
* Set when this processor cannot read the document in the current
|
|
113
|
+
* environment (for example the optional `@kreuzberg/node` engine is not
|
|
114
|
+
* installed). `parts` is empty; callers should degrade gracefully rather
|
|
115
|
+
* than treat it as a failure of the file.
|
|
116
|
+
*/
|
|
117
|
+
unsupported?: UnsupportedReason;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Why a document could not be processed.
|
|
121
|
+
*/
|
|
122
|
+
export interface UnsupportedReason {
|
|
123
|
+
/** Machine-readable code. */
|
|
124
|
+
code: 'dependency-missing';
|
|
125
|
+
/** Human-readable explanation. */
|
|
126
|
+
reason: string;
|
|
127
|
+
/** The missing optional dependency, when applicable. */
|
|
128
|
+
dependency?: string;
|
|
102
129
|
}
|
|
103
130
|
/**
|
|
104
131
|
* Options for fetching a document
|
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;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/metadata.json
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@happyvertical/documents",
|
|
3
|
-
"version": "0.111.
|
|
4
|
-
"description": "Multi-part document processing with support for PDF, HTML, and Markdown",
|
|
3
|
+
"version": "0.111.5",
|
|
4
|
+
"description": "Multi-part document processing with support for PDF, Word, PowerPoint, HTML, and Markdown",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -36,11 +36,14 @@
|
|
|
36
36
|
}
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@happyvertical/files": "0.111.
|
|
39
|
+
"@happyvertical/files": "0.111.5",
|
|
40
40
|
"@happyvertical/ocr": "^0.61.1",
|
|
41
41
|
"@happyvertical/pdf": "^0.65.9",
|
|
42
42
|
"@happyvertical/spider": "^1.1",
|
|
43
|
-
"@happyvertical/utils": "0.111.
|
|
43
|
+
"@happyvertical/utils": "0.111.5"
|
|
44
|
+
},
|
|
45
|
+
"optionalDependencies": {
|
|
46
|
+
"@kreuzberg/node": "^4.1.1"
|
|
44
47
|
},
|
|
45
48
|
"devDependencies": {
|
|
46
49
|
"@types/node": "25.0.10",
|
|
@@ -51,6 +54,8 @@
|
|
|
51
54
|
"keywords": [
|
|
52
55
|
"documents",
|
|
53
56
|
"pdf",
|
|
57
|
+
"docx",
|
|
58
|
+
"pptx",
|
|
54
59
|
"html",
|
|
55
60
|
"markdown",
|
|
56
61
|
"ocr",
|