@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 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, with script, style and comments dropped |
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 every 256 KiB of progress, so an aborted
51
- extraction stops within one such step.
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
 
@@ -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"}