@doccloak/core 0.10.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +162 -2
- package/README.md +252 -19
- package/dist/doc.d.ts +68 -1
- package/dist/doc.js +447 -62
- package/dist/dom/docx.d.ts +135 -7
- package/dist/dom/docx.js +747 -134
- package/dist/dom/drawingml.d.ts +59 -0
- package/dist/dom/drawingml.js +176 -0
- package/dist/dom/errors.d.ts +35 -0
- package/dist/dom/errors.js +24 -0
- package/dist/dom/index.d.ts +3 -0
- package/dist/dom/index.js +6 -0
- package/dist/dom/office.d.ts +56 -0
- package/dist/dom/office.js +76 -23
- package/dist/dom/opc.d.ts +141 -7
- package/dist/dom/opc.js +424 -18
- package/dist/dom/package-policy.d.ts +137 -0
- package/dist/dom/package-policy.js +355 -0
- package/dist/dom/xlsx.d.ts +64 -6
- package/dist/dom/xlsx.js +75 -20
- package/dist/engine.d.ts +18 -0
- package/dist/engine.js +37 -13
- package/dist/env.d.ts +21 -1
- package/dist/index.d.ts +8 -6
- package/dist/index.js +10 -5
- package/dist/model-loader.d.ts +61 -6
- package/dist/model-loader.js +122 -17
- package/dist/pipeline.d.ts +87 -8
- package/dist/pipeline.js +249 -50
- package/dist/providers/bardsai.d.ts +8 -1
- package/dist/providers/bardsai.js +22 -2
- package/dist/providers/gliner-base.d.ts +8 -1
- package/dist/providers/gliner-base.js +22 -2
- package/dist/providers/gliner.d.ts +8 -1
- package/dist/providers/gliner.js +22 -2
- package/dist/regex/loader.d.ts +2 -0
- package/dist/regex/loader.js +2 -0
- package/dist/regex/rules.data.js +15 -14
- package/dist/regex/types.d.ts +6 -0
- package/dist/session.d.ts +137 -4
- package/dist/session.js +591 -62
- package/dist/surrogates.js +57 -45
- package/dist/worker-protocol.d.ts +13 -1
- package/dist/worker-protocol.js +164 -12
- package/package.json +1 -1
- package/rules/at.json +1 -1
- package/rules/be.json +1 -1
- package/rules/ch.json +1 -1
- package/rules/de.json +1 -1
- package/rules/dk.json +1 -1
- package/rules/es.json +1 -1
- package/rules/fi.json +1 -1
- package/rules/fr.json +1 -1
- package/rules/it.json +1 -1
- package/rules/no.json +1 -1
- package/rules/pl.json +1 -1
- package/rules/pt.json +1 -1
- package/rules/schema.json +4 -0
- package/rules/se.json +1 -1
- package/rules/universal.json +2 -1
package/dist/dom/xlsx.js
CHANGED
|
@@ -15,18 +15,28 @@
|
|
|
15
15
|
* - worksheet header/footer definitions
|
|
16
16
|
* - legacy cell comments (xl/comments*.xml) and threaded comments
|
|
17
17
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
18
|
+
* - drawing text boxes and chart titles / string caches (xl/drawings,
|
|
19
|
+
* xl/charts) through the DrawingML units (T175)
|
|
20
|
+
*
|
|
21
|
+
* Deliberately out of scope (T176): sheet names, defined-name formulas, table
|
|
22
|
+
* column names, pivot caches; layer zero scrubs known values there. Data
|
|
23
|
+
* connections and external links are reported as unredactable (T177).
|
|
24
|
+
* Metadata scrubbing follows the docx module's policy via the shared OPC
|
|
25
|
+
* helpers.
|
|
22
26
|
*/
|
|
23
27
|
import JSZip from 'jszip';
|
|
24
|
-
import { applyTextReplacements } from "./docx.js";
|
|
25
|
-
import {
|
|
28
|
+
import { applyTextReplacements, assertUnredactableAllowed, unredactableWarnings } from "./docx.js";
|
|
29
|
+
import { drawingUnits } from "./drawingml.js";
|
|
30
|
+
import { XLSX_DRAWING_TEXT_PARTS, MAX_UNPACKED_BYTES, assertUnpackedSize, collectUnredactableParts, } from "./package-policy.js";
|
|
31
|
+
import { OOXML_NS, isElementIn, elementsByLocalName, foreignNamespaces, readXmlPart, writeXmlPart, scrubDocPropsParts, normalizeZipEntries, removePackageThumbnail, scrubExternalRelTargets, replaceSensitiveValues, scrubPackageValues, } from "./opc.js";
|
|
26
32
|
import { getFileExtension } from "./docx.js";
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
33
|
+
import { UnsupportedDocumentError } from "./errors.js";
|
|
34
|
+
/** True for a SpreadsheetML element (transitional or strict namespace, T172). */
|
|
35
|
+
export function isSpreadsheetEl(el, localName) {
|
|
36
|
+
return isElementIn(el, OOXML_NS.ss, localName);
|
|
37
|
+
}
|
|
38
|
+
function isSpreadsheetNs(el) {
|
|
39
|
+
return el.namespaceURI !== null && OOXML_NS.ss.has(el.namespaceURI);
|
|
30
40
|
}
|
|
31
41
|
/** Collect descendant <t> elements (spreadsheet namespace) in document order. */
|
|
32
42
|
function collectTextLeaves(root) {
|
|
@@ -41,9 +51,8 @@ function collectTextLeaves(root) {
|
|
|
41
51
|
/** String units in xl/sharedStrings.xml: one per <si>. */
|
|
42
52
|
function sharedStringUnits(xmlDoc) {
|
|
43
53
|
const units = [];
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
units.push(collectTextLeaves(sis[i]));
|
|
54
|
+
for (const si of elementsByLocalName(xmlDoc, OOXML_NS.ss, 'si')) {
|
|
55
|
+
units.push(collectTextLeaves(si));
|
|
47
56
|
}
|
|
48
57
|
return units;
|
|
49
58
|
}
|
|
@@ -59,7 +68,7 @@ function worksheetUnits(xmlDoc) {
|
|
|
59
68
|
const all = xmlDoc.getElementsByTagName('*');
|
|
60
69
|
for (let i = 0; i < all.length; i++) {
|
|
61
70
|
const el = all[i];
|
|
62
|
-
if (el
|
|
71
|
+
if (!isSpreadsheetNs(el))
|
|
63
72
|
continue;
|
|
64
73
|
if (el.localName === 'c') {
|
|
65
74
|
const type = el.getAttribute('t');
|
|
@@ -92,9 +101,8 @@ function commentUnits(xmlDoc) {
|
|
|
92
101
|
const units = [];
|
|
93
102
|
const root = xmlDoc.documentElement;
|
|
94
103
|
if (root.localName === 'comments') {
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
const leaves = collectTextLeaves(texts[i]);
|
|
104
|
+
for (const text of elementsByLocalName(xmlDoc, OOXML_NS.ss, 'text')) {
|
|
105
|
+
const leaves = collectTextLeaves(text);
|
|
98
106
|
if (leaves.length > 0)
|
|
99
107
|
units.push(leaves);
|
|
100
108
|
}
|
|
@@ -115,16 +123,19 @@ function commentUnits(xmlDoc) {
|
|
|
115
123
|
/**
|
|
116
124
|
* Read a .xlsx file and extract its text content with position mapping.
|
|
117
125
|
*/
|
|
118
|
-
export async function readXlsx(file) {
|
|
126
|
+
export async function readXlsx(file, options = {}) {
|
|
119
127
|
const arrayBuffer = await file.arrayBuffer();
|
|
120
128
|
const zip = await JSZip.loadAsync(arrayBuffer);
|
|
129
|
+
assertUnpackedSize(zip, options.maxUnpackedBytes ?? MAX_UNPACKED_BYTES);
|
|
121
130
|
if (!zip.file('xl/workbook.xml')) {
|
|
122
131
|
throw new Error('Invalid .xlsx file: missing xl/workbook.xml');
|
|
123
132
|
}
|
|
124
133
|
// Deterministic part order so load-time and export-time extractions produce
|
|
125
|
-
// identical offsets: sharedStrings, then worksheets, then comment parts
|
|
134
|
+
// identical offsets: sharedStrings, then worksheets, then comment parts,
|
|
135
|
+
// then drawing text boxes and charts (T175: a:p paragraphs, string caches).
|
|
126
136
|
const worksheetPaths = [];
|
|
127
137
|
const commentPaths = [];
|
|
138
|
+
const drawingPaths = [];
|
|
128
139
|
zip.forEach((relativePath) => {
|
|
129
140
|
if (/^xl\/worksheets\/[^/]+\.xml$/.test(relativePath)) {
|
|
130
141
|
worksheetPaths.push(relativePath);
|
|
@@ -133,20 +144,37 @@ export async function readXlsx(file) {
|
|
|
133
144
|
/^xl\/threadedComments\/[^/]+\.xml$/.test(relativePath)) {
|
|
134
145
|
commentPaths.push(relativePath);
|
|
135
146
|
}
|
|
147
|
+
else if (XLSX_DRAWING_TEXT_PARTS.test(relativePath)) {
|
|
148
|
+
drawingPaths.push(relativePath);
|
|
149
|
+
}
|
|
136
150
|
});
|
|
137
151
|
worksheetPaths.sort();
|
|
138
152
|
commentPaths.sort();
|
|
153
|
+
drawingPaths.sort();
|
|
139
154
|
const partPlans = [
|
|
140
155
|
{ path: 'xl/sharedStrings.xml', unitsOf: sharedStringUnits },
|
|
141
156
|
...worksheetPaths.map((path) => ({ path, unitsOf: worksheetUnits })),
|
|
142
157
|
...commentPaths.map((path) => ({ path, unitsOf: commentUnits })),
|
|
158
|
+
...drawingPaths.map((path) => ({ path, unitsOf: drawingUnits })),
|
|
143
159
|
];
|
|
144
160
|
let flatText = '';
|
|
145
161
|
const contentParts = [];
|
|
162
|
+
// Fail closed (H1): worksheets whose sheetData is not SpreadsheetML in a
|
|
163
|
+
// namespace we read (transitional or strict) while foreign elements exist
|
|
164
|
+
// are refused rather than copied through.
|
|
165
|
+
let sheetDataFound = false;
|
|
166
|
+
const foreign = new Set();
|
|
146
167
|
for (const plan of partPlans) {
|
|
147
168
|
const xmlDoc = await readXmlPart(zip, plan.path);
|
|
148
169
|
if (!xmlDoc)
|
|
149
170
|
continue;
|
|
171
|
+
if (plan.unitsOf === worksheetUnits) {
|
|
172
|
+
if (elementsByLocalName(xmlDoc, OOXML_NS.ss, 'sheetData').length > 0)
|
|
173
|
+
sheetDataFound = true;
|
|
174
|
+
else
|
|
175
|
+
for (const ns of foreignNamespaces(xmlDoc))
|
|
176
|
+
foreign.add(ns);
|
|
177
|
+
}
|
|
150
178
|
const textNodes = [];
|
|
151
179
|
for (const unit of plan.unitsOf(xmlDoc)) {
|
|
152
180
|
const nodes = [];
|
|
@@ -172,11 +200,20 @@ export async function readXlsx(file) {
|
|
|
172
200
|
}
|
|
173
201
|
contentParts.push({ path: plan.path, xmlDoc, textNodes });
|
|
174
202
|
}
|
|
203
|
+
if (worksheetPaths.length > 0 && !sheetDataFound && foreign.size > 0) {
|
|
204
|
+
throw new UnsupportedDocumentError('unrecognized-namespace', `Unsupported .xlsx: no SpreadsheetML sheetData in any worksheet, found elements in ${[...foreign].join(', ')}`, [...foreign]);
|
|
205
|
+
}
|
|
206
|
+
// Package policy (T177): parts the writer would copy verbatim are
|
|
207
|
+
// reported; the writer refuses them until the host passes allowUnredactable.
|
|
208
|
+
const unredactable = await collectUnredactableParts(zip, 'xlsx');
|
|
175
209
|
return {
|
|
176
210
|
plainText: flatText,
|
|
177
211
|
zip,
|
|
178
212
|
contentParts,
|
|
179
213
|
textNodes: contentParts.flatMap((part) => part.textNodes),
|
|
214
|
+
empty: flatText.trim().length === 0,
|
|
215
|
+
unredactable,
|
|
216
|
+
warnings: [],
|
|
180
217
|
};
|
|
181
218
|
}
|
|
182
219
|
/**
|
|
@@ -259,17 +296,35 @@ async function sanitizeXlsxMetadata(zip, valueReplacements) {
|
|
|
259
296
|
* Apply text replacements to the xlsx XML, preserving formatting, then scrub
|
|
260
297
|
* metadata. Returns a new .xlsx file as a Blob; the input file is untouched.
|
|
261
298
|
*/
|
|
262
|
-
export async function writeAnonymizedXlsx(extraction, replacements, valueReplacements = []) {
|
|
299
|
+
export async function writeAnonymizedXlsx(extraction, replacements, valueReplacements = [], options = {}) {
|
|
300
|
+
const result = await writeAnonymizedXlsxWithReport(extraction, replacements, valueReplacements, options);
|
|
301
|
+
return result.blob;
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Same as writeAnonymizedXlsx, with the write report. After the content
|
|
305
|
+
* parts and metadata, layer zero (T172) scrubs every known value from every
|
|
306
|
+
* other XML/rels/vml part: pivot cache records, drawings, charts, tables,
|
|
307
|
+
* defined names in the workbook, docProps.
|
|
308
|
+
*/
|
|
309
|
+
export async function writeAnonymizedXlsxWithReport(extraction, replacements, valueReplacements = [], options = {}) {
|
|
310
|
+
assertUnredactableAllowed(extraction.unredactable, options.allowUnredactable);
|
|
263
311
|
applyTextReplacements(extraction.textNodes, replacements);
|
|
312
|
+
const contentPaths = new Set();
|
|
264
313
|
for (const part of extraction.contentParts) {
|
|
265
314
|
sanitizeXlsxPart(part.xmlDoc, valueReplacements);
|
|
266
315
|
writeXmlPart(extraction.zip, part.path, part.xmlDoc);
|
|
316
|
+
contentPaths.add(part.path);
|
|
267
317
|
}
|
|
268
318
|
await sanitizeXlsxMetadata(extraction.zip, valueReplacements);
|
|
269
|
-
|
|
319
|
+
const scrub = await scrubPackageValues(extraction.zip, valueReplacements, { skip: contentPaths });
|
|
320
|
+
// Container normalisation (T179): fixed entry dates, no comments.
|
|
321
|
+
normalizeZipEntries(extraction.zip);
|
|
322
|
+
const blob = await extraction.zip.generateAsync({
|
|
270
323
|
type: 'blob',
|
|
271
324
|
mimeType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
325
|
+
comment: '',
|
|
272
326
|
});
|
|
327
|
+
return { blob, warnings: [...scrub.warnings, ...unredactableWarnings(extraction.unredactable)] };
|
|
273
328
|
}
|
|
274
329
|
/**
|
|
275
330
|
* Check if a file is a supported Excel workbook (.xlsx only; the legacy
|
package/dist/engine.d.ts
CHANGED
|
@@ -28,6 +28,24 @@ export type ProviderFactory = (env: CoreEnv) => DetectionProvider;
|
|
|
28
28
|
export interface EngineOptions {
|
|
29
29
|
/** Override provider construction (tests inject deterministic stubs). */
|
|
30
30
|
providers?: Partial<Record<ProviderId, ProviderFactory>>;
|
|
31
|
+
/**
|
|
32
|
+
* Whether detect() may download and initialise the model on its own
|
|
33
|
+
* (T185, security report S9). Default true (previous behaviour). With
|
|
34
|
+
* false the engine never touches the network implicitly: detect() before
|
|
35
|
+
* a completed preload() (or switchProvider()) rejects with
|
|
36
|
+
* ModelNotLoadedError, while a preload that is still in flight is awaited.
|
|
37
|
+
* Hosts that gate model downloads behind user consent should set this to
|
|
38
|
+
* false and call preload() from the consent flow.
|
|
39
|
+
*/
|
|
40
|
+
autoLoad?: boolean;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* detect() was called while the active provider's model is not loaded and
|
|
44
|
+
* createEngine was given `autoLoad: false`. Call engine.preload() first.
|
|
45
|
+
*/
|
|
46
|
+
export declare class ModelNotLoadedError extends Error {
|
|
47
|
+
providerId: ProviderId;
|
|
48
|
+
constructor(providerId: ProviderId);
|
|
31
49
|
}
|
|
32
50
|
/**
|
|
33
51
|
* env.kv keys, byte-identical to the localStorage keys the web app used
|
package/dist/engine.js
CHANGED
|
@@ -36,6 +36,17 @@ export const PROVIDERS = [
|
|
|
36
36
|
description: 'Best multilingual accuracy, 24+ EU languages, 35 PII types (~279 MB)',
|
|
37
37
|
},
|
|
38
38
|
];
|
|
39
|
+
/**
|
|
40
|
+
* detect() was called while the active provider's model is not loaded and
|
|
41
|
+
* createEngine was given `autoLoad: false`. Call engine.preload() first.
|
|
42
|
+
*/
|
|
43
|
+
export class ModelNotLoadedError extends Error {
|
|
44
|
+
constructor(providerId) {
|
|
45
|
+
super(`Model for provider "${providerId}" is not loaded; call engine.preload() before detect() (createEngine autoLoad is off)`);
|
|
46
|
+
this.name = 'ModelNotLoadedError';
|
|
47
|
+
this.providerId = providerId;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
39
50
|
// ── Settings ───────────────────────────────────────────────
|
|
40
51
|
/**
|
|
41
52
|
* env.kv keys, byte-identical to the localStorage keys the web app used
|
|
@@ -95,10 +106,13 @@ export function createEngine(env, initial, options) {
|
|
|
95
106
|
bardsai: (e) => new BardsaiProvider(e),
|
|
96
107
|
...options?.providers,
|
|
97
108
|
};
|
|
109
|
+
const autoLoad = options?.autoLoad ?? true;
|
|
98
110
|
const settings = {
|
|
99
111
|
providerId: initial?.providerId ?? pickDefaultProvider(env.hardware),
|
|
100
112
|
threshold: 0,
|
|
101
|
-
|
|
113
|
+
// Regex rules are on unless the host or a saved setting turns them off
|
|
114
|
+
// (T203): structured identifiers must not depend on the model alone.
|
|
115
|
+
regexEnabled: initial?.regexEnabled ?? true,
|
|
102
116
|
regexRegion: initial?.regexRegion ?? 'all',
|
|
103
117
|
customLabels: [...(initial?.customLabels ?? [])],
|
|
104
118
|
};
|
|
@@ -175,14 +189,16 @@ export function createEngine(env, initial, options) {
|
|
|
175
189
|
await p.setCustomLabels(settings.customLabels);
|
|
176
190
|
}
|
|
177
191
|
}
|
|
178
|
-
|
|
179
|
-
|
|
192
|
+
function preload() {
|
|
193
|
+
// inflightLoad is assigned synchronously so a detect() issued right
|
|
194
|
+
// after preload() (without awaiting it) sees the load in flight.
|
|
180
195
|
if (inflightLoad)
|
|
181
196
|
return inflightLoad;
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
197
|
+
const run = (async () => {
|
|
198
|
+
await ready;
|
|
199
|
+
const p = ensureProvider();
|
|
200
|
+
if (p.isLoaded())
|
|
201
|
+
return;
|
|
186
202
|
// A previous attempt may have failed: release() clears the cached load
|
|
187
203
|
// error so load() can retry (model bytes are served from the blob
|
|
188
204
|
// cache when available). Mirrors the old worker init path.
|
|
@@ -194,12 +210,11 @@ export function createEngine(env, initial, options) {
|
|
|
194
210
|
if (supportsCustomLabels(p))
|
|
195
211
|
settings.customLabels = p.getCustomLabels();
|
|
196
212
|
})();
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
}
|
|
213
|
+
inflightLoad = run;
|
|
214
|
+
return run.finally(() => {
|
|
215
|
+
if (inflightLoad === run)
|
|
216
|
+
inflightLoad = null;
|
|
217
|
+
});
|
|
203
218
|
}
|
|
204
219
|
async function switchProvider(id) {
|
|
205
220
|
await ready;
|
|
@@ -254,6 +269,15 @@ export function createEngine(env, initial, options) {
|
|
|
254
269
|
if (!text.trim())
|
|
255
270
|
return [];
|
|
256
271
|
const p = ensureProvider();
|
|
272
|
+
if (!autoLoad && !p.isLoaded()) {
|
|
273
|
+
// Explicit-preload mode: never download from detect(). A preload that
|
|
274
|
+
// is already running is awaited; otherwise the host must call it.
|
|
275
|
+
if (!inflightLoad)
|
|
276
|
+
throw new ModelNotLoadedError(settings.providerId);
|
|
277
|
+
await inflightLoad;
|
|
278
|
+
if (!p.isLoaded())
|
|
279
|
+
throw new ModelNotLoadedError(settings.providerId);
|
|
280
|
+
}
|
|
257
281
|
const mlResults = await p.detect(text, (progress) => {
|
|
258
282
|
onProgress?.(progress);
|
|
259
283
|
for (const cb of detectionListeners)
|
package/dist/env.d.ts
CHANGED
|
@@ -31,7 +31,27 @@ export interface CoreEnv {
|
|
|
31
31
|
paths: string;
|
|
32
32
|
numThreads?: number;
|
|
33
33
|
};
|
|
34
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Build a tokenizer from the parsed contents of the provider's pinned
|
|
36
|
+
* `tokenizer.json` and `tokenizer_config.json` (T185). Core fetches both
|
|
37
|
+
* files itself through fetchModelBlob (immutable resolve/<commit> URL,
|
|
38
|
+
* SHA-256 + size check, blob cache, resume and retry) and only hands the
|
|
39
|
+
* verified, parsed objects here; the host never contacts the network for
|
|
40
|
+
* tokenizers. With @huggingface/transformers this is
|
|
41
|
+
* `new PreTrainedTokenizer(tokenizerJson, tokenizerConfig)` (or the class
|
|
42
|
+
* named by `tokenizerConfig.tokenizer_class`). May return a promise.
|
|
43
|
+
*
|
|
44
|
+
* Optional in 0.12.0 only so hosts still on loadTokenizer keep compiling;
|
|
45
|
+
* required from 0.13.0.
|
|
46
|
+
*/
|
|
47
|
+
buildTokenizer?(tokenizerJson: unknown, tokenizerConfig: unknown): unknown | Promise<unknown>;
|
|
48
|
+
/**
|
|
49
|
+
* @deprecated since 0.12.0, removed in 0.13.0. Used only when
|
|
50
|
+
* buildTokenizer is absent. Loading by Hugging Face model id lets the
|
|
51
|
+
* host library probe mutable `resolve/main` and keep an unverified
|
|
52
|
+
* second cache (security report S1/S3); implement buildTokenizer.
|
|
53
|
+
*/
|
|
54
|
+
loadTokenizer?(hfModelId: string): Promise<unknown>;
|
|
35
55
|
hardware?: HardwareHints;
|
|
36
56
|
persistStorage?: () => Promise<boolean>;
|
|
37
57
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -4,21 +4,23 @@
|
|
|
4
4
|
export type { KVStore, BlobCache, HardwareHints, CoreEnv, MemoryBlobCacheOptions, } from './env.js';
|
|
5
5
|
export { memoryKV, memoryBlobCache } from './env.js';
|
|
6
6
|
export declare const CORE_PACKAGE_NAME = "@doccloak/core";
|
|
7
|
-
export { fetchModelBlob, evictModelFromCache, retryAsync, ModelIntegrityError, verificationMarkerKey } from './model-loader.js';
|
|
8
|
-
export type { ModelLoaderEnv, DownloadProgress, FetchModelOptions, ModelVerification } from './model-loader.js';
|
|
7
|
+
export { fetchModelBlob, evictModelFromCache, retryAsync, ModelIntegrityError, verificationMarkerKey, verificationMarkerValue, parseVerificationMarker, fetchPinnedJson, loadPinnedTokenizer, } from './model-loader.js';
|
|
8
|
+
export type { ModelLoaderEnv, DownloadProgress, FetchModelOptions, ModelVerification, TokenizerFileSpec, TokenizerFiles, PinnedTokenizerOptions, } from './model-loader.js';
|
|
9
9
|
export { ALL_REGEX_RULES, REGEX_REGIONS } from './regex/index.js';
|
|
10
10
|
export type { RegexRule, PiiDomain, RegionCode, RegexRegionId } from './regex/index.js';
|
|
11
11
|
export * from './types.js';
|
|
12
12
|
export * from './session.js';
|
|
13
13
|
export * from './doc.js';
|
|
14
|
+
export { UnsupportedDocumentError, isUnsupportedDocumentError } from './dom/errors.js';
|
|
15
|
+
export type { UnsupportedDocumentCode } from './dom/errors.js';
|
|
14
16
|
export { generateSurrogate, generateUniqueSurrogate, generateSessionSalt, sessionDayOffset, FAKE_EMAIL_DOMAINS, } from './surrogates.js';
|
|
15
17
|
export type { SurrogateContext } from './surrogates.js';
|
|
16
|
-
export { GlinerProvider, GLINER_MODEL_URL, GLINER_MODEL_REVISION, GLINER_MODEL_SHA256 } from './providers/gliner.js';
|
|
17
|
-
export { GlinerBaseProvider, GLINER_BASE_MODEL_URL, GLINER_BASE_MODEL_REVISION, GLINER_BASE_MODEL_SHA256, GLINER_BASE_MAX_WIDTH, buildSpanIndices, decodeSpanLogits, } from './providers/gliner-base.js';
|
|
18
|
+
export { GlinerProvider, GLINER_MODEL_URL, GLINER_MODEL_REVISION, GLINER_MODEL_SHA256, GLINER_TOKENIZER_FILES, } from './providers/gliner.js';
|
|
19
|
+
export { GlinerBaseProvider, GLINER_BASE_MODEL_URL, GLINER_BASE_MODEL_REVISION, GLINER_BASE_MODEL_SHA256, GLINER_BASE_TOKENIZER_FILES, GLINER_BASE_MAX_WIDTH, buildSpanIndices, decodeSpanLogits, } from './providers/gliner-base.js';
|
|
18
20
|
export type { SpanIndices } from './providers/gliner-base.js';
|
|
19
|
-
export { BardsaiProvider, BARDSAI_MODEL_URL, BARDSAI_MODEL_REVISION, BARDSAI_MODEL_SHA256 } from './providers/bardsai.js';
|
|
21
|
+
export { BardsaiProvider, BARDSAI_MODEL_URL, BARDSAI_MODEL_REVISION, BARDSAI_MODEL_SHA256, BARDSAI_TOKENIZER_FILES, } from './providers/bardsai.js';
|
|
20
22
|
export { detectWithRegex, resolveOverlaps, filterFalsePositives, propagateEntities, detectEntities, } from './pipeline.js';
|
|
21
|
-
export { createEngine, PROVIDERS, pickDefaultProvider, defaultThresholdFor, clampThreshold, ENGINE_SETTINGS_KEYS, } from './engine.js';
|
|
23
|
+
export { createEngine, ModelNotLoadedError, PROVIDERS, pickDefaultProvider, defaultThresholdFor, clampThreshold, ENGINE_SETTINGS_KEYS, } from './engine.js';
|
|
22
24
|
export type { ProviderId, ProviderEntry, ProviderFactory, EngineOptions, EngineSettings, DocCloakEngine, } from './engine.js';
|
|
23
25
|
export { serveEngine, connectEngine } from './worker-protocol.js';
|
|
24
26
|
export type { PortLike, EngineClient, EngineRequest, EngineResponse, } from './worker-protocol.js';
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
export { memoryKV, memoryBlobCache } from "./env.js";
|
|
5
5
|
export const CORE_PACKAGE_NAME = '@doccloak/core';
|
|
6
6
|
// T006: model loader
|
|
7
|
-
export { fetchModelBlob, evictModelFromCache, retryAsync, ModelIntegrityError, verificationMarkerKey } from "./model-loader.js";
|
|
7
|
+
export { fetchModelBlob, evictModelFromCache, retryAsync, ModelIntegrityError, verificationMarkerKey, verificationMarkerValue, parseVerificationMarker, fetchPinnedJson, loadPinnedTokenizer, } from "./model-loader.js";
|
|
8
8
|
// end T006
|
|
9
9
|
// T004: regex rules (REGEX_REGIONS moved here from the web engine in T009)
|
|
10
10
|
export { ALL_REGEX_RULES, REGEX_REGIONS } from "./regex/index.js";
|
|
@@ -14,22 +14,27 @@ export * from "./types.js";
|
|
|
14
14
|
export * from "./session.js";
|
|
15
15
|
export * from "./doc.js";
|
|
16
16
|
// end T003
|
|
17
|
+
// T178: readDocText / writeAnonymizedDoc refuse fast-saved and encrypted .doc
|
|
18
|
+
// files with this error (also exported from '@doccloak/core/dom'); re-exported
|
|
19
|
+
// here so hosts using only the main entry can switch on `code`.
|
|
20
|
+
export { UnsupportedDocumentError, isUnsupportedDocumentError } from "./dom/errors.js";
|
|
21
|
+
// end T178
|
|
17
22
|
// T043: surrogate generator (realistic, shape-preserving replacements)
|
|
18
23
|
export { generateSurrogate, generateUniqueSurrogate, generateSessionSalt, sessionDayOffset, FAKE_EMAIL_DOMAINS, } from "./surrogates.js";
|
|
19
24
|
// end T043
|
|
20
25
|
// T007: gliner provider
|
|
21
|
-
export { GlinerProvider, GLINER_MODEL_URL, GLINER_MODEL_REVISION, GLINER_MODEL_SHA256 } from "./providers/gliner.js";
|
|
26
|
+
export { GlinerProvider, GLINER_MODEL_URL, GLINER_MODEL_REVISION, GLINER_MODEL_SHA256, GLINER_TOKENIZER_FILES, } from "./providers/gliner.js";
|
|
22
27
|
// end T007
|
|
23
28
|
// T122: gliner base provider (markerV0 span mode)
|
|
24
|
-
export { GlinerBaseProvider, GLINER_BASE_MODEL_URL, GLINER_BASE_MODEL_REVISION, GLINER_BASE_MODEL_SHA256, GLINER_BASE_MAX_WIDTH, buildSpanIndices, decodeSpanLogits, } from "./providers/gliner-base.js";
|
|
29
|
+
export { GlinerBaseProvider, GLINER_BASE_MODEL_URL, GLINER_BASE_MODEL_REVISION, GLINER_BASE_MODEL_SHA256, GLINER_BASE_TOKENIZER_FILES, GLINER_BASE_MAX_WIDTH, buildSpanIndices, decodeSpanLogits, } from "./providers/gliner-base.js";
|
|
25
30
|
// end T122
|
|
26
31
|
// T008: bardsai provider
|
|
27
|
-
export { BardsaiProvider, BARDSAI_MODEL_URL, BARDSAI_MODEL_REVISION, BARDSAI_MODEL_SHA256 } from "./providers/bardsai.js";
|
|
32
|
+
export { BardsaiProvider, BARDSAI_MODEL_URL, BARDSAI_MODEL_REVISION, BARDSAI_MODEL_SHA256, BARDSAI_TOKENIZER_FILES, } from "./providers/bardsai.js";
|
|
28
33
|
// end T008
|
|
29
34
|
// T005: pipeline
|
|
30
35
|
export { detectWithRegex, resolveOverlaps, filterFalsePositives, propagateEntities, detectEntities, } from "./pipeline.js";
|
|
31
36
|
// end T005
|
|
32
37
|
// T009: engine assembly + worker protocol
|
|
33
|
-
export { createEngine, PROVIDERS, pickDefaultProvider, defaultThresholdFor, clampThreshold, ENGINE_SETTINGS_KEYS, } from "./engine.js";
|
|
38
|
+
export { createEngine, ModelNotLoadedError, PROVIDERS, pickDefaultProvider, defaultThresholdFor, clampThreshold, ENGINE_SETTINGS_KEYS, } from "./engine.js";
|
|
34
39
|
export { serveEngine, connectEngine } from "./worker-protocol.js";
|
|
35
40
|
// end T009
|
package/dist/model-loader.d.ts
CHANGED
|
@@ -17,13 +17,18 @@
|
|
|
17
17
|
* - Verifies downloads against a pinned SHA-256 when the caller provides one
|
|
18
18
|
* (supply-chain hardening, T116): mismatches evict the cache entry and
|
|
19
19
|
* reject with ModelIntegrityError; verified cache entries carry a marker
|
|
20
|
-
* so they are not re-hashed on every startup.
|
|
20
|
+
* so they are not re-hashed on every startup. The marker records the
|
|
21
|
+
* verified byte size (T185): a cache hit whose blob size no longer matches
|
|
22
|
+
* the marker is re-hashed instead of trusted.
|
|
23
|
+
* - Fetches the pinned tokenizer files (tokenizer.json, tokenizer_config.json)
|
|
24
|
+
* through the same path (loadPinnedTokenizer, T185) so tokenizers get the
|
|
25
|
+
* same resume, retry, cache and integrity treatment as the model.
|
|
21
26
|
*
|
|
22
27
|
* Environment-agnostic: all host capabilities (blob cache, fetch, persistent
|
|
23
28
|
* storage request) are injected via ModelLoaderEnv. This module never touches
|
|
24
29
|
* caches, navigator or global fetch directly.
|
|
25
30
|
*/
|
|
26
|
-
import type { BlobCache } from './env.js';
|
|
31
|
+
import type { BlobCache, CoreEnv } from './env.js';
|
|
27
32
|
export type DownloadProgress = (downloaded: number, total: number) => void;
|
|
28
33
|
/**
|
|
29
34
|
* Host capabilities the loader needs. Hosts pass the relevant slice of their
|
|
@@ -64,6 +69,19 @@ export interface ModelVerification {
|
|
|
64
69
|
* written for the previous pin.
|
|
65
70
|
*/
|
|
66
71
|
export declare function verificationMarkerKey(url: string, sha256: string): string;
|
|
72
|
+
/**
|
|
73
|
+
* Value stored in a verification marker: `${sha256}:${size}`, binding the
|
|
74
|
+
* marker to the byte size of the blob that was actually hashed (T185, S4).
|
|
75
|
+
* A cache hit is only trusted without re-hashing when the cached blob's
|
|
76
|
+
* size equals the recorded one. Markers written by 0.11.0 hold the single
|
|
77
|
+
* byte '1' (size unknown): they are upgraded by hashing once and rewriting.
|
|
78
|
+
*/
|
|
79
|
+
export declare function verificationMarkerValue(sha256: string, size: number): string;
|
|
80
|
+
/**
|
|
81
|
+
* Size recorded in a marker value for the given hash, or null when the
|
|
82
|
+
* marker is the legacy 0.11.0 '1' or otherwise unreadable (size unknown).
|
|
83
|
+
*/
|
|
84
|
+
export declare function parseVerificationMarker(value: string, sha256: string): number | null;
|
|
67
85
|
export interface FetchModelOptions {
|
|
68
86
|
maxAttempts?: number;
|
|
69
87
|
retryBaseDelayMs?: number;
|
|
@@ -76,6 +94,13 @@ export interface FetchModelOptions {
|
|
|
76
94
|
* marked verified or evicted and re-downloaded.
|
|
77
95
|
*/
|
|
78
96
|
sha256?: string;
|
|
97
|
+
/**
|
|
98
|
+
* Pinned byte size of the file. With sha256 it is cross-checked against
|
|
99
|
+
* every download (a size mismatch is an integrity failure like a hash
|
|
100
|
+
* mismatch). It also serves as the progress total when the server sends
|
|
101
|
+
* no content-length. Without sha256 it is only a progress hint.
|
|
102
|
+
*/
|
|
103
|
+
size?: number;
|
|
79
104
|
/** Fired once the served blob is known to match the pinned sha256. */
|
|
80
105
|
onVerified?: (verification: ModelVerification) => void;
|
|
81
106
|
}
|
|
@@ -90,9 +115,39 @@ export declare function fetchModelBlob(env: ModelLoaderEnv, url: string, onProgr
|
|
|
90
115
|
*/
|
|
91
116
|
export declare function evictModelFromCache(env: ModelLoaderEnv, url: string, sha256?: string): Promise<void>;
|
|
92
117
|
/**
|
|
93
|
-
* Retry an async operation with exponential backoff.
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
118
|
+
* Retry an async operation with exponential backoff. Kept for the
|
|
119
|
+
* deprecated CoreEnv.loadTokenizer path (a host library without its own
|
|
120
|
+
* retry handling) and for hosts composing their own downloads; the pinned
|
|
121
|
+
* tokenizer files now go through fetchModelBlob via loadPinnedTokenizer.
|
|
97
122
|
*/
|
|
98
123
|
export declare function retryAsync<T>(operation: () => Promise<T>, label: string, options?: FetchModelOptions): Promise<T>;
|
|
124
|
+
/** One pinned companion file: immutable resolve/<commit> URL, SHA-256 and byte size. */
|
|
125
|
+
export interface TokenizerFileSpec {
|
|
126
|
+
url: string;
|
|
127
|
+
/** Lowercase hex SHA-256 of the file at the pinned commit. */
|
|
128
|
+
sha256: string;
|
|
129
|
+
/** Byte size of the file at the pinned commit. */
|
|
130
|
+
size: number;
|
|
131
|
+
}
|
|
132
|
+
/** The two files a provider pins for its tokenizer: tokenizer.json, then tokenizer_config.json. */
|
|
133
|
+
export type TokenizerFiles = readonly [tokenizerJson: TokenizerFileSpec, tokenizerConfig: TokenizerFileSpec];
|
|
134
|
+
export type PinnedTokenizerOptions = Pick<FetchModelOptions, 'maxAttempts' | 'retryBaseDelayMs' | 'onVerified'>;
|
|
135
|
+
/**
|
|
136
|
+
* Download (or serve from cache) one pinned JSON file through fetchModelBlob
|
|
137
|
+
* with its sha256 and size, then parse it. Integrity failures surface as
|
|
138
|
+
* ModelIntegrityError with the entry evicted, like the model itself.
|
|
139
|
+
*/
|
|
140
|
+
export declare function fetchPinnedJson(env: ModelLoaderEnv, file: TokenizerFileSpec, options?: PinnedTokenizerOptions): Promise<unknown>;
|
|
141
|
+
/**
|
|
142
|
+
* Resolve a provider's tokenizer through the host env.
|
|
143
|
+
*
|
|
144
|
+
* Preferred path: fetch both pinned files via fetchPinnedJson (resume,
|
|
145
|
+
* retry, blob cache, verification marker, ModelIntegrityError) and hand the
|
|
146
|
+
* parsed objects to CoreEnv.buildTokenizer. No library probes mutable
|
|
147
|
+
* resolve/main, no second cache bucket, and a warm cache works offline.
|
|
148
|
+
*
|
|
149
|
+
* Fallback (deprecated, removed in 0.13.0): hosts that only implement
|
|
150
|
+
* CoreEnv.loadTokenizer(hfModelId) are still served through it with the
|
|
151
|
+
* previous retry wrapper.
|
|
152
|
+
*/
|
|
153
|
+
export declare function loadPinnedTokenizer(env: CoreEnv, files: TokenizerFiles, legacyHfModelId: string, options?: PinnedTokenizerOptions): Promise<unknown>;
|