@substrat-run/attachment-extractors 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -3
- package/dist/index.d.ts +98 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1345 -0
- package/dist/index.js.map +1 -0
- package/package.json +12 -11
package/README.md
CHANGED
|
@@ -30,13 +30,26 @@ extractors" a valid configuration, not a broken one.
|
|
|
30
30
|
| Extractor | Reads |
|
|
31
31
|
|---|---|
|
|
32
32
|
| `textExtractor` | `text/*` other than HTML, and `.txt` `.md` `.csv` `.tsv` when the type says nothing |
|
|
33
|
-
| `htmlExtractor` | `text/html`, `application/xhtml+xml`: the visible text,
|
|
33
|
+
| `htmlExtractor` | `text/html`, `application/xhtml+xml`: the visible text, each construct ended where a browser's tokenizer ends it; script, style, template content and comments dropped |
|
|
34
34
|
| `docxExtractor` | Word documents: the body, then footnotes, endnotes, headers and footers |
|
|
35
35
|
| `xlsxExtractor` | Spreadsheets: shared and inline strings, never a cell's number |
|
|
36
36
|
| `pptxExtractor` | Presentations: slides in order, then speaker notes |
|
|
37
37
|
|
|
38
38
|
There is no PDF extractor yet, and nothing is OCR'd.
|
|
39
39
|
|
|
40
|
+
The HTML extractor **never indexes text the HTML parser keeps out of the rendered document**:
|
|
41
|
+
comments, `script`, `style`, the other elements whose content a browser does not render, and
|
|
42
|
+
`template` content. Where it does not model how a browser parses, it **under-indexes** rather
|
|
43
|
+
than guess. It indexes nothing inside a `select` or an inline `svg` or `math`, and nothing after
|
|
44
|
+
a `frameset` unless it can prove a browser ignores it (and an honoured frameset discards the
|
|
45
|
+
body, so what was read in it is dropped too). After such a context it resumes only where it is certain a browser ends
|
|
46
|
+
it, and otherwise indexes nothing more of the file. Its tests hold this against parse5, a
|
|
47
|
+
browser-grade HTML parser.
|
|
48
|
+
|
|
49
|
+
It follows the parser, not the renderer. It does not evaluate the `hidden` attribute, CSS or
|
|
50
|
+
interactive state (a closed `<details>`), so text those hide **is** indexed. That is never more
|
|
51
|
+
than a searcher can read: search is gated by the same read permission as opening the file.
|
|
52
|
+
|
|
40
53
|
## Bounds
|
|
41
54
|
|
|
42
55
|
The bounds that protect the parsing process live here. `maxInputBytes` (32 MiB) is declared to
|
|
@@ -47,8 +60,9 @@ extractor returns.
|
|
|
47
60
|
|
|
48
61
|
Every parser is a single forward scan over a capped input, so its CPU is bounded by the code
|
|
49
62
|
rather than the file. The kernel's time budget is cooperative. The parsers check the `signal`
|
|
50
|
-
they are handed and yield between zip entries and
|
|
51
|
-
|
|
63
|
+
they are handed and yield between zip entries and at least every `EXTRACTION_STRIDE` (the
|
|
64
|
+
kernel's, 256 K) units of work — every search included, so a single long comment or tag is
|
|
65
|
+
no exception — and an aborted extraction stops within one stride.
|
|
52
66
|
|
|
53
67
|
## License
|
|
54
68
|
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@substrat-run/attachment-extractors` — the parsers behind the kernel's attachment
|
|
3
|
+
* extractor seam (#1575, K-43).
|
|
4
|
+
*
|
|
5
|
+
* **The kernel indexes attachment text; it does not parse file formats** (K-43). It owns the
|
|
6
|
+
* text table, its index, the job, the outcome states, the output cap and the gated search,
|
|
7
|
+
* and defines one seam, `AttachmentExtractor`. Everything that reads a file FORMAT lives
|
|
8
|
+
* here, and the kernel and the adapters never import this package (`lint:deps` refuses it):
|
|
9
|
+
* whoever constructs the host passes `defaultAttachmentExtractors()` in, or a list of its
|
|
10
|
+
* own. Untrusted-input parsing belongs at the edge, not beside the permission checker.
|
|
11
|
+
*
|
|
12
|
+
* Best-effort by content type, and explicit about what it does not do:
|
|
13
|
+
*
|
|
14
|
+
* - **text** (`text/*` other than HTML): decoded as the declared charset, UTF-8 otherwise.
|
|
15
|
+
* - **html** (`text/html`, `application/xhtml+xml`): tags stripped, each construct ended where
|
|
16
|
+
* the HTML tokenizer ends it; comments, `script`, `style`, the other raw-text elements a
|
|
17
|
+
* browser does not render, and `template` content, dropped — an unclosed one through to the
|
|
18
|
+
* end; entities decoded. **Its contract: it never indexes text the HTML parser keeps out of
|
|
19
|
+
* the rendered document**, and it may under-index what it does not model — inline SVG and
|
|
20
|
+
* MathML, `select`, a honoured frameset — indexing nothing inside them, and nothing after one
|
|
21
|
+
* whose end it cannot be sure of (`htmlText` lists the rules). It does not evaluate the
|
|
22
|
+
* `hidden` attribute, CSS or interactive state, so text those hide is indexed.
|
|
23
|
+
* - **docx / xlsx / pptx**: an OOXML file is a zip of XML parts, so this reads the zip's
|
|
24
|
+
* central directory, inflates only the parts that carry text, and keeps the text of
|
|
25
|
+
* their `t` elements. The inflate is the web-standard `DecompressionStream('deflate-raw')`,
|
|
26
|
+
* present in Node and workerd alike, so the whole format costs no dependency.
|
|
27
|
+
* - **No PDF yet, and no OCR.** A host given no extractor for a type records `unsupported`
|
|
28
|
+
* with that reason — a legible outcome, never "indexed, no match".
|
|
29
|
+
*
|
|
30
|
+
* ## The bounds this package owns, and why
|
|
31
|
+
*
|
|
32
|
+
* The bounds that protect the PROCESS doing the parsing live here (K-43); the one that
|
|
33
|
+
* protects the scope — the output cap — is the kernel's, enforced on whatever comes back.
|
|
34
|
+
*
|
|
35
|
+
* - `maxInputBytes` is declared on each extractor, so the kernel can refuse an oversized
|
|
36
|
+
* file on its RECORDED size before fetching a byte; each extractor checks it again.
|
|
37
|
+
* - `maxInflatedBytes` is the zip-bomb cap: a counter on the bytes the inflater actually
|
|
38
|
+
* produces, across every part of one file. An entry's declared size is checked first as
|
|
39
|
+
* a cheap refusal, and never trusted, because a bomb is exactly a file that lies about it.
|
|
40
|
+
*
|
|
41
|
+
* A file that is not what it says, or breaks a bound, is answered `{ failed }` with a
|
|
42
|
+
* content-free reason — never a throw, and never a quote of the file: the reason lands in
|
|
43
|
+
* the scope, and the content it would quote is what the read gate protects.
|
|
44
|
+
*
|
|
45
|
+
* ## CPU, bounded by construction — and stoppable
|
|
46
|
+
*
|
|
47
|
+
* Every parser here is a LINEAR scan (no regular expression that can rescan from each
|
|
48
|
+
* opener), over an input with a fixed cap, so the worst case is a property of the code rather
|
|
49
|
+
* than of the file. Per format, at the default bounds:
|
|
50
|
+
*
|
|
51
|
+
* - **text**: a decode of at most 4 × the kernel's output cap (2 MiB).
|
|
52
|
+
* - **html**: a decode of at most 16 × the output cap (8 MiB), one forward scan of it, one
|
|
53
|
+
* entity pass over what survives.
|
|
54
|
+
* - **docx / xlsx / pptx**: the central directory (at most 20 000 entries), inflate and decode
|
|
55
|
+
* of at most `maxInflatedBytes` (16 MiB) in total, and one forward scan of each inflated
|
|
56
|
+
* part; parts stop being read once the text collected is twice the output cap.
|
|
57
|
+
*
|
|
58
|
+
* Each extractor honours the kernel's time budget cooperatively (`Pace`): it checks the
|
|
59
|
+
* `signal`, and yields a turn of the event loop so the kernel's timer can fire, between zip
|
|
60
|
+
* entries and at least every `EXTRACTION_STRIDE` (the kernel's, 256 Ki) units of work. That
|
|
61
|
+
* holds for every pass over the content — decoding, inflating, the zip directory, the entity
|
|
62
|
+
* pass, and each search for a construct's end, a native `indexOf` included, which is cut to
|
|
63
|
+
* the window left before the next check. An aborted extraction answers
|
|
64
|
+
* `{ failed: 'the extraction was aborted' }` within one stride. The steps between two checks
|
|
65
|
+
* that are NOT cut to a stride are copies (joining the text collected) and the name tests
|
|
66
|
+
* over the zip directory, at most 20 000 names of at most 255 bytes each.
|
|
67
|
+
*/
|
|
68
|
+
import { type AttachmentExtractor } from '@substrat-run/kernel';
|
|
69
|
+
/** The bounds that protect the parsing process (K-43). */
|
|
70
|
+
export interface ExtractorBounds {
|
|
71
|
+
/** Largest file an extractor will read, declared to the kernel and checked again here. */
|
|
72
|
+
readonly maxInputBytes: number;
|
|
73
|
+
/** Bytes the inflater may produce across every part of one file (the zip-bomb cap). */
|
|
74
|
+
readonly maxInflatedBytes: number;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* The defaults. 16 MiB of inflated XML is far more than the text of any ordinary office
|
|
78
|
+
* document, and small enough that decoding it fits a Worker's memory.
|
|
79
|
+
*/
|
|
80
|
+
export declare const DEFAULT_EXTRACTOR_BOUNDS: ExtractorBounds;
|
|
81
|
+
/** `text/*` other than HTML — and `.txt`, `.md`, `.csv`, `.tsv` when the type says nothing. */
|
|
82
|
+
export declare function textExtractor(bounds?: ExtractorBounds): AttachmentExtractor;
|
|
83
|
+
/** HTML and XHTML — the text a reader of the page would see. */
|
|
84
|
+
export declare function htmlExtractor(bounds?: ExtractorBounds): AttachmentExtractor;
|
|
85
|
+
/** Word documents: the body, then footnotes, endnotes, headers and footers. */
|
|
86
|
+
export declare const docxExtractor: (bounds?: ExtractorBounds) => AttachmentExtractor;
|
|
87
|
+
/** Spreadsheets: shared strings and inline strings — never a cell's number. */
|
|
88
|
+
export declare const xlsxExtractor: (bounds?: ExtractorBounds) => AttachmentExtractor;
|
|
89
|
+
/** Presentations: slides in order, then their speaker notes. */
|
|
90
|
+
export declare const pptxExtractor: (bounds?: ExtractorBounds) => AttachmentExtractor;
|
|
91
|
+
/**
|
|
92
|
+
* Every extractor this package has, in the order a host should try them — what a host is
|
|
93
|
+
* constructed with when it has no list of its own:
|
|
94
|
+
*
|
|
95
|
+
* new SqliteScopeHost({ dir, attachmentExtractors: defaultAttachmentExtractors() })
|
|
96
|
+
*/
|
|
97
|
+
export declare function defaultAttachmentExtractors(bounds?: ExtractorBounds): AttachmentExtractor[];
|
|
98
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkEG;AACH,OAAO,EAEL,KAAK,mBAAmB,EAIzB,MAAM,sBAAsB,CAAC;AAsB9B,0DAA0D;AAC1D,MAAM,WAAW,eAAe;IAC9B,0FAA0F;IAC1F,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,uFAAuF;IACvF,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;CACnC;AAED;;;GAGG;AACH,eAAO,MAAM,wBAAwB,EAAE,eAGtC,CAAC;AAmrCF,+FAA+F;AAC/F,wBAAgB,aAAa,CAAC,MAAM,GAAE,eAA0C,GAAG,mBAAmB,CAUrG;AAED,gEAAgE;AAChE,wBAAgB,aAAa,CAAC,MAAM,GAAE,eAA0C,GAAG,mBAAmB,CAOrG;AAWD,+EAA+E;AAC/E,eAAO,MAAM,aAAa,YAAY,eAAe,wBAAoE,CAAC;AAC1H,+EAA+E;AAC/E,eAAO,MAAM,aAAa,YAAY,eAAe,wBAAoE,CAAC;AAC1H,gEAAgE;AAChE,eAAO,MAAM,aAAa,YAAY,eAAe,wBAAoE,CAAC;AAE1H;;;;;GAKG;AACH,wBAAgB,2BAA2B,CAAC,MAAM,GAAE,eAA0C,GAAG,mBAAmB,EAAE,CAErH"}
|