@stll/anonymize-docx 2.8.2 → 2.8.3
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/dist/index.mjs +239 -49
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { loadNativeAnonymizeBinding } from "@stll/anonymize";
|
|
1
|
+
import { CALLER_DETECTION_MAX_COUNT, loadNativeAnonymizeBinding } from "@stll/anonymize";
|
|
2
2
|
//#region src/types.ts
|
|
3
3
|
const DOCX_PART_TYPES = {
|
|
4
4
|
comments: "comments",
|
|
@@ -39,6 +39,140 @@ const DOCX_EXTRACTION_ERROR_CODES = {
|
|
|
39
39
|
uncompressedLimitExceeded: "uncompressed-limit-exceeded"
|
|
40
40
|
};
|
|
41
41
|
//#endregion
|
|
42
|
+
//#region src/native-codec.ts
|
|
43
|
+
const PART_FIELDS = {
|
|
44
|
+
type: true,
|
|
45
|
+
path: true
|
|
46
|
+
};
|
|
47
|
+
const BASE_LOCATION_FIELDS = {
|
|
48
|
+
type: true,
|
|
49
|
+
part: true,
|
|
50
|
+
blockIndex: true,
|
|
51
|
+
xmlPath: true
|
|
52
|
+
};
|
|
53
|
+
const PARAGRAPH_LOCATION_FIELDS = BASE_LOCATION_FIELDS;
|
|
54
|
+
const TABLE_LOCATION_FIELDS = {
|
|
55
|
+
...BASE_LOCATION_FIELDS,
|
|
56
|
+
tablePath: true,
|
|
57
|
+
rowPath: true,
|
|
58
|
+
cellPath: true
|
|
59
|
+
};
|
|
60
|
+
const TEXT_BOX_LOCATION_FIELDS = {
|
|
61
|
+
...BASE_LOCATION_FIELDS,
|
|
62
|
+
textBoxPath: true
|
|
63
|
+
};
|
|
64
|
+
const HYPERLINK_CONTEXT_FIELDS = {
|
|
65
|
+
type: true,
|
|
66
|
+
relationshipId: true,
|
|
67
|
+
anchor: true
|
|
68
|
+
};
|
|
69
|
+
const REVISION_CONTEXT_FIELDS = {
|
|
70
|
+
type: true,
|
|
71
|
+
revision: true
|
|
72
|
+
};
|
|
73
|
+
const SEGMENT_FIELDS = {
|
|
74
|
+
start: true,
|
|
75
|
+
end: true,
|
|
76
|
+
source: true,
|
|
77
|
+
contexts: true,
|
|
78
|
+
xmlPath: true
|
|
79
|
+
};
|
|
80
|
+
const BLOCK_FIELDS = {
|
|
81
|
+
text: true,
|
|
82
|
+
location: true,
|
|
83
|
+
segments: true
|
|
84
|
+
};
|
|
85
|
+
const EXTRACTED_COVERAGE_FIELDS = {
|
|
86
|
+
status: true,
|
|
87
|
+
part: true,
|
|
88
|
+
blockCount: true
|
|
89
|
+
};
|
|
90
|
+
const UNSUPPORTED_COVERAGE_FIELDS = {
|
|
91
|
+
status: true,
|
|
92
|
+
path: true,
|
|
93
|
+
contentType: true,
|
|
94
|
+
reason: true
|
|
95
|
+
};
|
|
96
|
+
const COVERAGE_FIELDS = {
|
|
97
|
+
parts: true,
|
|
98
|
+
hyperlinkTextSegmentCount: true,
|
|
99
|
+
revisionTextSegmentCount: true,
|
|
100
|
+
unsupportedAlternateContentCount: true,
|
|
101
|
+
unsupportedSymbolCount: true,
|
|
102
|
+
unsupportedFieldInstructionCount: true
|
|
103
|
+
};
|
|
104
|
+
const EXTRACTION_FIELDS = {
|
|
105
|
+
contractVersion: true,
|
|
106
|
+
blocks: true,
|
|
107
|
+
coverage: true
|
|
108
|
+
};
|
|
109
|
+
const RESTORATION_CANDIDATE_FIELDS = {
|
|
110
|
+
start: true,
|
|
111
|
+
end: true,
|
|
112
|
+
candidate: true
|
|
113
|
+
};
|
|
114
|
+
const RESTORATION_BLOCK_FIELDS = {
|
|
115
|
+
location: true,
|
|
116
|
+
expectedText: true,
|
|
117
|
+
candidates: true
|
|
118
|
+
};
|
|
119
|
+
const RESTORATION_PLAN_FIELDS = {
|
|
120
|
+
extraction: true,
|
|
121
|
+
blocks: true,
|
|
122
|
+
candidateCount: true
|
|
123
|
+
};
|
|
124
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
125
|
+
const hasExactFields = (value, fields) => {
|
|
126
|
+
const keys = Object.keys(value);
|
|
127
|
+
return keys.length === Object.keys(fields).length && keys.every((key) => Object.hasOwn(fields, key));
|
|
128
|
+
};
|
|
129
|
+
const isNonNegativeInteger = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
130
|
+
const isPath = (value) => Array.isArray(value) && value.every(isNonNegativeInteger);
|
|
131
|
+
const isPart = (value) => isRecord(value) && hasExactFields(value, PART_FIELDS) && Object.values(DOCX_PART_TYPES).some((partType) => partType === value["type"]) && typeof value["path"] === "string";
|
|
132
|
+
const hasBaseLocation = (value) => isPart(value["part"]) && isNonNegativeInteger(value["blockIndex"]) && isPath(value["xmlPath"]);
|
|
133
|
+
const isLocation = (value) => {
|
|
134
|
+
if (!isRecord(value) || !hasBaseLocation(value)) return false;
|
|
135
|
+
switch (value["type"]) {
|
|
136
|
+
case "paragraph": return hasExactFields(value, PARAGRAPH_LOCATION_FIELDS);
|
|
137
|
+
case "table-cell-paragraph": return hasExactFields(value, TABLE_LOCATION_FIELDS) && isPath(value["tablePath"]) && isPath(value["rowPath"]) && isPath(value["cellPath"]);
|
|
138
|
+
case "text-box-paragraph": return hasExactFields(value, TEXT_BOX_LOCATION_FIELDS) && isPath(value["textBoxPath"]);
|
|
139
|
+
default: return false;
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
const isNullableString = (value) => value === null || typeof value === "string";
|
|
143
|
+
const isInlineContext = (value) => {
|
|
144
|
+
if (!isRecord(value)) return false;
|
|
145
|
+
switch (value["type"]) {
|
|
146
|
+
case "hyperlink": return hasExactFields(value, HYPERLINK_CONTEXT_FIELDS) && isNullableString(value["relationshipId"]) && isNullableString(value["anchor"]);
|
|
147
|
+
case "revision": return hasExactFields(value, REVISION_CONTEXT_FIELDS) && (value["revision"] === "deletion" || value["revision"] === "insertion" || value["revision"] === "move-from" || value["revision"] === "move-to");
|
|
148
|
+
default: return false;
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
const isSegment = (value) => isRecord(value) && hasExactFields(value, SEGMENT_FIELDS) && isNonNegativeInteger(value["start"]) && isNonNegativeInteger(value["end"]) && value["end"] >= value["start"] && (value["source"] === "break" || value["source"] === "tab" || value["source"] === "text") && Array.isArray(value["contexts"]) && value["contexts"].every(isInlineContext) && isPath(value["xmlPath"]);
|
|
152
|
+
const isBlock = (value) => isRecord(value) && hasExactFields(value, BLOCK_FIELDS) && typeof value["text"] === "string" && isLocation(value["location"]) && Array.isArray(value["segments"]) && value["segments"].every(isSegment);
|
|
153
|
+
const isCoverageItem = (value) => {
|
|
154
|
+
if (!isRecord(value)) return false;
|
|
155
|
+
switch (value["status"]) {
|
|
156
|
+
case "extracted": return hasExactFields(value, EXTRACTED_COVERAGE_FIELDS) && isPart(value["part"]) && isNonNegativeInteger(value["blockCount"]);
|
|
157
|
+
case "unsupported": return hasExactFields(value, UNSUPPORTED_COVERAGE_FIELDS) && typeof value["path"] === "string" && typeof value["contentType"] === "string" && typeof value["reason"] === "string";
|
|
158
|
+
default: return false;
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
const isCoverage = (value) => isRecord(value) && hasExactFields(value, COVERAGE_FIELDS) && Array.isArray(value["parts"]) && value["parts"].every(isCoverageItem) && isNonNegativeInteger(value["hyperlinkTextSegmentCount"]) && isNonNegativeInteger(value["revisionTextSegmentCount"]) && isNonNegativeInteger(value["unsupportedAlternateContentCount"]) && isNonNegativeInteger(value["unsupportedSymbolCount"]) && isNonNegativeInteger(value["unsupportedFieldInstructionCount"]);
|
|
162
|
+
const isExtraction = (value) => isRecord(value) && hasExactFields(value, EXTRACTION_FIELDS) && value["contractVersion"] === 1 && Array.isArray(value["blocks"]) && value["blocks"].every(isBlock) && isCoverage(value["coverage"]);
|
|
163
|
+
const isRestorationCandidate = (value) => isRecord(value) && hasExactFields(value, RESTORATION_CANDIDATE_FIELDS) && isNonNegativeInteger(value["start"]) && isNonNegativeInteger(value["end"]) && value["end"] >= value["start"] && typeof value["candidate"] === "string";
|
|
164
|
+
const isRestorationPlan = (value) => isRecord(value) && hasExactFields(value, RESTORATION_PLAN_FIELDS) && isExtraction(value["extraction"]) && Array.isArray(value["blocks"]) && value["blocks"].every((block) => isRecord(block) && hasExactFields(block, RESTORATION_BLOCK_FIELDS) && isLocation(block["location"]) && typeof block["expectedText"] === "string" && Array.isArray(block["candidates"]) && block["candidates"].every(isRestorationCandidate)) && isNonNegativeInteger(value["candidateCount"]);
|
|
165
|
+
const decodeDocxExtraction = (json) => {
|
|
166
|
+
const value = JSON.parse(json);
|
|
167
|
+
if (!isExtraction(value)) throw new Error("Native DOCX extraction does not match contract version 1");
|
|
168
|
+
return value;
|
|
169
|
+
};
|
|
170
|
+
const decodeDocxRestorationPlan = (json) => {
|
|
171
|
+
const value = JSON.parse(json);
|
|
172
|
+
if (!isRestorationPlan(value)) throw new Error("Native DOCX restoration plan does not match contract version 1");
|
|
173
|
+
return value;
|
|
174
|
+
};
|
|
175
|
+
//#endregion
|
|
42
176
|
//#region src/extract.ts
|
|
43
177
|
const DOCX_EXTRACTION_CONTRACT_VERSION = 1;
|
|
44
178
|
const DOCX_ARCHIVE_MAX_BYTES = 64 * 1024 * 1024;
|
|
@@ -65,7 +199,7 @@ const extractDocxText = (archive) => {
|
|
|
65
199
|
const extract = loadNativeAnonymizeBinding().extractDocxTextJson;
|
|
66
200
|
if (extract === void 0) throw new DocxExtractionError(DOCX_EXTRACTION_ERROR_CODES.invalidPackage, "Native anonymize binding does not expose DOCX extraction");
|
|
67
201
|
try {
|
|
68
|
-
return
|
|
202
|
+
return decodeDocxExtraction(extract(archive));
|
|
69
203
|
} catch (error) {
|
|
70
204
|
const message = error instanceof Error ? error.message : "DOCX extraction failed";
|
|
71
205
|
throw new DocxExtractionError(nativeExtractionErrorCode(message), message);
|
|
@@ -85,13 +219,83 @@ const REWRITE_ERROR_CODES = new Set(Object.values(DOCX_REWRITE_ERROR_CODES));
|
|
|
85
219
|
const EXTRACTION_ERROR_CODES = new Set(Object.values(DOCX_EXTRACTION_ERROR_CODES));
|
|
86
220
|
const DOCX_REWRITE_MAX_BLOCKS = 1e5;
|
|
87
221
|
const DOCX_REWRITE_MAX_REPLACEMENTS = 1e6;
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
"
|
|
93
|
-
|
|
94
|
-
|
|
222
|
+
const invalidLocation = (message) => {
|
|
223
|
+
throw new DocxRewriteError(DOCX_REWRITE_ERROR_CODES.invalidReplacement, message);
|
|
224
|
+
};
|
|
225
|
+
const ownDataRecord = (value, fields) => {
|
|
226
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return invalidLocation("DOCX rewrite locations must be plain objects");
|
|
227
|
+
const prototype = Object.getPrototypeOf(value);
|
|
228
|
+
if (prototype !== Object.prototype && prototype !== null) return invalidLocation("DOCX rewrite locations must be plain objects");
|
|
229
|
+
const keys = Reflect.ownKeys(value);
|
|
230
|
+
if (keys.some((key) => typeof key !== "string") || fields !== void 0 && (keys.length !== fields.length || keys.some((key) => typeof key !== "string" || !fields.includes(key)))) return invalidLocation("DOCX rewrite locations must contain exactly their declared fields");
|
|
231
|
+
for (const field of fields ?? keys) {
|
|
232
|
+
if (typeof field !== "string") return invalidLocation("DOCX rewrite locations must contain string fields");
|
|
233
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, field);
|
|
234
|
+
if (descriptor === void 0 || !("value" in descriptor)) return invalidLocation("DOCX rewrite locations must contain own data properties");
|
|
235
|
+
}
|
|
236
|
+
return value;
|
|
237
|
+
};
|
|
238
|
+
const ownValue = (record, field) => Object.getOwnPropertyDescriptor(record, field)?.value;
|
|
239
|
+
const isDocxPartType = (value) => Object.values(DOCX_PART_TYPES).some((type) => type === value);
|
|
240
|
+
const copyPath = (path) => {
|
|
241
|
+
if (!Array.isArray(path)) return invalidLocation("DOCX rewrite location paths must be arrays");
|
|
242
|
+
if (path.length > 256) throw new DocxRewriteError(DOCX_REWRITE_ERROR_CODES.invalidReplacement, `DOCX rewrite location paths must not exceed 256 entries`);
|
|
243
|
+
const copy = [];
|
|
244
|
+
for (let pathIndex = 0; pathIndex < path.length; pathIndex += 1) {
|
|
245
|
+
const index = Object.getOwnPropertyDescriptor(path, pathIndex)?.value;
|
|
246
|
+
if (index === void 0 || !Number.isSafeInteger(index) || index < 0) throw new DocxRewriteError(DOCX_REWRITE_ERROR_CODES.invalidReplacement, "DOCX rewrite location paths must contain non-negative integers");
|
|
247
|
+
copy.push(index);
|
|
248
|
+
}
|
|
249
|
+
return copy;
|
|
250
|
+
};
|
|
251
|
+
const copyLocation = (location) => {
|
|
252
|
+
const candidate = ownDataRecord(location);
|
|
253
|
+
const type = ownValue(candidate, "type");
|
|
254
|
+
const fields = [
|
|
255
|
+
"type",
|
|
256
|
+
"part",
|
|
257
|
+
"blockIndex",
|
|
258
|
+
"xmlPath"
|
|
259
|
+
];
|
|
260
|
+
if (type === "table-cell-paragraph") fields.push("tablePath", "rowPath", "cellPath");
|
|
261
|
+
else if (type === "text-box-paragraph") fields.push("textBoxPath");
|
|
262
|
+
if (Object.hasOwn(candidate, "toJSON")) fields.push("toJSON");
|
|
263
|
+
const base = ownDataRecord(location, fields);
|
|
264
|
+
const partRecord = ownDataRecord(ownValue(base, "part"), ["type", "path"]);
|
|
265
|
+
const partType = ownValue(partRecord, "type");
|
|
266
|
+
const partPath = ownValue(partRecord, "path");
|
|
267
|
+
const blockIndex = ownValue(base, "blockIndex");
|
|
268
|
+
if (!isDocxPartType(partType) || typeof partPath !== "string" || !Number.isSafeInteger(blockIndex) || typeof blockIndex !== "number" || blockIndex < 0) return invalidLocation("DOCX rewrite locations must contain a known type, part, and block index");
|
|
269
|
+
const part = {
|
|
270
|
+
type: partType,
|
|
271
|
+
path: partPath
|
|
272
|
+
};
|
|
273
|
+
switch (type) {
|
|
274
|
+
case "paragraph": return {
|
|
275
|
+
type,
|
|
276
|
+
part,
|
|
277
|
+
blockIndex,
|
|
278
|
+
xmlPath: copyPath(ownValue(base, "xmlPath"))
|
|
279
|
+
};
|
|
280
|
+
case "table-cell-paragraph": return {
|
|
281
|
+
type,
|
|
282
|
+
part,
|
|
283
|
+
blockIndex,
|
|
284
|
+
xmlPath: copyPath(ownValue(base, "xmlPath")),
|
|
285
|
+
tablePath: copyPath(ownValue(base, "tablePath")),
|
|
286
|
+
rowPath: copyPath(ownValue(base, "rowPath")),
|
|
287
|
+
cellPath: copyPath(ownValue(base, "cellPath"))
|
|
288
|
+
};
|
|
289
|
+
case "text-box-paragraph": return {
|
|
290
|
+
type,
|
|
291
|
+
part,
|
|
292
|
+
blockIndex,
|
|
293
|
+
xmlPath: copyPath(ownValue(base, "xmlPath")),
|
|
294
|
+
textBoxPath: copyPath(ownValue(base, "textBoxPath"))
|
|
295
|
+
};
|
|
296
|
+
default: return invalidLocation("DOCX rewrite locations must use a known location type");
|
|
297
|
+
}
|
|
298
|
+
};
|
|
95
299
|
const preflightRewritePlan = (rewrites) => {
|
|
96
300
|
const rewriteCount = rewrites.length;
|
|
97
301
|
if (rewriteCount > DOCX_REWRITE_MAX_BLOCKS) throw new DocxRewriteError(DOCX_REWRITE_ERROR_CODES.rewriteLimitExceeded, `DOCX rewrites must not contain more than ${DOCX_REWRITE_MAX_BLOCKS} blocks`);
|
|
@@ -105,51 +309,37 @@ const preflightRewritePlan = (rewrites) => {
|
|
|
105
309
|
const blockReplacementCount = rewrite.replacements.length;
|
|
106
310
|
replacementCount += blockReplacementCount;
|
|
107
311
|
if (replacementCount > DOCX_REWRITE_MAX_REPLACEMENTS) throw new DocxRewriteError(DOCX_REWRITE_ERROR_CODES.rewriteLimitExceeded, `DOCX rewrites must not contain more than ${DOCX_REWRITE_MAX_REPLACEMENTS} replacements`);
|
|
108
|
-
|
|
109
|
-
|
|
312
|
+
if (typeof rewrite.expectedText !== "string") throw new DocxRewriteError(DOCX_REWRITE_ERROR_CODES.invalidReplacement, "DOCX block rewrite expectedText must be a string");
|
|
313
|
+
estimatedBytes += rewrite.expectedText.length * 6 + blockReplacementCount * 96;
|
|
314
|
+
const replacements = [];
|
|
110
315
|
for (let replacementIndex = 0; replacementIndex < blockReplacementCount; replacementIndex += 1) {
|
|
111
316
|
const replacement = rewrite.replacements[replacementIndex];
|
|
112
317
|
if (replacement === void 0) throw new DocxRewriteError(DOCX_REWRITE_ERROR_CODES.invalidReplacement, "DOCX rewrite plans must not contain sparse replacements");
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
start:
|
|
117
|
-
end:
|
|
118
|
-
replacement:
|
|
318
|
+
if (!Number.isSafeInteger(replacement.start) || replacement.start < 0 || !Number.isSafeInteger(replacement.end) || replacement.end < replacement.start || typeof replacement.replacement !== "string") throw new DocxRewriteError(DOCX_REWRITE_ERROR_CODES.invalidReplacement, "DOCX replacements require ordered non-negative integer offsets and string values");
|
|
319
|
+
estimatedBytes += replacement.replacement.length * 6;
|
|
320
|
+
replacements.push({
|
|
321
|
+
start: replacement.start,
|
|
322
|
+
end: replacement.end,
|
|
323
|
+
replacement: replacement.replacement
|
|
119
324
|
});
|
|
120
325
|
}
|
|
121
|
-
const location = rewrite
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
},
|
|
134
|
-
blockIndex: typeof location["blockIndex"] === "number" ? location["blockIndex"] : null
|
|
135
|
-
};
|
|
136
|
-
for (const key of LOCATION_PATH_KEYS) {
|
|
137
|
-
const path = location[key];
|
|
138
|
-
if (Array.isArray(path)) {
|
|
139
|
-
if (path.length > 256) throw new DocxRewriteError(DOCX_REWRITE_ERROR_CODES.invalidReplacement, `DOCX rewrite location paths must not exceed 256 entries`);
|
|
140
|
-
estimatedBytes += path.length * 24;
|
|
141
|
-
const serializablePath = [];
|
|
142
|
-
for (let pathIndex = 0; pathIndex < path.length; pathIndex += 1) {
|
|
143
|
-
const value = path[pathIndex];
|
|
144
|
-
serializablePath.push(typeof value === "number" ? value : null);
|
|
145
|
-
}
|
|
146
|
-
serializableLocation[key] = serializablePath;
|
|
147
|
-
}
|
|
326
|
+
const { location } = rewrite;
|
|
327
|
+
const serializableLocation = copyLocation(location);
|
|
328
|
+
estimatedBytes += (serializableLocation.type.length + serializableLocation.part.type.length + serializableLocation.part.path.length) * 6;
|
|
329
|
+
estimatedBytes += serializableLocation.xmlPath.length * 24;
|
|
330
|
+
switch (serializableLocation.type) {
|
|
331
|
+
case "paragraph": break;
|
|
332
|
+
case "table-cell-paragraph":
|
|
333
|
+
estimatedBytes += (serializableLocation.tablePath.length + serializableLocation.rowPath.length + serializableLocation.cellPath.length) * 24;
|
|
334
|
+
break;
|
|
335
|
+
case "text-box-paragraph":
|
|
336
|
+
estimatedBytes += serializableLocation.textBoxPath.length * 24;
|
|
337
|
+
break;
|
|
148
338
|
}
|
|
149
339
|
serializableRewrites.push({
|
|
150
340
|
location: serializableLocation,
|
|
151
|
-
expectedText:
|
|
152
|
-
replacements
|
|
341
|
+
expectedText: rewrite.expectedText,
|
|
342
|
+
replacements
|
|
153
343
|
});
|
|
154
344
|
if (estimatedBytes > 134217728) throw new DocxRewriteError(DOCX_REWRITE_ERROR_CODES.rewriteLimitExceeded, `DOCX rewrite plans must not exceed ${DOCX_UNCOMPRESSED_MAX_BYTES} estimated serialized bytes`);
|
|
155
345
|
}
|
|
@@ -237,7 +427,7 @@ const restoreDocxText = ({ document, session, expectedSessionId, observedAtEpoch
|
|
|
237
427
|
if (planRestoration === void 0) throw restorationError(DOCX_RESTORATION_ERROR_CODES.invalidSession, "Native anonymize binding does not expose DOCX restoration planning");
|
|
238
428
|
let plan;
|
|
239
429
|
try {
|
|
240
|
-
plan =
|
|
430
|
+
plan = decodeDocxRestorationPlan(planRestoration(document, sessionId));
|
|
241
431
|
} catch (error) {
|
|
242
432
|
const message = error instanceof Error ? error.message : String(error);
|
|
243
433
|
const separator = message.indexOf(": ");
|
|
@@ -293,7 +483,7 @@ const docxLocationsEqual = (left, right) => {
|
|
|
293
483
|
const docxLocationKey = ({ blockIndex, part }) => `${part.path}\0${blockIndex}`;
|
|
294
484
|
//#endregion
|
|
295
485
|
//#region src/anonymize.ts
|
|
296
|
-
const DOCX_ANONYMIZATION_MAX_CALLER_DETECTIONS =
|
|
486
|
+
const DOCX_ANONYMIZATION_MAX_CALLER_DETECTIONS = CALLER_DETECTION_MAX_COUNT;
|
|
297
487
|
var DocxAnonymizationError = class extends Error {
|
|
298
488
|
code;
|
|
299
489
|
constructor(code, message) {
|
|
@@ -312,7 +502,7 @@ const planCallerDetections = (extractionBlocks, inputs) => {
|
|
|
312
502
|
if (detectionsByLocation.has(key)) throw anonymizationError(DOCX_ANONYMIZATION_ERROR_CODES.invalidCallerDetections, "Each DOCX block may have only one caller-detection input");
|
|
313
503
|
const block = blocksByLocation.get(key);
|
|
314
504
|
if (block === void 0 || !docxLocationsEqual(block.location, input.location) || block.text !== input.expectedText) throw anonymizationError(DOCX_ANONYMIZATION_ERROR_CODES.invalidCallerDetections, "DOCX caller-detection location or expected text no longer matches");
|
|
315
|
-
if (input.detections.length >
|
|
505
|
+
if (input.detections.length > DOCX_ANONYMIZATION_MAX_CALLER_DETECTIONS - callerDetectionCount) throw anonymizationError(DOCX_ANONYMIZATION_ERROR_CODES.invalidCallerDetections, `DOCX workflows must not contain more than ${DOCX_ANONYMIZATION_MAX_CALLER_DETECTIONS} caller detections`);
|
|
316
506
|
detectionsByLocation.set(key, input);
|
|
317
507
|
callerDetectionCount += input.detections.length;
|
|
318
508
|
}
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/types.ts","../src/extract.ts","../src/rewrite.ts","../src/coverage.ts","../src/restore.ts","../src/location.ts","../src/anonymize.ts"],"sourcesContent":["import type {\n NativeCallerDetection,\n NativeOperatorConfig,\n NativeSessionBlockRedactionPlan,\n NativeSessionCallerRedactionPlanOptions,\n} from \"@stll/anonymize\";\n\nexport const DOCX_PART_TYPES = {\n comments: \"comments\",\n endnotes: \"endnotes\",\n footer: \"footer\",\n footnotes: \"footnotes\",\n header: \"header\",\n mainDocument: \"main-document\",\n} as const;\n\nexport type DocxPartType =\n (typeof DOCX_PART_TYPES)[keyof typeof DOCX_PART_TYPES];\n\nexport type DocxPart = {\n type: DocxPartType;\n path: string;\n};\n\ntype DocxBaseBlockLocation = {\n part: DocxPart;\n blockIndex: number;\n xmlPath: readonly number[];\n};\n\nexport type DocxBlockLocation =\n | (DocxBaseBlockLocation & {\n type: \"paragraph\";\n })\n | (DocxBaseBlockLocation & {\n type: \"table-cell-paragraph\";\n tablePath: readonly number[];\n rowPath: readonly number[];\n cellPath: readonly number[];\n })\n | (DocxBaseBlockLocation & {\n type: \"text-box-paragraph\";\n textBoxPath: readonly number[];\n });\n\nexport type DocxInlineContext =\n | {\n type: \"hyperlink\";\n relationshipId: string | null;\n anchor: string | null;\n }\n | {\n type: \"revision\";\n revision: \"deletion\" | \"insertion\" | \"move-from\" | \"move-to\";\n };\n\nexport type DocxTextSegment = {\n start: number;\n end: number;\n source: \"break\" | \"tab\" | \"text\";\n contexts: readonly DocxInlineContext[];\n xmlPath: readonly number[];\n};\n\nexport type DocxTextBlock = {\n text: string;\n location: DocxBlockLocation;\n segments: readonly DocxTextSegment[];\n};\n\nexport type DocxCoverageItem =\n | {\n status: \"extracted\";\n part: DocxPart;\n blockCount: number;\n }\n | {\n status: \"unsupported\";\n path: string;\n contentType: string;\n reason: string;\n };\n\nexport type DocxCoverage = {\n parts: readonly DocxCoverageItem[];\n hyperlinkTextSegmentCount: number;\n revisionTextSegmentCount: number;\n unsupportedAlternateContentCount: number;\n unsupportedSymbolCount: number;\n unsupportedFieldInstructionCount: number;\n};\n\nexport type DocxExtraction = {\n contractVersion: 1;\n blocks: readonly DocxTextBlock[];\n coverage: DocxCoverage;\n};\n\nexport type DocxTextReplacement = {\n start: number;\n end: number;\n replacement: string;\n};\n\nexport type DocxBlockRewrite = {\n location: DocxBlockLocation;\n expectedText: string;\n replacements: readonly DocxTextReplacement[];\n};\n\nexport type DocxRewriteResult = {\n document: Uint8Array;\n rewrittenBlockCount: number;\n appliedReplacementCount: number;\n};\n\nexport const DOCX_COVERAGE_MODES = {\n allowPartial: \"allow-partial\",\n requireFull: \"require-full\",\n} as const;\n\nexport type DocxCoverageMode =\n (typeof DOCX_COVERAGE_MODES)[keyof typeof DOCX_COVERAGE_MODES];\n\nexport type DocxCoveragePolicy =\n | { mode: typeof DOCX_COVERAGE_MODES.requireFull }\n | { mode: typeof DOCX_COVERAGE_MODES.allowPartial };\n\nexport type DocxAnonymizationPolicy = {\n coverage: DocxCoveragePolicy;\n operators?: NativeOperatorConfig;\n};\n\nexport type DocxCallerDetection = NativeCallerDetection;\n\nexport type DocxBlockCallerDetections = {\n location: DocxBlockLocation;\n expectedText: string;\n detections: readonly DocxCallerDetection[];\n};\n\nexport type DocxSessionRedactionPlan = {\n blocks: readonly NativeSessionBlockRedactionPlan[];\n commit: () => void;\n};\n\nexport type DocxAnonymizationSession = {\n sessionId: () => string;\n planTextBatchWithCallerDetections: (\n options: NativeSessionCallerRedactionPlanOptions,\n ) => DocxSessionRedactionPlan;\n};\n\nexport type AnonymizeDocxOptions = {\n document: Uint8Array;\n session: DocxAnonymizationSession;\n expectedSessionId: string;\n policy: DocxAnonymizationPolicy;\n callerDetections?: readonly DocxBlockCallerDetections[];\n observedAtEpochSeconds?: number;\n};\n\nexport type DocxCoverageSummary = {\n extractedPartCount: number;\n unsupportedPartCount: number;\n hyperlinkTextSegmentCount: number;\n revisionTextSegmentCount: number;\n unsupportedAlternateContentCount: number;\n unsupportedSymbolCount: number;\n unsupportedFieldInstructionCount: number;\n};\n\nexport type DocxWorkflowCoverage =\n | { status: \"full\"; counts: DocxCoverageSummary }\n | { status: \"partial\"; counts: DocxCoverageSummary };\n\nexport type DocxAnonymizationSummary = {\n contractVersion: 1;\n sessionId: string;\n blockCount: number;\n rewrittenBlockCount: number;\n appliedReplacementCount: number;\n entityCount: number;\n callerDetectionCount: number;\n retainedCallerDetectionCount: number;\n coverage: DocxWorkflowCoverage;\n};\n\nexport type DocxAnonymizationResult = {\n document: Uint8Array;\n summary: DocxAnonymizationSummary;\n};\n\nexport const DOCX_ANONYMIZATION_ERROR_CODES = {\n incompleteCoverage: \"incomplete-coverage\",\n invalidCallerDetections: \"invalid-caller-detections\",\n sessionMismatch: \"session-mismatch\",\n} as const;\n\nexport type DocxAnonymizationErrorCode =\n (typeof DOCX_ANONYMIZATION_ERROR_CODES)[keyof typeof DOCX_ANONYMIZATION_ERROR_CODES];\n\nexport type DocxRestorationSession = {\n sessionId: () => string;\n restoreText: (text: string, observedAtEpochSeconds?: number) => string;\n};\n\nexport type RestoreDocxTextOptions = {\n document: Uint8Array;\n session: DocxRestorationSession;\n expectedSessionId: string;\n observedAtEpochSeconds?: number;\n};\n\nexport type DocxRestorationResult = {\n document: Uint8Array;\n sessionId: string;\n restoredBlockCount: number;\n restoredPlaceholderCount: number;\n coverage: DocxWorkflowCoverage;\n};\n\nexport const DOCX_RESTORATION_ERROR_CODES = {\n invalidPlaceholder: \"invalid-placeholder\",\n invalidSession: \"invalid-session\",\n restorationLimitExceeded: \"restoration-limit-exceeded\",\n sessionMismatch: \"session-mismatch\",\n unsupportedDocument: \"unsupported-document\",\n} as const;\n\nexport type DocxRestorationErrorCode =\n (typeof DOCX_RESTORATION_ERROR_CODES)[keyof typeof DOCX_RESTORATION_ERROR_CODES];\n\nexport const DOCX_REWRITE_ERROR_CODES = {\n invalidReplacement: \"invalid-replacement\",\n rewriteLimitExceeded: \"rewrite-limit-exceeded\",\n staleExtraction: \"stale-extraction\",\n unsupportedReplacement: \"unsupported-replacement\",\n} as const;\n\nexport type DocxRewriteErrorCode =\n (typeof DOCX_REWRITE_ERROR_CODES)[keyof typeof DOCX_REWRITE_ERROR_CODES];\n\nexport const DOCX_EXTRACTION_ERROR_CODES = {\n archiveLimitExceeded: \"archive-limit-exceeded\",\n invalidArchive: \"invalid-archive\",\n invalidPackage: \"invalid-package\",\n invalidXml: \"invalid-xml\",\n unsafeEntryPath: \"unsafe-entry-path\",\n uncompressedLimitExceeded: \"uncompressed-limit-exceeded\",\n} as const;\n\nexport type DocxExtractionErrorCode =\n (typeof DOCX_EXTRACTION_ERROR_CODES)[keyof typeof DOCX_EXTRACTION_ERROR_CODES];\n","import { loadNativeAnonymizeBinding } from \"@stll/anonymize\";\n\nimport {\n DOCX_EXTRACTION_ERROR_CODES,\n type DocxExtraction,\n type DocxExtractionErrorCode,\n} from \"./types\";\n\nexport const DOCX_EXTRACTION_CONTRACT_VERSION = 1 as const;\nexport const DOCX_ARCHIVE_MAX_BYTES = 64 * 1024 * 1024;\nexport const DOCX_ENTRY_MAX_BYTES = 16 * 1024 * 1024;\nexport const DOCX_UNCOMPRESSED_MAX_BYTES = 128 * 1024 * 1024;\nexport const DOCX_XML_MAX_DEPTH = 256;\n\nexport class DocxExtractionError extends Error {\n readonly code: DocxExtractionErrorCode;\n\n constructor(code: DocxExtractionErrorCode, message: string) {\n super(message);\n this.name = \"DocxExtractionError\";\n this.code = code;\n }\n}\n\nconst nativeExtractionErrorCode = (\n message: string,\n): DocxExtractionErrorCode => {\n if (message.includes(\"unsafe entry path\")) {\n return DOCX_EXTRACTION_ERROR_CODES.unsafeEntryPath;\n }\n if (message.includes(\"valid bounded DOCX ZIP archive\")) {\n return DOCX_EXTRACTION_ERROR_CODES.invalidArchive;\n }\n if (message.includes(\"valid XML\") || message.includes(\"valid UTF-8\")) {\n return DOCX_EXTRACTION_ERROR_CODES.invalidXml;\n }\n if (\n message.includes(\n `DOCX archives must not exceed ${DOCX_ARCHIVE_MAX_BYTES} bytes`,\n )\n ) {\n return DOCX_EXTRACTION_ERROR_CODES.archiveLimitExceeded;\n }\n if (\n message.includes(\"must not exceed\") ||\n message.includes(\"must not contain more than\") ||\n message.includes(\"at most\")\n ) {\n return DOCX_EXTRACTION_ERROR_CODES.uncompressedLimitExceeded;\n }\n return DOCX_EXTRACTION_ERROR_CODES.invalidPackage;\n};\n\nexport const extractDocxText = (archive: Uint8Array): DocxExtraction => {\n const extract = loadNativeAnonymizeBinding().extractDocxTextJson;\n if (extract === undefined) {\n throw new DocxExtractionError(\n DOCX_EXTRACTION_ERROR_CODES.invalidPackage,\n \"Native anonymize binding does not expose DOCX extraction\",\n );\n }\n try {\n return JSON.parse(extract(archive)) as DocxExtraction;\n } catch (error) {\n const message =\n error instanceof Error ? error.message : \"DOCX extraction failed\";\n throw new DocxExtractionError(nativeExtractionErrorCode(message), message);\n }\n};\n","import { loadNativeAnonymizeBinding } from \"@stll/anonymize\";\n\nimport {\n DOCX_EXTRACTION_ERROR_CODES,\n DOCX_REWRITE_ERROR_CODES,\n type DocxBlockRewrite,\n type DocxExtractionErrorCode,\n type DocxRewriteErrorCode,\n type DocxRewriteResult,\n} from \"./types\";\nimport {\n DOCX_UNCOMPRESSED_MAX_BYTES,\n DOCX_XML_MAX_DEPTH,\n DocxExtractionError,\n} from \"./extract\";\n\nexport class DocxRewriteError extends Error {\n readonly code: DocxRewriteErrorCode;\n\n constructor(code: DocxRewriteErrorCode, message: string) {\n super(message);\n this.name = \"DocxRewriteError\";\n this.code = code;\n }\n}\n\nconst REWRITE_ERROR_CODES = new Set<DocxRewriteErrorCode>(\n Object.values(DOCX_REWRITE_ERROR_CODES),\n);\nconst EXTRACTION_ERROR_CODES = new Set<DocxExtractionErrorCode>(\n Object.values(DOCX_EXTRACTION_ERROR_CODES),\n);\nconst DOCX_REWRITE_MAX_BLOCKS = 100_000;\nconst DOCX_REWRITE_MAX_REPLACEMENTS = 1_000_000;\nconst LOCATION_PATH_KEYS = [\n \"xmlPath\",\n \"tablePath\",\n \"rowPath\",\n \"cellPath\",\n \"textBoxPath\",\n] as const;\n\nconst preflightRewritePlan = (\n rewrites: readonly DocxBlockRewrite[],\n): readonly unknown[] => {\n const rewriteCount = rewrites.length;\n if (rewriteCount > DOCX_REWRITE_MAX_BLOCKS) {\n throw new DocxRewriteError(\n DOCX_REWRITE_ERROR_CODES.rewriteLimitExceeded,\n `DOCX rewrites must not contain more than ${DOCX_REWRITE_MAX_BLOCKS} blocks`,\n );\n }\n let replacementCount = 0;\n let estimatedBytes = rewriteCount * 256;\n const serializableRewrites: unknown[] = [];\n for (let rewriteIndex = 0; rewriteIndex < rewriteCount; rewriteIndex += 1) {\n const rewrite = rewrites[rewriteIndex];\n if (rewrite === undefined) {\n throw new DocxRewriteError(\n DOCX_REWRITE_ERROR_CODES.invalidReplacement,\n \"DOCX rewrite plans must not contain sparse blocks\",\n );\n }\n if (!Array.isArray(rewrite.replacements)) {\n throw new DocxRewriteError(\n DOCX_REWRITE_ERROR_CODES.invalidReplacement,\n \"DOCX block rewrite replacements must be an array\",\n );\n }\n const blockReplacementCount = rewrite.replacements.length;\n replacementCount += blockReplacementCount;\n if (replacementCount > DOCX_REWRITE_MAX_REPLACEMENTS) {\n throw new DocxRewriteError(\n DOCX_REWRITE_ERROR_CODES.rewriteLimitExceeded,\n `DOCX rewrites must not contain more than ${DOCX_REWRITE_MAX_REPLACEMENTS} replacements`,\n );\n }\n estimatedBytes +=\n (typeof rewrite.expectedText === \"string\"\n ? rewrite.expectedText.length * 6\n : 0) +\n blockReplacementCount * 96;\n const serializableReplacements: unknown[] = [];\n for (\n let replacementIndex = 0;\n replacementIndex < blockReplacementCount;\n replacementIndex += 1\n ) {\n const replacement = rewrite.replacements[replacementIndex];\n if (replacement === undefined) {\n throw new DocxRewriteError(\n DOCX_REWRITE_ERROR_CODES.invalidReplacement,\n \"DOCX rewrite plans must not contain sparse replacements\",\n );\n }\n const value = replacement.replacement;\n if (typeof value === \"string\") {\n estimatedBytes += value.length * 6;\n }\n serializableReplacements.push({\n start: typeof replacement.start === \"number\" ? replacement.start : null,\n end: typeof replacement.end === \"number\" ? replacement.end : null,\n replacement: typeof value === \"string\" ? value : null,\n });\n }\n const location = rewrite.location as unknown as Record<string, unknown>;\n const part = location[\"part\"] as Record<string, unknown> | undefined;\n for (const value of [location[\"type\"], part?.[\"type\"], part?.[\"path\"]]) {\n if (typeof value === \"string\") {\n estimatedBytes += value.length * 6;\n }\n }\n const serializableLocation: Record<string, unknown> = {\n type: typeof location[\"type\"] === \"string\" ? location[\"type\"] : null,\n part: {\n type: typeof part?.[\"type\"] === \"string\" ? part[\"type\"] : null,\n path: typeof part?.[\"path\"] === \"string\" ? part[\"path\"] : null,\n },\n blockIndex:\n typeof location[\"blockIndex\"] === \"number\"\n ? location[\"blockIndex\"]\n : null,\n };\n for (const key of LOCATION_PATH_KEYS) {\n const path = location[key];\n if (Array.isArray(path)) {\n if (path.length > DOCX_XML_MAX_DEPTH) {\n throw new DocxRewriteError(\n DOCX_REWRITE_ERROR_CODES.invalidReplacement,\n `DOCX rewrite location paths must not exceed ${DOCX_XML_MAX_DEPTH} entries`,\n );\n }\n estimatedBytes += path.length * 24;\n const serializablePath: Array<number | null> = [];\n for (let pathIndex = 0; pathIndex < path.length; pathIndex += 1) {\n const value = path[pathIndex];\n serializablePath.push(typeof value === \"number\" ? value : null);\n }\n serializableLocation[key] = serializablePath;\n }\n }\n serializableRewrites.push({\n location: serializableLocation,\n expectedText:\n typeof rewrite.expectedText === \"string\" ? rewrite.expectedText : null,\n replacements: serializableReplacements,\n });\n if (estimatedBytes > DOCX_UNCOMPRESSED_MAX_BYTES) {\n throw new DocxRewriteError(\n DOCX_REWRITE_ERROR_CODES.rewriteLimitExceeded,\n `DOCX rewrite plans must not exceed ${DOCX_UNCOMPRESSED_MAX_BYTES} estimated serialized bytes`,\n );\n }\n }\n return serializableRewrites;\n};\n\nexport const rewriteDocxText = (\n archive: Uint8Array,\n rewrites: readonly DocxBlockRewrite[],\n): DocxRewriteResult => {\n const rewrite = loadNativeAnonymizeBinding().rewriteDocxTextNative;\n if (rewrite === undefined) {\n throw new Error(\n \"The native anonymize binding does not expose DOCX rewriting\",\n );\n }\n let serializableRewrites: readonly unknown[];\n try {\n serializableRewrites = preflightRewritePlan(rewrites);\n } catch (error) {\n if (error instanceof DocxRewriteError) {\n throw error;\n }\n const message = error instanceof Error ? error.message : String(error);\n throw new DocxRewriteError(\n DOCX_REWRITE_ERROR_CODES.invalidReplacement,\n `DOCX rewrite plan is invalid: ${message}`,\n );\n }\n let rewritesJson: string;\n try {\n rewritesJson = JSON.stringify(serializableRewrites);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n throw new DocxRewriteError(\n DOCX_REWRITE_ERROR_CODES.invalidReplacement,\n `DOCX rewrite plan is not serializable: ${message}`,\n );\n }\n try {\n return rewrite(archive, rewritesJson);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const separator = message.indexOf(\": \");\n const rawCode = message.slice(0, separator);\n const extractionCode = rawCode as DocxExtractionErrorCode;\n if (separator > 0 && EXTRACTION_ERROR_CODES.has(extractionCode)) {\n throw new DocxExtractionError(\n extractionCode,\n message.slice(separator + 2),\n );\n }\n const code = rawCode as DocxRewriteErrorCode;\n if (separator > 0 && REWRITE_ERROR_CODES.has(code)) {\n throw new DocxRewriteError(code, message.slice(separator + 2));\n }\n throw error;\n }\n};\n","import type { DocxCoverage, DocxWorkflowCoverage } from \"./types\";\n\nconst hasPartialCoverage = (coverage: DocxCoverage): boolean =>\n coverage.parts.some(({ status }) => status === \"unsupported\") ||\n coverage.hyperlinkTextSegmentCount > 0 ||\n coverage.revisionTextSegmentCount > 0 ||\n coverage.unsupportedAlternateContentCount > 0 ||\n coverage.unsupportedSymbolCount > 0 ||\n coverage.unsupportedFieldInstructionCount > 0;\n\nexport const docxWorkflowCoverage = (\n coverage: DocxCoverage,\n): DocxWorkflowCoverage => {\n const counts = {\n extractedPartCount: coverage.parts.filter(\n ({ status }) => status === \"extracted\",\n ).length,\n unsupportedPartCount: coverage.parts.filter(\n ({ status }) => status === \"unsupported\",\n ).length,\n hyperlinkTextSegmentCount: coverage.hyperlinkTextSegmentCount,\n revisionTextSegmentCount: coverage.revisionTextSegmentCount,\n unsupportedAlternateContentCount: coverage.unsupportedAlternateContentCount,\n unsupportedSymbolCount: coverage.unsupportedSymbolCount,\n unsupportedFieldInstructionCount: coverage.unsupportedFieldInstructionCount,\n };\n return hasPartialCoverage(coverage)\n ? { status: \"partial\", counts }\n : { status: \"full\", counts };\n};\n","import { loadNativeAnonymizeBinding } from \"@stll/anonymize\";\n\nimport { docxWorkflowCoverage } from \"./coverage\";\nimport { rewriteDocxText } from \"./rewrite\";\nimport {\n DOCX_RESTORATION_ERROR_CODES,\n type DocxBlockRewrite,\n type DocxRestorationErrorCode,\n type DocxRestorationResult,\n type DocxTextReplacement,\n type RestoreDocxTextOptions,\n} from \"./types\";\n\nexport class DocxRestorationError extends Error {\n readonly code: DocxRestorationErrorCode;\n\n constructor(code: DocxRestorationErrorCode, message: string) {\n super(message);\n this.name = \"DocxRestorationError\";\n this.code = code;\n }\n}\n\nconst restorationError = (\n code: DocxRestorationErrorCode,\n message: string,\n): DocxRestorationError => new DocxRestorationError(code, message);\n\ntype NativeRestorationPlan = {\n extraction: {\n coverage: Parameters<typeof docxWorkflowCoverage>[0];\n };\n blocks: readonly {\n location: DocxBlockRewrite[\"location\"];\n expectedText: string;\n candidates: readonly {\n start: number;\n end: number;\n candidate: string;\n }[];\n }[];\n};\n\nexport const restoreDocxText = ({\n document,\n session,\n expectedSessionId,\n observedAtEpochSeconds,\n}: RestoreDocxTextOptions): DocxRestorationResult => {\n const sessionId = session.sessionId();\n if (sessionId !== expectedSessionId) {\n throw restorationError(\n DOCX_RESTORATION_ERROR_CODES.sessionMismatch,\n \"DOCX restoration session does not match the expected session id\",\n );\n }\n\n const assertSessionAvailable = (): void => {\n if (session.restoreText(\"\", observedAtEpochSeconds) !== \"\") {\n throw restorationError(\n DOCX_RESTORATION_ERROR_CODES.invalidSession,\n \"DOCX restoration session must preserve text without placeholders\",\n );\n }\n };\n assertSessionAvailable();\n const restoredCandidates = new Map<string, string>();\n const restoreCandidate = (candidate: string): string => {\n const cached = restoredCandidates.get(candidate);\n if (cached !== undefined) {\n return cached;\n }\n const restored = session.restoreText(candidate, observedAtEpochSeconds);\n restoredCandidates.set(candidate, restored);\n return restored;\n };\n const planRestoration = loadNativeAnonymizeBinding().planDocxRestorationJson;\n if (planRestoration === undefined) {\n throw restorationError(\n DOCX_RESTORATION_ERROR_CODES.invalidSession,\n \"Native anonymize binding does not expose DOCX restoration planning\",\n );\n }\n let plan: NativeRestorationPlan;\n try {\n plan = JSON.parse(\n planRestoration(document, sessionId),\n ) as NativeRestorationPlan;\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const separator = message.indexOf(\": \");\n const code = message.slice(0, separator) as DocxRestorationErrorCode;\n const knownCodes = new Set<DocxRestorationErrorCode>([\n DOCX_RESTORATION_ERROR_CODES.invalidPlaceholder,\n DOCX_RESTORATION_ERROR_CODES.restorationLimitExceeded,\n DOCX_RESTORATION_ERROR_CODES.unsupportedDocument,\n ]);\n if (separator > 0 && knownCodes.has(code)) {\n throw restorationError(code, message.slice(separator + 2));\n }\n throw error;\n }\n const rewrites: DocxBlockRewrite[] = [];\n let restoredPlaceholderCount = 0;\n for (const block of plan.blocks) {\n const replacements: DocxTextReplacement[] = block.candidates.map(\n ({ candidate, end, start }) => {\n const replacement = restoreCandidate(candidate);\n if (replacement === candidate) {\n throw restorationError(\n DOCX_RESTORATION_ERROR_CODES.invalidPlaceholder,\n \"DOCX text contains an unknown placeholder for the expected session\",\n );\n }\n return { start, end, replacement };\n },\n );\n if (replacements.length === 0) {\n continue;\n }\n restoredPlaceholderCount += replacements.length;\n rewrites.push({\n location: block.location,\n expectedText: block.expectedText,\n replacements,\n });\n }\n assertSessionAvailable();\n const restored = rewriteDocxText(document, rewrites);\n return {\n document: restored.document,\n sessionId,\n restoredBlockCount: restored.rewrittenBlockCount,\n restoredPlaceholderCount,\n coverage: docxWorkflowCoverage(plan.extraction.coverage),\n };\n};\n","import type { DocxBlockLocation } from \"./types\";\n\nconst arraysEqual = (\n left: readonly number[],\n right: readonly number[],\n): boolean =>\n left.length === right.length &&\n left.every((value, index) => value === right.at(index));\n\nexport const docxLocationsEqual = (\n left: DocxBlockLocation,\n right: DocxBlockLocation,\n): boolean => {\n if (\n left.type !== right.type ||\n left.part.type !== right.part.type ||\n left.part.path !== right.part.path ||\n left.blockIndex !== right.blockIndex ||\n !arraysEqual(left.xmlPath, right.xmlPath)\n ) {\n return false;\n }\n if (left.type === \"paragraph\" && right.type === \"paragraph\") {\n return true;\n }\n if (\n left.type === \"table-cell-paragraph\" &&\n right.type === \"table-cell-paragraph\"\n ) {\n return (\n arraysEqual(left.tablePath, right.tablePath) &&\n arraysEqual(left.rowPath, right.rowPath) &&\n arraysEqual(left.cellPath, right.cellPath)\n );\n }\n if (\n left.type === \"text-box-paragraph\" &&\n right.type === \"text-box-paragraph\"\n ) {\n return arraysEqual(left.textBoxPath, right.textBoxPath);\n }\n return false;\n};\n\nexport const docxLocationKey = ({\n blockIndex,\n part,\n}: DocxBlockLocation): string => `${part.path}\\0${blockIndex}`;\n","import { docxWorkflowCoverage } from \"./coverage\";\nimport { extractDocxText } from \"./extract\";\nimport { docxLocationKey, docxLocationsEqual } from \"./location\";\nimport { rewriteDocxText } from \"./rewrite\";\nimport {\n DOCX_ANONYMIZATION_ERROR_CODES,\n DOCX_COVERAGE_MODES,\n type AnonymizeDocxOptions,\n type DocxAnonymizationErrorCode,\n type DocxAnonymizationResult,\n type DocxBlockCallerDetections,\n type DocxBlockRewrite,\n} from \"./types\";\n\nexport const DOCX_ANONYMIZATION_MAX_CALLER_DETECTIONS = 1_000_000;\n\nexport class DocxAnonymizationError extends Error {\n readonly code: DocxAnonymizationErrorCode;\n\n constructor(code: DocxAnonymizationErrorCode, message: string) {\n super(message);\n this.name = \"DocxAnonymizationError\";\n this.code = code;\n }\n}\n\nconst anonymizationError = (\n code: DocxAnonymizationErrorCode,\n message: string,\n): DocxAnonymizationError => new DocxAnonymizationError(code, message);\n\ntype DetectionPlan = {\n detectionsByLocation: ReadonlyMap<string, DocxBlockCallerDetections>;\n callerDetectionCount: number;\n};\n\nconst planCallerDetections = (\n extractionBlocks: ReturnType<typeof extractDocxText>[\"blocks\"],\n inputs: readonly DocxBlockCallerDetections[],\n): DetectionPlan => {\n const blocksByLocation = new Map(\n extractionBlocks.map((block) => [docxLocationKey(block.location), block]),\n );\n const detectionsByLocation = new Map<string, DocxBlockCallerDetections>();\n let callerDetectionCount = 0;\n for (const input of inputs) {\n const key = docxLocationKey(input.location);\n if (detectionsByLocation.has(key)) {\n throw anonymizationError(\n DOCX_ANONYMIZATION_ERROR_CODES.invalidCallerDetections,\n \"Each DOCX block may have only one caller-detection input\",\n );\n }\n const block = blocksByLocation.get(key);\n if (\n block === undefined ||\n !docxLocationsEqual(block.location, input.location) ||\n block.text !== input.expectedText\n ) {\n throw anonymizationError(\n DOCX_ANONYMIZATION_ERROR_CODES.invalidCallerDetections,\n \"DOCX caller-detection location or expected text no longer matches\",\n );\n }\n if (\n input.detections.length >\n DOCX_ANONYMIZATION_MAX_CALLER_DETECTIONS - callerDetectionCount\n ) {\n throw anonymizationError(\n DOCX_ANONYMIZATION_ERROR_CODES.invalidCallerDetections,\n `DOCX workflows must not contain more than ${DOCX_ANONYMIZATION_MAX_CALLER_DETECTIONS} caller detections`,\n );\n }\n detectionsByLocation.set(key, input);\n callerDetectionCount += input.detections.length;\n }\n return { detectionsByLocation, callerDetectionCount };\n};\n\nexport const anonymizeDocx = ({\n document,\n session,\n expectedSessionId,\n policy,\n callerDetections = [],\n observedAtEpochSeconds,\n}: AnonymizeDocxOptions): DocxAnonymizationResult => {\n const sessionId = session.sessionId();\n if (sessionId !== expectedSessionId) {\n throw anonymizationError(\n DOCX_ANONYMIZATION_ERROR_CODES.sessionMismatch,\n \"DOCX anonymization session does not match the expected session\",\n );\n }\n\n const extraction = extractDocxText(document);\n const coverage = docxWorkflowCoverage(extraction.coverage);\n if (\n coverage.status === \"partial\" &&\n policy.coverage.mode === DOCX_COVERAGE_MODES.requireFull\n ) {\n throw anonymizationError(\n DOCX_ANONYMIZATION_ERROR_CODES.incompleteCoverage,\n \"DOCX contains content outside the fully supported anonymization coverage\",\n );\n }\n\n const { detectionsByLocation, callerDetectionCount } = planCallerDetections(\n extraction.blocks,\n callerDetections,\n );\n const plan = session.planTextBatchWithCallerDetections({\n inputs: extraction.blocks.map((block) => ({\n fullText: block.text,\n detections:\n detectionsByLocation.get(docxLocationKey(block.location))?.detections ??\n [],\n })),\n ...(policy.operators === undefined ? {} : { operators: policy.operators }),\n ...(observedAtEpochSeconds === undefined ? {} : { observedAtEpochSeconds }),\n });\n if (plan.blocks.length !== extraction.blocks.length) {\n throw anonymizationError(\n DOCX_ANONYMIZATION_ERROR_CODES.invalidCallerDetections,\n \"DOCX session redaction plan does not match the extracted block count\",\n );\n }\n\n const rewrites: DocxBlockRewrite[] = [];\n let entityCount = 0;\n let retainedCallerDetectionCount = 0;\n for (const [index, block] of extraction.blocks.entries()) {\n const blockPlan = plan.blocks.at(index);\n if (blockPlan === undefined) {\n throw anonymizationError(\n DOCX_ANONYMIZATION_ERROR_CODES.invalidCallerDetections,\n \"DOCX session redaction plan is missing an extracted block\",\n );\n }\n entityCount += blockPlan.entityCount;\n retainedCallerDetectionCount += blockPlan.callerEntityCount;\n if (blockPlan.replacements.length === 0) {\n continue;\n }\n rewrites.push({\n location: block.location,\n expectedText: block.text,\n replacements: blockPlan.replacements,\n });\n }\n\n const rewritten = rewriteDocxText(document, rewrites);\n plan.commit();\n return {\n document: rewritten.document,\n summary: {\n contractVersion: 1,\n sessionId,\n blockCount: extraction.blocks.length,\n rewrittenBlockCount: rewritten.rewrittenBlockCount,\n appliedReplacementCount: rewritten.appliedReplacementCount,\n entityCount,\n callerDetectionCount,\n retainedCallerDetectionCount,\n coverage,\n },\n };\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/types.ts","../src/native-codec.ts","../src/extract.ts","../src/rewrite.ts","../src/coverage.ts","../src/restore.ts","../src/location.ts","../src/anonymize.ts"],"sourcesContent":["import type {\n NativeCallerDetection,\n NativeOperatorConfig,\n NativeSessionBlockRedactionPlan,\n NativeSessionCallerRedactionPlanOptions,\n} from \"@stll/anonymize\";\n\nexport const DOCX_PART_TYPES = {\n comments: \"comments\",\n endnotes: \"endnotes\",\n footer: \"footer\",\n footnotes: \"footnotes\",\n header: \"header\",\n mainDocument: \"main-document\",\n} as const;\n\nexport type DocxPartType =\n (typeof DOCX_PART_TYPES)[keyof typeof DOCX_PART_TYPES];\n\nexport type DocxPart = {\n type: DocxPartType;\n path: string;\n};\n\ntype DocxBaseBlockLocation = {\n part: DocxPart;\n blockIndex: number;\n xmlPath: readonly number[];\n};\n\nexport type DocxBlockLocation =\n | (DocxBaseBlockLocation & {\n type: \"paragraph\";\n })\n | (DocxBaseBlockLocation & {\n type: \"table-cell-paragraph\";\n tablePath: readonly number[];\n rowPath: readonly number[];\n cellPath: readonly number[];\n })\n | (DocxBaseBlockLocation & {\n type: \"text-box-paragraph\";\n textBoxPath: readonly number[];\n });\n\nexport type DocxInlineContext =\n | {\n type: \"hyperlink\";\n relationshipId: string | null;\n anchor: string | null;\n }\n | {\n type: \"revision\";\n revision: \"deletion\" | \"insertion\" | \"move-from\" | \"move-to\";\n };\n\nexport type DocxTextSegment = {\n start: number;\n end: number;\n source: \"break\" | \"tab\" | \"text\";\n contexts: readonly DocxInlineContext[];\n xmlPath: readonly number[];\n};\n\nexport type DocxTextBlock = {\n text: string;\n location: DocxBlockLocation;\n segments: readonly DocxTextSegment[];\n};\n\nexport type DocxCoverageItem =\n | {\n status: \"extracted\";\n part: DocxPart;\n blockCount: number;\n }\n | {\n status: \"unsupported\";\n path: string;\n contentType: string;\n reason: string;\n };\n\nexport type DocxCoverage = {\n parts: readonly DocxCoverageItem[];\n hyperlinkTextSegmentCount: number;\n revisionTextSegmentCount: number;\n unsupportedAlternateContentCount: number;\n unsupportedSymbolCount: number;\n unsupportedFieldInstructionCount: number;\n};\n\nexport type DocxExtraction = {\n contractVersion: 1;\n blocks: readonly DocxTextBlock[];\n coverage: DocxCoverage;\n};\n\nexport type DocxTextReplacement = {\n start: number;\n end: number;\n replacement: string;\n};\n\nexport type DocxBlockRewrite = {\n location: DocxBlockLocation;\n expectedText: string;\n replacements: readonly DocxTextReplacement[];\n};\n\nexport type DocxRewriteResult = {\n document: Uint8Array;\n rewrittenBlockCount: number;\n appliedReplacementCount: number;\n};\n\nexport const DOCX_COVERAGE_MODES = {\n allowPartial: \"allow-partial\",\n requireFull: \"require-full\",\n} as const;\n\nexport type DocxCoverageMode =\n (typeof DOCX_COVERAGE_MODES)[keyof typeof DOCX_COVERAGE_MODES];\n\nexport type DocxCoveragePolicy =\n | { mode: typeof DOCX_COVERAGE_MODES.requireFull }\n | { mode: typeof DOCX_COVERAGE_MODES.allowPartial };\n\nexport type DocxAnonymizationPolicy = {\n coverage: DocxCoveragePolicy;\n operators?: NativeOperatorConfig;\n};\n\nexport type DocxCallerDetection = NativeCallerDetection;\n\nexport type DocxBlockCallerDetections = {\n location: DocxBlockLocation;\n expectedText: string;\n detections: readonly DocxCallerDetection[];\n};\n\nexport type DocxSessionRedactionPlan = {\n blocks: readonly NativeSessionBlockRedactionPlan[];\n commit: () => void;\n};\n\nexport type DocxAnonymizationSession = {\n sessionId: () => string;\n planTextBatchWithCallerDetections: (\n options: NativeSessionCallerRedactionPlanOptions,\n ) => DocxSessionRedactionPlan;\n};\n\nexport type AnonymizeDocxOptions = {\n document: Uint8Array;\n session: DocxAnonymizationSession;\n expectedSessionId: string;\n policy: DocxAnonymizationPolicy;\n callerDetections?: readonly DocxBlockCallerDetections[];\n observedAtEpochSeconds?: number;\n};\n\nexport type DocxCoverageSummary = {\n extractedPartCount: number;\n unsupportedPartCount: number;\n hyperlinkTextSegmentCount: number;\n revisionTextSegmentCount: number;\n unsupportedAlternateContentCount: number;\n unsupportedSymbolCount: number;\n unsupportedFieldInstructionCount: number;\n};\n\nexport type DocxWorkflowCoverage =\n | { status: \"full\"; counts: DocxCoverageSummary }\n | { status: \"partial\"; counts: DocxCoverageSummary };\n\nexport type DocxAnonymizationSummary = {\n contractVersion: 1;\n sessionId: string;\n blockCount: number;\n rewrittenBlockCount: number;\n appliedReplacementCount: number;\n entityCount: number;\n callerDetectionCount: number;\n retainedCallerDetectionCount: number;\n coverage: DocxWorkflowCoverage;\n};\n\nexport type DocxAnonymizationResult = {\n document: Uint8Array;\n summary: DocxAnonymizationSummary;\n};\n\nexport const DOCX_ANONYMIZATION_ERROR_CODES = {\n incompleteCoverage: \"incomplete-coverage\",\n invalidCallerDetections: \"invalid-caller-detections\",\n sessionMismatch: \"session-mismatch\",\n} as const;\n\nexport type DocxAnonymizationErrorCode =\n (typeof DOCX_ANONYMIZATION_ERROR_CODES)[keyof typeof DOCX_ANONYMIZATION_ERROR_CODES];\n\nexport type DocxRestorationSession = {\n sessionId: () => string;\n restoreText: (text: string, observedAtEpochSeconds?: number) => string;\n};\n\nexport type RestoreDocxTextOptions = {\n document: Uint8Array;\n session: DocxRestorationSession;\n expectedSessionId: string;\n observedAtEpochSeconds?: number;\n};\n\nexport type DocxRestorationResult = {\n document: Uint8Array;\n sessionId: string;\n restoredBlockCount: number;\n restoredPlaceholderCount: number;\n coverage: DocxWorkflowCoverage;\n};\n\nexport const DOCX_RESTORATION_ERROR_CODES = {\n invalidPlaceholder: \"invalid-placeholder\",\n invalidSession: \"invalid-session\",\n restorationLimitExceeded: \"restoration-limit-exceeded\",\n sessionMismatch: \"session-mismatch\",\n unsupportedDocument: \"unsupported-document\",\n} as const;\n\nexport type DocxRestorationErrorCode =\n (typeof DOCX_RESTORATION_ERROR_CODES)[keyof typeof DOCX_RESTORATION_ERROR_CODES];\n\nexport const DOCX_REWRITE_ERROR_CODES = {\n invalidReplacement: \"invalid-replacement\",\n rewriteLimitExceeded: \"rewrite-limit-exceeded\",\n staleExtraction: \"stale-extraction\",\n unsupportedReplacement: \"unsupported-replacement\",\n} as const;\n\nexport type DocxRewriteErrorCode =\n (typeof DOCX_REWRITE_ERROR_CODES)[keyof typeof DOCX_REWRITE_ERROR_CODES];\n\nexport const DOCX_EXTRACTION_ERROR_CODES = {\n archiveLimitExceeded: \"archive-limit-exceeded\",\n invalidArchive: \"invalid-archive\",\n invalidPackage: \"invalid-package\",\n invalidXml: \"invalid-xml\",\n unsafeEntryPath: \"unsafe-entry-path\",\n uncompressedLimitExceeded: \"uncompressed-limit-exceeded\",\n} as const;\n\nexport type DocxExtractionErrorCode =\n (typeof DOCX_EXTRACTION_ERROR_CODES)[keyof typeof DOCX_EXTRACTION_ERROR_CODES];\n","import {\n DOCX_PART_TYPES,\n type DocxBlockLocation,\n type DocxBlockRewrite,\n type DocxCoverage,\n type DocxCoverageItem,\n type DocxExtraction,\n type DocxInlineContext,\n type DocxPart,\n type DocxTextBlock,\n type DocxTextSegment,\n} from \"./types\";\n\ntype RequiredFields<T> = { [Key in keyof T]-?: true };\n\nconst PART_FIELDS = {\n type: true,\n path: true,\n} as const satisfies RequiredFields<DocxPart>;\nconst BASE_LOCATION_FIELDS = {\n type: true,\n part: true,\n blockIndex: true,\n xmlPath: true,\n} as const;\nconst PARAGRAPH_LOCATION_FIELDS = BASE_LOCATION_FIELDS satisfies RequiredFields<\n Extract<DocxBlockLocation, { type: \"paragraph\" }>\n>;\nconst TABLE_LOCATION_FIELDS = {\n ...BASE_LOCATION_FIELDS,\n tablePath: true,\n rowPath: true,\n cellPath: true,\n} as const satisfies RequiredFields<\n Extract<DocxBlockLocation, { type: \"table-cell-paragraph\" }>\n>;\nconst TEXT_BOX_LOCATION_FIELDS = {\n ...BASE_LOCATION_FIELDS,\n textBoxPath: true,\n} as const satisfies RequiredFields<\n Extract<DocxBlockLocation, { type: \"text-box-paragraph\" }>\n>;\nconst HYPERLINK_CONTEXT_FIELDS = {\n type: true,\n relationshipId: true,\n anchor: true,\n} as const satisfies RequiredFields<\n Extract<DocxInlineContext, { type: \"hyperlink\" }>\n>;\nconst REVISION_CONTEXT_FIELDS = {\n type: true,\n revision: true,\n} as const satisfies RequiredFields<\n Extract<DocxInlineContext, { type: \"revision\" }>\n>;\nconst SEGMENT_FIELDS = {\n start: true,\n end: true,\n source: true,\n contexts: true,\n xmlPath: true,\n} as const satisfies RequiredFields<DocxTextSegment>;\nconst BLOCK_FIELDS = {\n text: true,\n location: true,\n segments: true,\n} as const satisfies RequiredFields<DocxTextBlock>;\nconst EXTRACTED_COVERAGE_FIELDS = {\n status: true,\n part: true,\n blockCount: true,\n} as const satisfies RequiredFields<\n Extract<DocxCoverageItem, { status: \"extracted\" }>\n>;\nconst UNSUPPORTED_COVERAGE_FIELDS = {\n status: true,\n path: true,\n contentType: true,\n reason: true,\n} as const satisfies RequiredFields<\n Extract<DocxCoverageItem, { status: \"unsupported\" }>\n>;\nconst COVERAGE_FIELDS = {\n parts: true,\n hyperlinkTextSegmentCount: true,\n revisionTextSegmentCount: true,\n unsupportedAlternateContentCount: true,\n unsupportedSymbolCount: true,\n unsupportedFieldInstructionCount: true,\n} as const satisfies RequiredFields<DocxCoverage>;\nconst EXTRACTION_FIELDS = {\n contractVersion: true,\n blocks: true,\n coverage: true,\n} as const satisfies RequiredFields<DocxExtraction>;\n\nexport type DocxRestorationCandidate = {\n start: number;\n end: number;\n candidate: string;\n};\n\nexport type NativeDocxRestorationPlan = {\n extraction: DocxExtraction;\n blocks: readonly {\n location: DocxBlockRewrite[\"location\"];\n expectedText: string;\n candidates: readonly DocxRestorationCandidate[];\n }[];\n candidateCount: number;\n};\n\nconst RESTORATION_CANDIDATE_FIELDS = {\n start: true,\n end: true,\n candidate: true,\n} as const satisfies RequiredFields<DocxRestorationCandidate>;\nconst RESTORATION_BLOCK_FIELDS = {\n location: true,\n expectedText: true,\n candidates: true,\n} as const satisfies RequiredFields<\n NativeDocxRestorationPlan[\"blocks\"][number]\n>;\nconst RESTORATION_PLAN_FIELDS = {\n extraction: true,\n blocks: true,\n candidateCount: true,\n} as const satisfies RequiredFields<NativeDocxRestorationPlan>;\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nconst hasExactFields = (\n value: Record<string, unknown>,\n fields: Record<string, true>,\n): boolean => {\n const keys = Object.keys(value);\n return (\n keys.length === Object.keys(fields).length &&\n keys.every((key) => Object.hasOwn(fields, key))\n );\n};\n\nconst isNonNegativeInteger = (value: unknown): value is number =>\n typeof value === \"number\" && Number.isSafeInteger(value) && value >= 0;\n\nconst isPath = (value: unknown): value is readonly number[] =>\n Array.isArray(value) && value.every(isNonNegativeInteger);\n\nconst isPart = (value: unknown): value is DocxPart =>\n isRecord(value) &&\n hasExactFields(value, PART_FIELDS) &&\n Object.values(DOCX_PART_TYPES).some(\n (partType) => partType === value[\"type\"],\n ) &&\n typeof value[\"path\"] === \"string\";\n\nconst hasBaseLocation = (value: Record<string, unknown>): boolean =>\n isPart(value[\"part\"]) &&\n isNonNegativeInteger(value[\"blockIndex\"]) &&\n isPath(value[\"xmlPath\"]);\n\nconst isLocation = (value: unknown): value is DocxBlockLocation => {\n if (!isRecord(value) || !hasBaseLocation(value)) {\n return false;\n }\n switch (value[\"type\"]) {\n case \"paragraph\":\n return hasExactFields(value, PARAGRAPH_LOCATION_FIELDS);\n case \"table-cell-paragraph\":\n return (\n hasExactFields(value, TABLE_LOCATION_FIELDS) &&\n isPath(value[\"tablePath\"]) &&\n isPath(value[\"rowPath\"]) &&\n isPath(value[\"cellPath\"])\n );\n case \"text-box-paragraph\":\n return (\n hasExactFields(value, TEXT_BOX_LOCATION_FIELDS) &&\n isPath(value[\"textBoxPath\"])\n );\n default:\n return false;\n }\n};\n\nconst isNullableString = (value: unknown): value is string | null =>\n value === null || typeof value === \"string\";\n\nconst isInlineContext = (value: unknown): value is DocxInlineContext => {\n if (!isRecord(value)) {\n return false;\n }\n switch (value[\"type\"]) {\n case \"hyperlink\":\n return (\n hasExactFields(value, HYPERLINK_CONTEXT_FIELDS) &&\n isNullableString(value[\"relationshipId\"]) &&\n isNullableString(value[\"anchor\"])\n );\n case \"revision\":\n return (\n hasExactFields(value, REVISION_CONTEXT_FIELDS) &&\n (value[\"revision\"] === \"deletion\" ||\n value[\"revision\"] === \"insertion\" ||\n value[\"revision\"] === \"move-from\" ||\n value[\"revision\"] === \"move-to\")\n );\n default:\n return false;\n }\n};\n\nconst isSegment = (value: unknown): value is DocxTextSegment =>\n isRecord(value) &&\n hasExactFields(value, SEGMENT_FIELDS) &&\n isNonNegativeInteger(value[\"start\"]) &&\n isNonNegativeInteger(value[\"end\"]) &&\n value[\"end\"] >= value[\"start\"] &&\n (value[\"source\"] === \"break\" ||\n value[\"source\"] === \"tab\" ||\n value[\"source\"] === \"text\") &&\n Array.isArray(value[\"contexts\"]) &&\n value[\"contexts\"].every(isInlineContext) &&\n isPath(value[\"xmlPath\"]);\n\nconst isBlock = (value: unknown): value is DocxTextBlock =>\n isRecord(value) &&\n hasExactFields(value, BLOCK_FIELDS) &&\n typeof value[\"text\"] === \"string\" &&\n isLocation(value[\"location\"]) &&\n Array.isArray(value[\"segments\"]) &&\n value[\"segments\"].every(isSegment);\n\nconst isCoverageItem = (value: unknown): value is DocxCoverageItem => {\n if (!isRecord(value)) {\n return false;\n }\n switch (value[\"status\"]) {\n case \"extracted\":\n return (\n hasExactFields(value, EXTRACTED_COVERAGE_FIELDS) &&\n isPart(value[\"part\"]) &&\n isNonNegativeInteger(value[\"blockCount\"])\n );\n case \"unsupported\":\n return (\n hasExactFields(value, UNSUPPORTED_COVERAGE_FIELDS) &&\n typeof value[\"path\"] === \"string\" &&\n typeof value[\"contentType\"] === \"string\" &&\n typeof value[\"reason\"] === \"string\"\n );\n default:\n return false;\n }\n};\n\nconst isCoverage = (value: unknown): value is DocxCoverage =>\n isRecord(value) &&\n hasExactFields(value, COVERAGE_FIELDS) &&\n Array.isArray(value[\"parts\"]) &&\n value[\"parts\"].every(isCoverageItem) &&\n isNonNegativeInteger(value[\"hyperlinkTextSegmentCount\"]) &&\n isNonNegativeInteger(value[\"revisionTextSegmentCount\"]) &&\n isNonNegativeInteger(value[\"unsupportedAlternateContentCount\"]) &&\n isNonNegativeInteger(value[\"unsupportedSymbolCount\"]) &&\n isNonNegativeInteger(value[\"unsupportedFieldInstructionCount\"]);\n\nconst isExtraction = (value: unknown): value is DocxExtraction =>\n isRecord(value) &&\n hasExactFields(value, EXTRACTION_FIELDS) &&\n value[\"contractVersion\"] === 1 &&\n Array.isArray(value[\"blocks\"]) &&\n value[\"blocks\"].every(isBlock) &&\n isCoverage(value[\"coverage\"]);\n\nconst isRestorationCandidate = (\n value: unknown,\n): value is DocxRestorationCandidate =>\n isRecord(value) &&\n hasExactFields(value, RESTORATION_CANDIDATE_FIELDS) &&\n isNonNegativeInteger(value[\"start\"]) &&\n isNonNegativeInteger(value[\"end\"]) &&\n value[\"end\"] >= value[\"start\"] &&\n typeof value[\"candidate\"] === \"string\";\n\nconst isRestorationPlan = (\n value: unknown,\n): value is NativeDocxRestorationPlan =>\n isRecord(value) &&\n hasExactFields(value, RESTORATION_PLAN_FIELDS) &&\n isExtraction(value[\"extraction\"]) &&\n Array.isArray(value[\"blocks\"]) &&\n value[\"blocks\"].every(\n (block) =>\n isRecord(block) &&\n hasExactFields(block, RESTORATION_BLOCK_FIELDS) &&\n isLocation(block[\"location\"]) &&\n typeof block[\"expectedText\"] === \"string\" &&\n Array.isArray(block[\"candidates\"]) &&\n block[\"candidates\"].every(isRestorationCandidate),\n ) &&\n isNonNegativeInteger(value[\"candidateCount\"]);\n\nexport const decodeDocxExtraction = (json: string): DocxExtraction => {\n const value: unknown = JSON.parse(json);\n if (!isExtraction(value)) {\n throw new Error(\"Native DOCX extraction does not match contract version 1\");\n }\n return value;\n};\n\nexport const decodeDocxRestorationPlan = (\n json: string,\n): NativeDocxRestorationPlan => {\n const value: unknown = JSON.parse(json);\n if (!isRestorationPlan(value)) {\n throw new Error(\n \"Native DOCX restoration plan does not match contract version 1\",\n );\n }\n return value;\n};\n","import { loadNativeAnonymizeBinding } from \"@stll/anonymize\";\n\nimport {\n DOCX_EXTRACTION_ERROR_CODES,\n type DocxExtraction,\n type DocxExtractionErrorCode,\n} from \"./types\";\nimport { decodeDocxExtraction } from \"./native-codec\";\n\nexport const DOCX_EXTRACTION_CONTRACT_VERSION = 1 as const;\nexport const DOCX_ARCHIVE_MAX_BYTES = 64 * 1024 * 1024;\nexport const DOCX_ENTRY_MAX_BYTES = 16 * 1024 * 1024;\nexport const DOCX_UNCOMPRESSED_MAX_BYTES = 128 * 1024 * 1024;\nexport const DOCX_XML_MAX_DEPTH = 256;\n\nexport class DocxExtractionError extends Error {\n readonly code: DocxExtractionErrorCode;\n\n constructor(code: DocxExtractionErrorCode, message: string) {\n super(message);\n this.name = \"DocxExtractionError\";\n this.code = code;\n }\n}\n\nconst nativeExtractionErrorCode = (\n message: string,\n): DocxExtractionErrorCode => {\n if (message.includes(\"unsafe entry path\")) {\n return DOCX_EXTRACTION_ERROR_CODES.unsafeEntryPath;\n }\n if (message.includes(\"valid bounded DOCX ZIP archive\")) {\n return DOCX_EXTRACTION_ERROR_CODES.invalidArchive;\n }\n if (message.includes(\"valid XML\") || message.includes(\"valid UTF-8\")) {\n return DOCX_EXTRACTION_ERROR_CODES.invalidXml;\n }\n if (\n message.includes(\n `DOCX archives must not exceed ${DOCX_ARCHIVE_MAX_BYTES} bytes`,\n )\n ) {\n return DOCX_EXTRACTION_ERROR_CODES.archiveLimitExceeded;\n }\n if (\n message.includes(\"must not exceed\") ||\n message.includes(\"must not contain more than\") ||\n message.includes(\"at most\")\n ) {\n return DOCX_EXTRACTION_ERROR_CODES.uncompressedLimitExceeded;\n }\n return DOCX_EXTRACTION_ERROR_CODES.invalidPackage;\n};\n\nexport const extractDocxText = (archive: Uint8Array): DocxExtraction => {\n const extract = loadNativeAnonymizeBinding().extractDocxTextJson;\n if (extract === undefined) {\n throw new DocxExtractionError(\n DOCX_EXTRACTION_ERROR_CODES.invalidPackage,\n \"Native anonymize binding does not expose DOCX extraction\",\n );\n }\n try {\n return decodeDocxExtraction(extract(archive));\n } catch (error) {\n const message =\n error instanceof Error ? error.message : \"DOCX extraction failed\";\n throw new DocxExtractionError(nativeExtractionErrorCode(message), message);\n }\n};\n","import { loadNativeAnonymizeBinding } from \"@stll/anonymize\";\n\nimport {\n DOCX_PART_TYPES,\n DOCX_EXTRACTION_ERROR_CODES,\n DOCX_REWRITE_ERROR_CODES,\n type DocxBlockRewrite,\n type DocxBlockLocation,\n type DocxExtractionErrorCode,\n type DocxRewriteErrorCode,\n type DocxRewriteResult,\n} from \"./types\";\nimport {\n DOCX_UNCOMPRESSED_MAX_BYTES,\n DOCX_XML_MAX_DEPTH,\n DocxExtractionError,\n} from \"./extract\";\n\nexport class DocxRewriteError extends Error {\n readonly code: DocxRewriteErrorCode;\n\n constructor(code: DocxRewriteErrorCode, message: string) {\n super(message);\n this.name = \"DocxRewriteError\";\n this.code = code;\n }\n}\n\nconst REWRITE_ERROR_CODES = new Set<DocxRewriteErrorCode>(\n Object.values(DOCX_REWRITE_ERROR_CODES),\n);\nconst EXTRACTION_ERROR_CODES = new Set<DocxExtractionErrorCode>(\n Object.values(DOCX_EXTRACTION_ERROR_CODES),\n);\nconst DOCX_REWRITE_MAX_BLOCKS = 100_000;\nconst DOCX_REWRITE_MAX_REPLACEMENTS = 1_000_000;\n\nconst invalidLocation = (message: string): never => {\n throw new DocxRewriteError(\n DOCX_REWRITE_ERROR_CODES.invalidReplacement,\n message,\n );\n};\n\nconst ownDataRecord = (value: unknown, fields?: readonly string[]): object => {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n return invalidLocation(\"DOCX rewrite locations must be plain objects\");\n }\n const prototype = Object.getPrototypeOf(value);\n if (prototype !== Object.prototype && prototype !== null) {\n return invalidLocation(\"DOCX rewrite locations must be plain objects\");\n }\n const keys = Reflect.ownKeys(value);\n if (\n keys.some((key) => typeof key !== \"string\") ||\n (fields !== undefined &&\n (keys.length !== fields.length ||\n keys.some((key) => typeof key !== \"string\" || !fields.includes(key))))\n ) {\n return invalidLocation(\n \"DOCX rewrite locations must contain exactly their declared fields\",\n );\n }\n for (const field of fields ?? keys) {\n if (typeof field !== \"string\") {\n return invalidLocation(\n \"DOCX rewrite locations must contain string fields\",\n );\n }\n const descriptor = Object.getOwnPropertyDescriptor(value, field);\n if (descriptor === undefined || !(\"value\" in descriptor)) {\n return invalidLocation(\n \"DOCX rewrite locations must contain own data properties\",\n );\n }\n }\n return value;\n};\n\nconst ownValue = (record: object, field: string): unknown =>\n Object.getOwnPropertyDescriptor(record, field)?.value;\n\nconst isDocxPartType = (\n value: unknown,\n): value is DocxBlockLocation[\"part\"][\"type\"] =>\n Object.values(DOCX_PART_TYPES).some((type) => type === value);\n\nconst copyPath = (path: unknown): number[] => {\n if (!Array.isArray(path)) {\n return invalidLocation(\"DOCX rewrite location paths must be arrays\");\n }\n if (path.length > DOCX_XML_MAX_DEPTH) {\n throw new DocxRewriteError(\n DOCX_REWRITE_ERROR_CODES.invalidReplacement,\n `DOCX rewrite location paths must not exceed ${DOCX_XML_MAX_DEPTH} entries`,\n );\n }\n const copy: number[] = [];\n for (let pathIndex = 0; pathIndex < path.length; pathIndex += 1) {\n const descriptor = Object.getOwnPropertyDescriptor(path, pathIndex);\n const index = descriptor?.value;\n if (index === undefined || !Number.isSafeInteger(index) || index < 0) {\n throw new DocxRewriteError(\n DOCX_REWRITE_ERROR_CODES.invalidReplacement,\n \"DOCX rewrite location paths must contain non-negative integers\",\n );\n }\n copy.push(index);\n }\n return copy;\n};\n\nconst copyLocation = (location: unknown): DocxBlockLocation => {\n const candidate = ownDataRecord(location);\n const type = ownValue(candidate, \"type\");\n const fields = [\"type\", \"part\", \"blockIndex\", \"xmlPath\"];\n if (type === \"table-cell-paragraph\") {\n fields.push(\"tablePath\", \"rowPath\", \"cellPath\");\n } else if (type === \"text-box-paragraph\") {\n fields.push(\"textBoxPath\");\n }\n if (Object.hasOwn(candidate, \"toJSON\")) {\n fields.push(\"toJSON\");\n }\n const base = ownDataRecord(location, fields);\n const partRecord = ownDataRecord(ownValue(base, \"part\"), [\"type\", \"path\"]);\n const partType = ownValue(partRecord, \"type\");\n const partPath = ownValue(partRecord, \"path\");\n const blockIndex = ownValue(base, \"blockIndex\");\n if (\n !isDocxPartType(partType) ||\n typeof partPath !== \"string\" ||\n !Number.isSafeInteger(blockIndex) ||\n typeof blockIndex !== \"number\" ||\n blockIndex < 0\n ) {\n return invalidLocation(\n \"DOCX rewrite locations must contain a known type, part, and block index\",\n );\n }\n const part = { type: partType, path: partPath };\n switch (type) {\n case \"paragraph\":\n return {\n type,\n part,\n blockIndex,\n xmlPath: copyPath(ownValue(base, \"xmlPath\")),\n };\n case \"table-cell-paragraph\":\n return {\n type,\n part,\n blockIndex,\n xmlPath: copyPath(ownValue(base, \"xmlPath\")),\n tablePath: copyPath(ownValue(base, \"tablePath\")),\n rowPath: copyPath(ownValue(base, \"rowPath\")),\n cellPath: copyPath(ownValue(base, \"cellPath\")),\n };\n case \"text-box-paragraph\":\n return {\n type,\n part,\n blockIndex,\n xmlPath: copyPath(ownValue(base, \"xmlPath\")),\n textBoxPath: copyPath(ownValue(base, \"textBoxPath\")),\n };\n default:\n return invalidLocation(\n \"DOCX rewrite locations must use a known location type\",\n );\n }\n};\n\nconst preflightRewritePlan = (\n rewrites: readonly DocxBlockRewrite[],\n): readonly DocxBlockRewrite[] => {\n const rewriteCount = rewrites.length;\n if (rewriteCount > DOCX_REWRITE_MAX_BLOCKS) {\n throw new DocxRewriteError(\n DOCX_REWRITE_ERROR_CODES.rewriteLimitExceeded,\n `DOCX rewrites must not contain more than ${DOCX_REWRITE_MAX_BLOCKS} blocks`,\n );\n }\n let replacementCount = 0;\n let estimatedBytes = rewriteCount * 256;\n const serializableRewrites: DocxBlockRewrite[] = [];\n for (let rewriteIndex = 0; rewriteIndex < rewriteCount; rewriteIndex += 1) {\n const rewrite = rewrites[rewriteIndex];\n if (rewrite === undefined) {\n throw new DocxRewriteError(\n DOCX_REWRITE_ERROR_CODES.invalidReplacement,\n \"DOCX rewrite plans must not contain sparse blocks\",\n );\n }\n if (!Array.isArray(rewrite.replacements)) {\n throw new DocxRewriteError(\n DOCX_REWRITE_ERROR_CODES.invalidReplacement,\n \"DOCX block rewrite replacements must be an array\",\n );\n }\n const blockReplacementCount = rewrite.replacements.length;\n replacementCount += blockReplacementCount;\n if (replacementCount > DOCX_REWRITE_MAX_REPLACEMENTS) {\n throw new DocxRewriteError(\n DOCX_REWRITE_ERROR_CODES.rewriteLimitExceeded,\n `DOCX rewrites must not contain more than ${DOCX_REWRITE_MAX_REPLACEMENTS} replacements`,\n );\n }\n if (typeof rewrite.expectedText !== \"string\") {\n throw new DocxRewriteError(\n DOCX_REWRITE_ERROR_CODES.invalidReplacement,\n \"DOCX block rewrite expectedText must be a string\",\n );\n }\n estimatedBytes +=\n rewrite.expectedText.length * 6 + blockReplacementCount * 96;\n const replacements: DocxBlockRewrite[\"replacements\"][number][] = [];\n for (\n let replacementIndex = 0;\n replacementIndex < blockReplacementCount;\n replacementIndex += 1\n ) {\n const replacement = rewrite.replacements[replacementIndex];\n if (replacement === undefined) {\n throw new DocxRewriteError(\n DOCX_REWRITE_ERROR_CODES.invalidReplacement,\n \"DOCX rewrite plans must not contain sparse replacements\",\n );\n }\n if (\n !Number.isSafeInteger(replacement.start) ||\n replacement.start < 0 ||\n !Number.isSafeInteger(replacement.end) ||\n replacement.end < replacement.start ||\n typeof replacement.replacement !== \"string\"\n ) {\n throw new DocxRewriteError(\n DOCX_REWRITE_ERROR_CODES.invalidReplacement,\n \"DOCX replacements require ordered non-negative integer offsets and string values\",\n );\n }\n estimatedBytes += replacement.replacement.length * 6;\n replacements.push({\n start: replacement.start,\n end: replacement.end,\n replacement: replacement.replacement,\n });\n }\n const { location } = rewrite;\n const serializableLocation = copyLocation(location);\n estimatedBytes +=\n (serializableLocation.type.length +\n serializableLocation.part.type.length +\n serializableLocation.part.path.length) *\n 6;\n estimatedBytes += serializableLocation.xmlPath.length * 24;\n switch (serializableLocation.type) {\n case \"paragraph\":\n break;\n case \"table-cell-paragraph\":\n estimatedBytes +=\n (serializableLocation.tablePath.length +\n serializableLocation.rowPath.length +\n serializableLocation.cellPath.length) *\n 24;\n break;\n case \"text-box-paragraph\":\n estimatedBytes += serializableLocation.textBoxPath.length * 24;\n break;\n }\n serializableRewrites.push({\n location: serializableLocation,\n expectedText: rewrite.expectedText,\n replacements,\n });\n if (estimatedBytes > DOCX_UNCOMPRESSED_MAX_BYTES) {\n throw new DocxRewriteError(\n DOCX_REWRITE_ERROR_CODES.rewriteLimitExceeded,\n `DOCX rewrite plans must not exceed ${DOCX_UNCOMPRESSED_MAX_BYTES} estimated serialized bytes`,\n );\n }\n }\n return serializableRewrites;\n};\n\nexport const rewriteDocxText = (\n archive: Uint8Array,\n rewrites: readonly DocxBlockRewrite[],\n): DocxRewriteResult => {\n const rewrite = loadNativeAnonymizeBinding().rewriteDocxTextNative;\n if (rewrite === undefined) {\n throw new Error(\n \"The native anonymize binding does not expose DOCX rewriting\",\n );\n }\n let serializableRewrites: readonly DocxBlockRewrite[];\n try {\n serializableRewrites = preflightRewritePlan(rewrites);\n } catch (error) {\n if (error instanceof DocxRewriteError) {\n throw error;\n }\n const message = error instanceof Error ? error.message : String(error);\n throw new DocxRewriteError(\n DOCX_REWRITE_ERROR_CODES.invalidReplacement,\n `DOCX rewrite plan is invalid: ${message}`,\n );\n }\n let rewritesJson: string;\n try {\n rewritesJson = JSON.stringify(serializableRewrites);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n throw new DocxRewriteError(\n DOCX_REWRITE_ERROR_CODES.invalidReplacement,\n `DOCX rewrite plan is not serializable: ${message}`,\n );\n }\n try {\n return rewrite(archive, rewritesJson);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const separator = message.indexOf(\": \");\n const rawCode = message.slice(0, separator);\n const extractionCode = rawCode as DocxExtractionErrorCode;\n if (separator > 0 && EXTRACTION_ERROR_CODES.has(extractionCode)) {\n throw new DocxExtractionError(\n extractionCode,\n message.slice(separator + 2),\n );\n }\n const code = rawCode as DocxRewriteErrorCode;\n if (separator > 0 && REWRITE_ERROR_CODES.has(code)) {\n throw new DocxRewriteError(code, message.slice(separator + 2));\n }\n throw error;\n }\n};\n","import type { DocxCoverage, DocxWorkflowCoverage } from \"./types\";\n\nconst hasPartialCoverage = (coverage: DocxCoverage): boolean =>\n coverage.parts.some(({ status }) => status === \"unsupported\") ||\n coverage.hyperlinkTextSegmentCount > 0 ||\n coverage.revisionTextSegmentCount > 0 ||\n coverage.unsupportedAlternateContentCount > 0 ||\n coverage.unsupportedSymbolCount > 0 ||\n coverage.unsupportedFieldInstructionCount > 0;\n\nexport const docxWorkflowCoverage = (\n coverage: DocxCoverage,\n): DocxWorkflowCoverage => {\n const counts = {\n extractedPartCount: coverage.parts.filter(\n ({ status }) => status === \"extracted\",\n ).length,\n unsupportedPartCount: coverage.parts.filter(\n ({ status }) => status === \"unsupported\",\n ).length,\n hyperlinkTextSegmentCount: coverage.hyperlinkTextSegmentCount,\n revisionTextSegmentCount: coverage.revisionTextSegmentCount,\n unsupportedAlternateContentCount: coverage.unsupportedAlternateContentCount,\n unsupportedSymbolCount: coverage.unsupportedSymbolCount,\n unsupportedFieldInstructionCount: coverage.unsupportedFieldInstructionCount,\n };\n return hasPartialCoverage(coverage)\n ? { status: \"partial\", counts }\n : { status: \"full\", counts };\n};\n","import { loadNativeAnonymizeBinding } from \"@stll/anonymize\";\n\nimport { docxWorkflowCoverage } from \"./coverage\";\nimport {\n decodeDocxRestorationPlan,\n type NativeDocxRestorationPlan,\n} from \"./native-codec\";\nimport { rewriteDocxText } from \"./rewrite\";\nimport {\n DOCX_RESTORATION_ERROR_CODES,\n type DocxBlockRewrite,\n type DocxRestorationErrorCode,\n type DocxRestorationResult,\n type DocxTextReplacement,\n type RestoreDocxTextOptions,\n} from \"./types\";\n\nexport class DocxRestorationError extends Error {\n readonly code: DocxRestorationErrorCode;\n\n constructor(code: DocxRestorationErrorCode, message: string) {\n super(message);\n this.name = \"DocxRestorationError\";\n this.code = code;\n }\n}\n\nconst restorationError = (\n code: DocxRestorationErrorCode,\n message: string,\n): DocxRestorationError => new DocxRestorationError(code, message);\n\nexport const restoreDocxText = ({\n document,\n session,\n expectedSessionId,\n observedAtEpochSeconds,\n}: RestoreDocxTextOptions): DocxRestorationResult => {\n const sessionId = session.sessionId();\n if (sessionId !== expectedSessionId) {\n throw restorationError(\n DOCX_RESTORATION_ERROR_CODES.sessionMismatch,\n \"DOCX restoration session does not match the expected session id\",\n );\n }\n\n const assertSessionAvailable = (): void => {\n if (session.restoreText(\"\", observedAtEpochSeconds) !== \"\") {\n throw restorationError(\n DOCX_RESTORATION_ERROR_CODES.invalidSession,\n \"DOCX restoration session must preserve text without placeholders\",\n );\n }\n };\n assertSessionAvailable();\n const restoredCandidates = new Map<string, string>();\n const restoreCandidate = (candidate: string): string => {\n const cached = restoredCandidates.get(candidate);\n if (cached !== undefined) {\n return cached;\n }\n const restored = session.restoreText(candidate, observedAtEpochSeconds);\n restoredCandidates.set(candidate, restored);\n return restored;\n };\n const planRestoration = loadNativeAnonymizeBinding().planDocxRestorationJson;\n if (planRestoration === undefined) {\n throw restorationError(\n DOCX_RESTORATION_ERROR_CODES.invalidSession,\n \"Native anonymize binding does not expose DOCX restoration planning\",\n );\n }\n let plan: NativeDocxRestorationPlan;\n try {\n plan = decodeDocxRestorationPlan(planRestoration(document, sessionId));\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const separator = message.indexOf(\": \");\n const code = message.slice(0, separator) as DocxRestorationErrorCode;\n const knownCodes = new Set<DocxRestorationErrorCode>([\n DOCX_RESTORATION_ERROR_CODES.invalidPlaceholder,\n DOCX_RESTORATION_ERROR_CODES.restorationLimitExceeded,\n DOCX_RESTORATION_ERROR_CODES.unsupportedDocument,\n ]);\n if (separator > 0 && knownCodes.has(code)) {\n throw restorationError(code, message.slice(separator + 2));\n }\n throw error;\n }\n const rewrites: DocxBlockRewrite[] = [];\n let restoredPlaceholderCount = 0;\n for (const block of plan.blocks) {\n const replacements: DocxTextReplacement[] = block.candidates.map(\n ({ candidate, end, start }) => {\n const replacement = restoreCandidate(candidate);\n if (replacement === candidate) {\n throw restorationError(\n DOCX_RESTORATION_ERROR_CODES.invalidPlaceholder,\n \"DOCX text contains an unknown placeholder for the expected session\",\n );\n }\n return { start, end, replacement };\n },\n );\n if (replacements.length === 0) {\n continue;\n }\n restoredPlaceholderCount += replacements.length;\n rewrites.push({\n location: block.location,\n expectedText: block.expectedText,\n replacements,\n });\n }\n assertSessionAvailable();\n const restored = rewriteDocxText(document, rewrites);\n return {\n document: restored.document,\n sessionId,\n restoredBlockCount: restored.rewrittenBlockCount,\n restoredPlaceholderCount,\n coverage: docxWorkflowCoverage(plan.extraction.coverage),\n };\n};\n","import type { DocxBlockLocation } from \"./types\";\n\nconst arraysEqual = (\n left: readonly number[],\n right: readonly number[],\n): boolean =>\n left.length === right.length &&\n left.every((value, index) => value === right.at(index));\n\nexport const docxLocationsEqual = (\n left: DocxBlockLocation,\n right: DocxBlockLocation,\n): boolean => {\n if (\n left.type !== right.type ||\n left.part.type !== right.part.type ||\n left.part.path !== right.part.path ||\n left.blockIndex !== right.blockIndex ||\n !arraysEqual(left.xmlPath, right.xmlPath)\n ) {\n return false;\n }\n if (left.type === \"paragraph\" && right.type === \"paragraph\") {\n return true;\n }\n if (\n left.type === \"table-cell-paragraph\" &&\n right.type === \"table-cell-paragraph\"\n ) {\n return (\n arraysEqual(left.tablePath, right.tablePath) &&\n arraysEqual(left.rowPath, right.rowPath) &&\n arraysEqual(left.cellPath, right.cellPath)\n );\n }\n if (\n left.type === \"text-box-paragraph\" &&\n right.type === \"text-box-paragraph\"\n ) {\n return arraysEqual(left.textBoxPath, right.textBoxPath);\n }\n return false;\n};\n\nexport const docxLocationKey = ({\n blockIndex,\n part,\n}: DocxBlockLocation): string => `${part.path}\\0${blockIndex}`;\n","import { CALLER_DETECTION_MAX_COUNT } from \"@stll/anonymize\";\n\nimport { docxWorkflowCoverage } from \"./coverage\";\nimport { extractDocxText } from \"./extract\";\nimport { docxLocationKey, docxLocationsEqual } from \"./location\";\nimport { rewriteDocxText } from \"./rewrite\";\nimport {\n DOCX_ANONYMIZATION_ERROR_CODES,\n DOCX_COVERAGE_MODES,\n type AnonymizeDocxOptions,\n type DocxAnonymizationErrorCode,\n type DocxAnonymizationResult,\n type DocxBlockCallerDetections,\n type DocxBlockRewrite,\n} from \"./types\";\n\nexport const DOCX_ANONYMIZATION_MAX_CALLER_DETECTIONS =\n CALLER_DETECTION_MAX_COUNT;\n\nexport class DocxAnonymizationError extends Error {\n readonly code: DocxAnonymizationErrorCode;\n\n constructor(code: DocxAnonymizationErrorCode, message: string) {\n super(message);\n this.name = \"DocxAnonymizationError\";\n this.code = code;\n }\n}\n\nconst anonymizationError = (\n code: DocxAnonymizationErrorCode,\n message: string,\n): DocxAnonymizationError => new DocxAnonymizationError(code, message);\n\ntype DetectionPlan = {\n detectionsByLocation: ReadonlyMap<string, DocxBlockCallerDetections>;\n callerDetectionCount: number;\n};\n\nconst planCallerDetections = (\n extractionBlocks: ReturnType<typeof extractDocxText>[\"blocks\"],\n inputs: readonly DocxBlockCallerDetections[],\n): DetectionPlan => {\n const blocksByLocation = new Map(\n extractionBlocks.map((block) => [docxLocationKey(block.location), block]),\n );\n const detectionsByLocation = new Map<string, DocxBlockCallerDetections>();\n let callerDetectionCount = 0;\n for (const input of inputs) {\n const key = docxLocationKey(input.location);\n if (detectionsByLocation.has(key)) {\n throw anonymizationError(\n DOCX_ANONYMIZATION_ERROR_CODES.invalidCallerDetections,\n \"Each DOCX block may have only one caller-detection input\",\n );\n }\n const block = blocksByLocation.get(key);\n if (\n block === undefined ||\n !docxLocationsEqual(block.location, input.location) ||\n block.text !== input.expectedText\n ) {\n throw anonymizationError(\n DOCX_ANONYMIZATION_ERROR_CODES.invalidCallerDetections,\n \"DOCX caller-detection location or expected text no longer matches\",\n );\n }\n if (\n input.detections.length >\n DOCX_ANONYMIZATION_MAX_CALLER_DETECTIONS - callerDetectionCount\n ) {\n throw anonymizationError(\n DOCX_ANONYMIZATION_ERROR_CODES.invalidCallerDetections,\n `DOCX workflows must not contain more than ${DOCX_ANONYMIZATION_MAX_CALLER_DETECTIONS} caller detections`,\n );\n }\n detectionsByLocation.set(key, input);\n callerDetectionCount += input.detections.length;\n }\n return { detectionsByLocation, callerDetectionCount };\n};\n\nexport const anonymizeDocx = ({\n document,\n session,\n expectedSessionId,\n policy,\n callerDetections = [],\n observedAtEpochSeconds,\n}: AnonymizeDocxOptions): DocxAnonymizationResult => {\n const sessionId = session.sessionId();\n if (sessionId !== expectedSessionId) {\n throw anonymizationError(\n DOCX_ANONYMIZATION_ERROR_CODES.sessionMismatch,\n \"DOCX anonymization session does not match the expected session\",\n );\n }\n\n const extraction = extractDocxText(document);\n const coverage = docxWorkflowCoverage(extraction.coverage);\n if (\n coverage.status === \"partial\" &&\n policy.coverage.mode === DOCX_COVERAGE_MODES.requireFull\n ) {\n throw anonymizationError(\n DOCX_ANONYMIZATION_ERROR_CODES.incompleteCoverage,\n \"DOCX contains content outside the fully supported anonymization coverage\",\n );\n }\n\n const { detectionsByLocation, callerDetectionCount } = planCallerDetections(\n extraction.blocks,\n callerDetections,\n );\n const plan = session.planTextBatchWithCallerDetections({\n inputs: extraction.blocks.map((block) => ({\n fullText: block.text,\n detections:\n detectionsByLocation.get(docxLocationKey(block.location))?.detections ??\n [],\n })),\n ...(policy.operators === undefined ? {} : { operators: policy.operators }),\n ...(observedAtEpochSeconds === undefined ? {} : { observedAtEpochSeconds }),\n });\n if (plan.blocks.length !== extraction.blocks.length) {\n throw anonymizationError(\n DOCX_ANONYMIZATION_ERROR_CODES.invalidCallerDetections,\n \"DOCX session redaction plan does not match the extracted block count\",\n );\n }\n\n const rewrites: DocxBlockRewrite[] = [];\n let entityCount = 0;\n let retainedCallerDetectionCount = 0;\n for (const [index, block] of extraction.blocks.entries()) {\n const blockPlan = plan.blocks.at(index);\n if (blockPlan === undefined) {\n throw anonymizationError(\n DOCX_ANONYMIZATION_ERROR_CODES.invalidCallerDetections,\n \"DOCX session redaction plan is missing an extracted block\",\n );\n }\n entityCount += blockPlan.entityCount;\n retainedCallerDetectionCount += blockPlan.callerEntityCount;\n if (blockPlan.replacements.length === 0) {\n continue;\n }\n rewrites.push({\n location: block.location,\n expectedText: block.text,\n replacements: blockPlan.replacements,\n });\n }\n\n const rewritten = rewriteDocxText(document, rewrites);\n plan.commit();\n return {\n document: rewritten.document,\n summary: {\n contractVersion: 1,\n sessionId,\n blockCount: extraction.blocks.length,\n rewrittenBlockCount: rewritten.rewrittenBlockCount,\n appliedReplacementCount: rewritten.appliedReplacementCount,\n entityCount,\n callerDetectionCount,\n retainedCallerDetectionCount,\n coverage,\n },\n };\n};\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stll/anonymize-docx",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.3",
|
|
4
4
|
"description": "Structure-aware DOCX text extraction and rewriting for stella anonymization workflows",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"format": "oxfmt ."
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@stll/anonymize": "^2.8.
|
|
35
|
+
"@stll/anonymize": "^2.8.3",
|
|
36
36
|
"fflate": "^0.8.3"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|