docxodus 9.9.0 → 11.0.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.
Files changed (124) hide show
  1. package/README.md +182 -4
  2. package/dist/canonical.d.ts +7 -0
  3. package/dist/canonical.d.ts.map +1 -0
  4. package/dist/canonical.js +56 -0
  5. package/dist/canonical.js.map +1 -0
  6. package/dist/docxodus.worker.d.ts +2 -1
  7. package/dist/docxodus.worker.d.ts.map +1 -1
  8. package/dist/docxodus.worker.js +250 -41
  9. package/dist/docxodus.worker.js.map +1 -1
  10. package/dist/editor.bundle.js +1980 -423
  11. package/dist/editor.d.ts +22 -2
  12. package/dist/editor.d.ts.map +1 -1
  13. package/dist/editor.js +94 -9
  14. package/dist/editor.js.map +1 -1
  15. package/dist/embed.bundle.js +2968 -597
  16. package/dist/embed.d.ts +1 -1
  17. package/dist/embed.iife.js +2969 -598
  18. package/dist/embed.js +1 -1
  19. package/dist/export-assets.json +277 -0
  20. package/dist/export-browser.bundle.js +7989 -0
  21. package/dist/export-browser.d.ts +266 -0
  22. package/dist/export-browser.d.ts.map +1 -0
  23. package/dist/export-browser.js +2915 -0
  24. package/dist/export-browser.js.map +1 -0
  25. package/dist/export-resource-limits-v1.json +57 -0
  26. package/dist/font-contract.d.ts +150 -0
  27. package/dist/font-contract.d.ts.map +1 -0
  28. package/dist/font-contract.js +59 -0
  29. package/dist/font-contract.js.map +1 -0
  30. package/dist/font-runtime.d.ts +33 -0
  31. package/dist/font-runtime.d.ts.map +1 -0
  32. package/dist/font-runtime.js +1100 -0
  33. package/dist/font-runtime.js.map +1 -0
  34. package/dist/index.d.ts +83 -65
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +194 -149
  37. package/dist/index.js.map +1 -1
  38. package/dist/page-geometry.d.ts +8 -1
  39. package/dist/page-geometry.d.ts.map +1 -1
  40. package/dist/page-geometry.js +37 -11
  41. package/dist/page-geometry.js.map +1 -1
  42. package/dist/pagination.bundle.js +1827 -410
  43. package/dist/pagination.d.ts +249 -15
  44. package/dist/pagination.d.ts.map +1 -1
  45. package/dist/pagination.js +1942 -453
  46. package/dist/pagination.js.map +1 -1
  47. package/dist/react.d.ts +13 -6
  48. package/dist/react.d.ts.map +1 -1
  49. package/dist/react.js +38 -5
  50. package/dist/react.js.map +1 -1
  51. package/dist/render-report-v2.schema.json +1581 -0
  52. package/dist/ribbon-chrome.d.ts +2 -2
  53. package/dist/ribbon-chrome.d.ts.map +1 -1
  54. package/dist/ribbon-chrome.js +72 -4
  55. package/dist/ribbon-chrome.js.map +1 -1
  56. package/dist/ribbon.js +119 -0
  57. package/dist/ribbon.js.map +1 -1
  58. package/dist/session.bundle.js +747 -29
  59. package/dist/session.d.ts +159 -19
  60. package/dist/session.d.ts.map +1 -1
  61. package/dist/session.js +593 -25
  62. package/dist/session.js.map +1 -1
  63. package/dist/types.d.ts +1287 -238
  64. package/dist/types.d.ts.map +1 -1
  65. package/dist/types.js +9 -75
  66. package/dist/types.js.map +1 -1
  67. package/dist/wasm/_framework/DocumentFormat.OpenXml.Framework.wasm +0 -0
  68. package/dist/wasm/_framework/DocumentFormat.OpenXml.Framework.wasm.br +0 -0
  69. package/dist/wasm/_framework/DocumentFormat.OpenXml.wasm +0 -0
  70. package/dist/wasm/_framework/DocumentFormat.OpenXml.wasm.br +0 -0
  71. package/dist/wasm/_framework/Docxodus.wasm +0 -0
  72. package/dist/wasm/_framework/Docxodus.wasm.br +0 -0
  73. package/dist/wasm/_framework/DocxodusWasm.wasm +0 -0
  74. package/dist/wasm/_framework/DocxodusWasm.wasm.br +0 -0
  75. package/dist/wasm/_framework/System.Collections.Concurrent.wasm +0 -0
  76. package/dist/wasm/_framework/System.Collections.Concurrent.wasm.br +0 -0
  77. package/dist/wasm/_framework/System.Collections.Immutable.wasm +0 -0
  78. package/dist/wasm/_framework/System.Collections.Immutable.wasm.br +0 -0
  79. package/dist/wasm/_framework/System.Collections.wasm +0 -0
  80. package/dist/wasm/_framework/System.Collections.wasm.br +0 -0
  81. package/dist/wasm/_framework/System.ComponentModel.Primitives.wasm +0 -0
  82. package/dist/wasm/_framework/System.ComponentModel.Primitives.wasm.br +0 -0
  83. package/dist/wasm/_framework/System.IO.Compression.wasm +0 -0
  84. package/dist/wasm/_framework/System.IO.Compression.wasm.br +0 -0
  85. package/dist/wasm/_framework/System.IO.Packaging.wasm +0 -0
  86. package/dist/wasm/_framework/System.IO.Packaging.wasm.br +0 -0
  87. package/dist/wasm/_framework/System.IO.Pipelines.wasm +0 -0
  88. package/dist/wasm/_framework/System.IO.Pipelines.wasm.br +0 -0
  89. package/dist/wasm/_framework/System.Linq.wasm +0 -0
  90. package/dist/wasm/_framework/System.Linq.wasm.br +0 -0
  91. package/dist/wasm/_framework/System.Private.CoreLib.wasm +0 -0
  92. package/dist/wasm/_framework/System.Private.CoreLib.wasm.br +0 -0
  93. package/dist/wasm/_framework/System.Private.Uri.wasm +0 -0
  94. package/dist/wasm/_framework/System.Private.Uri.wasm.br +0 -0
  95. package/dist/wasm/_framework/System.Private.Xml.Linq.wasm +0 -0
  96. package/dist/wasm/_framework/System.Private.Xml.Linq.wasm.br +0 -0
  97. package/dist/wasm/_framework/System.Private.Xml.wasm +0 -0
  98. package/dist/wasm/_framework/System.Private.Xml.wasm.br +0 -0
  99. package/dist/wasm/_framework/System.Runtime.InteropServices.JavaScript.wasm +0 -0
  100. package/dist/wasm/_framework/System.Runtime.InteropServices.JavaScript.wasm.br +0 -0
  101. package/dist/wasm/_framework/System.Runtime.wasm +0 -0
  102. package/dist/wasm/_framework/System.Runtime.wasm.br +0 -0
  103. package/dist/wasm/_framework/System.Security.Cryptography.wasm +0 -0
  104. package/dist/wasm/_framework/System.Security.Cryptography.wasm.br +0 -0
  105. package/dist/wasm/_framework/System.Text.Encodings.Web.wasm +0 -0
  106. package/dist/wasm/_framework/System.Text.Encodings.Web.wasm.br +0 -0
  107. package/dist/wasm/_framework/System.Text.Json.wasm +0 -0
  108. package/dist/wasm/_framework/System.Text.Json.wasm.br +0 -0
  109. package/dist/wasm/_framework/System.Text.RegularExpressions.wasm +0 -0
  110. package/dist/wasm/_framework/System.Text.RegularExpressions.wasm.br +0 -0
  111. package/dist/wasm/_framework/dotnet.boot.js +34 -40
  112. package/dist/wasm/_framework/dotnet.boot.js.br +0 -0
  113. package/dist/wasm/_framework/dotnet.native.js +39 -3
  114. package/dist/wasm/_framework/dotnet.native.js.br +0 -0
  115. package/dist/wasm/_framework/dotnet.native.wasm +0 -0
  116. package/dist/wasm/_framework/dotnet.native.wasm.br +0 -0
  117. package/dist/worker-proxy.bundle.js +198 -32
  118. package/dist/worker-proxy.d.ts +37 -4
  119. package/dist/worker-proxy.d.ts.map +1 -1
  120. package/dist/worker-proxy.js +187 -35
  121. package/dist/worker-proxy.js.map +1 -1
  122. package/package.json +22 -6
  123. package/dist/wasm/_framework/System.Diagnostics.Process.wasm +0 -0
  124. package/dist/wasm/_framework/System.Diagnostics.Process.wasm.br +0 -0
package/dist/index.js CHANGED
@@ -20,8 +20,8 @@ export function openDocxSession(bytes, settings) {
20
20
  export function createBlankDocx() {
21
21
  return ensureInitialized().DocxSessionBridge.CreateBlankDocx();
22
22
  }
23
- import { CommentRenderMode, PaginationMode, AnnotationLabelMode, RevisionType, ComparisonEngine, DocxDiffRevisionGranularity, DocxDiffFormatComparison, ConflictResolution, ProjectionScopes, AnchorRenderMode, TableRenderMode, TrackedChangeMode, EmptyParagraphMode, AnchorIdRendering, ProjectionDepth, DocumentElementType, ComparisonLogLevel, ComparisonLogCodes, isInsertion, isDeletion, isMove, isMoveSource, isMoveDestination, findMovePair, isFormatChange, findElementById, findElementsByType, getParagraphs, getTables, getTableColumns, targetElement, targetParagraph, targetParagraphRange, targetRun, targetTable, targetTableRow, targetTableCell, targetTableColumn, targetSearch, targetSearchInElement, } from "./types.js";
24
- export { PaginationEngine, paginateHtml } from "./pagination.js";
23
+ import { CommentRenderMode, PaginationMode, AnnotationLabelMode, RevisionType, DocxDiffRevisionGranularity, DocxDiffFormatComparison, ConflictResolution, ProjectionScopes, AnchorRenderMode, TableRenderMode, TrackedChangeMode, EmptyParagraphMode, AnchorIdRendering, ProjectionDepth, DocumentElementType, ComparisonLogLevel, ComparisonLogCodes, isInsertion, isDeletion, isMove, isFormatChange, findElementById, findElementsByType, getParagraphs, getTables, getTableColumns, targetElement, targetParagraph, targetParagraphRange, targetRun, targetTable, targetTableRow, targetTableCell, targetTableColumn, targetSearch, targetSearchInElement, } from "./types.js";
24
+ export { PaginationEngine, clearPageCitationHighlight, createUnavailablePageMap, navigateToPageCitation, paginateHtml, } from "./pagination.js";
25
25
  // Page geometry is the document's own page setup (w:sectPr), read off the section wrappers
26
26
  // the converter stamps in every render mode, plus the fit-to-width zoom a view applies to it.
27
27
  export { DEFAULT_MARGIN, DEFAULT_PAGE_HEIGHT, DEFAULT_PAGE_WIDTH, fitScale, parseSectionDimensions, ptToPx, pxToPt, } from "./page-geometry.js";
@@ -31,7 +31,7 @@ export { DocxEditor } from "./editor.js";
31
31
  // loading overlay wired onto DocxEditor's command surface. `createRibbonEditor`
32
32
  // (docxodus/embed) is the one-call version that also boots WASM.
33
33
  export { mountRibbon } from "./ribbon.js";
34
- export { CommentRenderMode, PaginationMode, AnnotationLabelMode, RevisionType, ComparisonEngine, DocxDiffRevisionGranularity, DocxDiffFormatComparison, ConflictResolution, ProjectionScopes, AnchorRenderMode, TableRenderMode, TrackedChangeMode, EmptyParagraphMode, AnchorIdRendering, ProjectionDepth, DocumentElementType, ComparisonLogLevel, ComparisonLogCodes, isInsertion, isDeletion, isMove, isMoveSource, isMoveDestination, findMovePair, isFormatChange,
34
+ export { CommentRenderMode, PaginationMode, AnnotationLabelMode, RevisionType, DocxDiffRevisionGranularity, DocxDiffFormatComparison, ConflictResolution, ProjectionScopes, AnchorRenderMode, TableRenderMode, TrackedChangeMode, EmptyParagraphMode, AnchorIdRendering, ProjectionDepth, DocumentElementType, ComparisonLogLevel, ComparisonLogCodes, isInsertion, isDeletion, isMove, isFormatChange,
35
35
  // Document structure helpers
36
36
  findElementById, findElementsByType, getParagraphs, getTables, getTableColumns,
37
37
  // Annotation target factory functions
@@ -71,7 +71,7 @@ async function yieldToMain() {
71
71
  function getDefaultWasmBasePath() {
72
72
  try {
73
73
  // import.meta.url gives us the URL of this module
74
- // e.g., "https://cdn.jsdelivr.net/npm/docxodus@3.1.1/dist/index.js"
74
+ // e.g., "https://cdn.jsdelivr.net/npm/docxodus@10.0.0/dist/index.js"
75
75
  // or "file:///path/to/node_modules/docxodus/dist/index.js"
76
76
  const moduleUrl = import.meta.url;
77
77
  // Remove the filename to get the directory
@@ -206,6 +206,71 @@ async function toBytes(input) {
206
206
  const buffer = await input.arrayBuffer();
207
207
  return new Uint8Array(buffer);
208
208
  }
209
+ /**
210
+ * Generate a deterministic, non-mutating verification manifest from DOCX bytes.
211
+ * Invalid, malformed, and encrypted packages are represented by structured findings.
212
+ */
213
+ export async function generatePackageManifest(document) {
214
+ const exports = ensureInitialized();
215
+ const bytes = await toBytes(document);
216
+ await yieldToMain();
217
+ return JSON.parse(exports.DocumentConverter.GeneratePackageManifest(bytes));
218
+ }
219
+ /**
220
+ * Run the default bounded deliverable-verification policy on exact DOCX bytes.
221
+ * Invalid, malformed, encrypted, and safety-limited packages are returned as
222
+ * structured report findings rather than editable-session errors. When supplied,
223
+ * the exact baseline bytes are used to classify pre-existing, new, and resolved findings.
224
+ */
225
+ export async function verifyDeliverable(document, baseline) {
226
+ const exports = ensureInitialized();
227
+ const bytes = await toBytes(document);
228
+ const baselineBytes = baseline === undefined ? undefined : await toBytes(baseline);
229
+ await yieldToMain();
230
+ return JSON.parse(baselineBytes === undefined
231
+ ? exports.DocumentConverter.VerifyDeliverable(bytes)
232
+ : exports.DocumentConverter.VerifyDeliverableWithBaseline(baselineBytes, bytes));
233
+ }
234
+ /**
235
+ * Verify a portable JSON delivery change receipt against supplied artifact bytes.
236
+ *
237
+ * The receipt travels as its JSON envelope string; `artifacts` maps each artifact id
238
+ * the receipt records to the exact bytes to independently re-hash against it. Omitted
239
+ * artifacts report `"missing"`. Malformed input yields a structured invalid verdict
240
+ * whose findings carry the reason — never a thrown error.
241
+ */
242
+ export async function verifyDeliveryReceipt(receiptJson, artifacts) {
243
+ const exports = ensureInitialized();
244
+ const artifactsJson = artifacts === undefined
245
+ ? ""
246
+ : JSON.stringify(Object.fromEntries(Object.entries(artifacts).map(([artifactId, bytes]) => [
247
+ artifactId,
248
+ bytesToBase64(bytes),
249
+ ])));
250
+ await yieldToMain();
251
+ return JSON.parse(exports.DocumentConverter.VerifyDeliveryReceipt(receiptJson, artifactsJson));
252
+ }
253
+ /**
254
+ * Prove that a redline's generated revisions accept to the intended final and reject to the
255
+ * selected baseline without consuming pre-existing review state.
256
+ *
257
+ * Three packages are inspected and two rebuilt, so on a UI thread prefer the worker proxy's
258
+ * `proveRedlineReversibility`. Malformed, encrypted, and safety-limited packages are reported as
259
+ * structured proof findings rather than thrown errors. The rebuilt packages are not returned:
260
+ * the proof carries their digests and the divergences between them and each expected document.
261
+ *
262
+ * @param baseline - The document the redline was generated against
263
+ * @param intendedFinal - The document accepting the generated revisions must reproduce
264
+ * @param redline - The generated redline under proof
265
+ */
266
+ export async function proveRedlineReversibility(baseline, intendedFinal, redline) {
267
+ const exports = ensureInitialized();
268
+ const baselineBytes = await toBytes(baseline);
269
+ const intendedFinalBytes = await toBytes(intendedFinal);
270
+ const redlineBytes = await toBytes(redline);
271
+ await yieldToMain();
272
+ return JSON.parse(exports.DocumentConverter.ProveRedlineReversibility(baselineBytes, intendedFinalBytes, redlineBytes));
273
+ }
209
274
  /**
210
275
  * Convert a DOCX document to HTML.
211
276
  *
@@ -319,12 +384,11 @@ export async function compareDocuments(original, modified, options) {
319
384
  // Yield to browser before heavy WASM work - allows loading states to render
320
385
  await yieldToMain();
321
386
  let result;
322
- const engine = options?.engine ?? ComparisonEngine.DocxDiff;
323
- if (options?.detailThreshold !== undefined || options?.caseInsensitive) {
324
- result = exports.DocumentComparer.CompareDocumentsWithOptions(originalBytes, modifiedBytes, options?.authorName ?? "Docxodus", options?.detailThreshold ?? 0.15, options?.caseInsensitive ?? false, engine);
387
+ if (options?.caseInsensitive) {
388
+ result = exports.DocumentComparer.CompareDocumentsWithOptions(originalBytes, modifiedBytes, options?.authorName ?? "Docxodus", options.caseInsensitive);
325
389
  }
326
390
  else {
327
- result = exports.DocumentComparer.CompareDocuments(originalBytes, modifiedBytes, options?.authorName ?? "Docxodus", engine);
391
+ result = exports.DocumentComparer.CompareDocuments(originalBytes, modifiedBytes, options?.authorName ?? "Docxodus");
328
392
  }
329
393
  if (result.length === 0) {
330
394
  throw new Error("Comparison failed - empty result");
@@ -347,14 +411,12 @@ export async function compareDocumentsToHtml(original, modified, options) {
347
411
  // Yield to browser before heavy WASM work - allows loading states to render
348
412
  await yieldToMain();
349
413
  const renderTrackedChanges = options?.renderTrackedChanges ?? true;
350
- const engine = options?.engine ?? ComparisonEngine.DocxDiff;
351
414
  let result;
352
- // Use full method when detailThreshold or caseInsensitive are specified
353
- if (options?.detailThreshold !== undefined || options?.caseInsensitive !== undefined) {
354
- result = exports.DocumentComparer.CompareDocumentsToHtmlFull(originalBytes, modifiedBytes, options?.authorName ?? "Docxodus", options?.detailThreshold ?? 0.15, options?.caseInsensitive ?? false, renderTrackedChanges, engine);
415
+ if (options?.caseInsensitive !== undefined) {
416
+ result = exports.DocumentComparer.CompareDocumentsToHtmlFull(originalBytes, modifiedBytes, options?.authorName ?? "Docxodus", options.caseInsensitive, renderTrackedChanges);
355
417
  }
356
418
  else {
357
- result = exports.DocumentComparer.CompareDocumentsToHtmlWithOptions(originalBytes, modifiedBytes, options?.authorName ?? "Docxodus", renderTrackedChanges, engine);
419
+ result = exports.DocumentComparer.CompareDocumentsToHtmlWithOptions(originalBytes, modifiedBytes, options?.authorName ?? "Docxodus", renderTrackedChanges);
358
420
  }
359
421
  if (isErrorResponse(result)) {
360
422
  const error = parseError(result);
@@ -362,107 +424,6 @@ export async function compareDocumentsToHtml(original, modified, options) {
362
424
  }
363
425
  return result;
364
426
  }
365
- /**
366
- * Compare two DOCX documents with logging enabled.
367
- * Returns both the redlined document and a log of any warnings/errors encountered.
368
- * This allows the comparison to continue past recoverable issues (like orphaned footnotes)
369
- * while providing visibility into what was fixed or skipped.
370
- *
371
- * @param original - Original DOCX document
372
- * @param modified - Modified DOCX document
373
- * @param options - Comparison options
374
- * @returns Result with document bytes and log entries
375
- *
376
- * @example
377
- * ```typescript
378
- * const result = await compareDocumentsWithLog(original, modified, {
379
- * authorName: "Reviewer",
380
- * detailThreshold: 0.15
381
- * });
382
- *
383
- * if (result.success) {
384
- * // Use result.document (Uint8Array)
385
- * if (result.hasWarnings) {
386
- * console.log("Warnings during comparison:");
387
- * for (const entry of result.log) {
388
- * console.log(` [${entry.level}] ${entry.code}: ${entry.message}`);
389
- * }
390
- * }
391
- * } else {
392
- * console.error(`Comparison failed: ${result.error}`);
393
- * }
394
- * ```
395
- */
396
- export async function compareDocumentsWithLog(original, modified, options) {
397
- const exports = ensureInitialized();
398
- const originalBytes = await toBytes(original);
399
- const modifiedBytes = await toBytes(modified);
400
- await yieldToMain();
401
- const result = exports.DocumentComparer.CompareDocumentsWithLog(originalBytes, modifiedBytes, options?.authorName ?? "Docxodus", options?.detailThreshold ?? 0.15, options?.caseInsensitive ?? false);
402
- const parsed = JSON.parse(result);
403
- return {
404
- success: parsed.Success ?? parsed.success ?? false,
405
- document: parsed.DocumentBase64 || parsed.documentBase64
406
- ? Uint8Array.from(atob(parsed.DocumentBase64 || parsed.documentBase64), c => c.charCodeAt(0))
407
- : undefined,
408
- error: parsed.Error ?? parsed.error,
409
- log: convertLogEntries(parsed.Log || parsed.log || []),
410
- hasWarnings: parsed.HasWarnings ?? parsed.hasWarnings ?? false,
411
- hasErrors: parsed.HasErrors ?? parsed.hasErrors ?? false,
412
- };
413
- }
414
- /**
415
- * Compare two DOCX documents to HTML with logging enabled.
416
- * Returns both the HTML output and a log of any warnings/errors encountered.
417
- *
418
- * @param original - Original DOCX document
419
- * @param modified - Modified DOCX document
420
- * @param options - Comparison options
421
- * @returns Result with HTML and log entries
422
- *
423
- * @example
424
- * ```typescript
425
- * const result = await compareDocumentsToHtmlWithLog(original, modified, {
426
- * authorName: "Reviewer",
427
- * renderTrackedChanges: true
428
- * });
429
- *
430
- * if (result.success) {
431
- * document.getElementById("viewer").innerHTML = result.html;
432
- * if (result.hasWarnings) {
433
- * console.log(`${result.log.length} warnings during comparison`);
434
- * }
435
- * }
436
- * ```
437
- */
438
- export async function compareDocumentsToHtmlWithLog(original, modified, options) {
439
- const exports = ensureInitialized();
440
- const originalBytes = await toBytes(original);
441
- const modifiedBytes = await toBytes(modified);
442
- await yieldToMain();
443
- const result = exports.DocumentComparer.CompareDocumentsToHtmlWithLog(originalBytes, modifiedBytes, options?.authorName ?? "Docxodus", options?.detailThreshold ?? 0.15, options?.caseInsensitive ?? false, options?.renderTrackedChanges ?? true);
444
- const parsed = JSON.parse(result);
445
- return {
446
- success: parsed.Success ?? parsed.success ?? false,
447
- html: parsed.Html ?? parsed.html,
448
- error: parsed.Error ?? parsed.error,
449
- log: convertLogEntries(parsed.Log || parsed.log || []),
450
- hasWarnings: parsed.HasWarnings ?? parsed.hasWarnings ?? false,
451
- hasErrors: parsed.HasErrors ?? parsed.hasErrors ?? false,
452
- };
453
- }
454
- /**
455
- * Convert log entries from PascalCase to camelCase.
456
- */
457
- function convertLogEntries(entries) {
458
- return entries.map((e) => ({
459
- level: e.Level ?? e.level ?? "Info",
460
- code: e.Code ?? e.code ?? "",
461
- message: e.Message ?? e.message ?? "",
462
- details: e.Details ?? e.details,
463
- location: e.Location ?? e.location,
464
- }));
465
- }
466
427
  /**
467
428
  * Get revisions from a compared document.
468
429
  *
@@ -488,35 +449,18 @@ function convertLogEntries(entries) {
488
449
  * const revisions = await getRevisions(comparedDoc, { detectMoves: false });
489
450
  * ```
490
451
  */
491
- export async function getRevisions(document, options) {
452
+ export async function getRevisions(document) {
492
453
  const exports = ensureInitialized();
493
454
  const bytes = await toBytes(document);
494
455
  // Yield to browser before WASM work - allows loading states to render
495
456
  await yieldToMain();
496
- // Apply defaults for move detection options
497
- const detectMoves = options?.detectMoves ?? true;
498
- const moveSimilarityThreshold = options?.moveSimilarityThreshold ?? 0.8;
499
- const moveMinimumWordCount = options?.moveMinimumWordCount ?? 3;
500
- const caseInsensitive = options?.caseInsensitive ?? false;
501
- const result = exports.DocumentComparer.GetRevisionsJsonWithOptions(bytes, detectMoves, moveSimilarityThreshold, moveMinimumWordCount, caseInsensitive);
457
+ const result = exports.DocumentComparer.GetRevisionsJson(bytes);
502
458
  if (isErrorResponse(result)) {
503
459
  const error = parseError(result);
504
460
  throw new Error(`Failed to get revisions: ${error.error}`);
505
461
  }
506
- const parsed = JSON.parse(result);
507
- return (parsed.Revisions || parsed.revisions || []).map((r) => ({
508
- author: r.Author || r.author,
509
- date: r.Date || r.date,
510
- revisionType: r.RevisionType || r.revisionType,
511
- text: r.Text || r.text,
512
- moveGroupId: r.MoveGroupId ?? r.moveGroupId,
513
- isMoveSource: r.IsMoveSource ?? r.isMoveSource,
514
- formatChange: (r.FormatChange || r.formatChange) ? {
515
- oldProperties: r.FormatChange?.OldProperties || r.formatChange?.oldProperties,
516
- newProperties: r.FormatChange?.NewProperties || r.formatChange?.newProperties,
517
- changedPropertyNames: r.FormatChange?.ChangedPropertyNames || r.formatChange?.changedPropertyNames,
518
- } : undefined,
519
- }));
462
+ // The payload is the session's own revision wire shape, so it needs no remapping.
463
+ return JSON.parse(result);
520
464
  }
521
465
  // ─── DocxDiff (IR diff engine) ──────────────────────────────────────────────
522
466
  //
@@ -569,20 +513,98 @@ export async function docxDiffGetRevisions(left, right, settings) {
569
513
  throw new Error(`Failed to get DocxDiff revisions: ${error.error}`);
570
514
  }
571
515
  const parsed = JSON.parse(result);
572
- return (parsed.revisions || parsed.Revisions || []).map((r) => ({
573
- revisionType: r.revisionType ?? r.RevisionType,
574
- text: r.text ?? r.Text,
575
- author: r.author ?? r.Author,
576
- date: r.date ?? r.Date,
577
- moveGroupId: r.moveGroupId ?? r.MoveGroupId ?? undefined,
578
- isMoveSource: r.isMoveSource ?? r.IsMoveSource ?? undefined,
579
- formatChange: (r.formatChange || r.FormatChange) ? {
580
- oldProperties: r.formatChange?.oldProperties ?? r.FormatChange?.OldProperties,
581
- newProperties: r.formatChange?.newProperties ?? r.FormatChange?.NewProperties,
582
- changedPropertyNames: r.formatChange?.changedPropertyNames ?? r.FormatChange?.ChangedPropertyNames,
583
- } : undefined,
584
- leftAnchor: r.leftAnchor ?? r.LeftAnchor ?? undefined,
585
- rightAnchor: r.rightAnchor ?? r.RightAnchor ?? undefined,
516
+ return (parsed.revisions || parsed.Revisions || []).map(mapDocxDiffRevision);
517
+ }
518
+ /**
519
+ * Compare two DOCX documents ONCE and return every requested data product from
520
+ * that single memoized pass (issue #594). Where a review pipeline calling
521
+ * {@link docxDiffCompare}, {@link docxDiffGetRevisions}, and
522
+ * {@link docxDiffGetEditScript} separately pays for the alignment per call, this
523
+ * runs it once — each product identical to its standalone counterpart (the edit
524
+ * script is handed over parsed rather than as the serialized string).
525
+ *
526
+ * @param left - The earlier/original document.
527
+ * @param right - The later/revised document.
528
+ * @param settings - Optional {@link DocxDiffSettings}; omit for engine defaults.
529
+ * @param products - Products to compute; omit for all four.
530
+ * @throws Error if the operation fails.
531
+ */
532
+ export async function docxDiffCompareProducts(left, right, settings, products) {
533
+ const exports = ensureInitialized();
534
+ const leftBytes = await toBytes(left);
535
+ const rightBytes = await toBytes(right);
536
+ await yieldToMain();
537
+ const result = exports.DocxDiffBridge.CompareProductsJson(leftBytes, rightBytes, docxDiffSettingsJson(settings), products ? JSON.stringify(products) : "");
538
+ if (isErrorResponse(result)) {
539
+ const error = parseError(result);
540
+ throw new Error(`Failed to compare products: ${error.error}`);
541
+ }
542
+ const parsed = JSON.parse(result);
543
+ return {
544
+ redline: typeof parsed.redlineB64 === "string"
545
+ ? Uint8Array.from(atob(parsed.redlineB64), c => c.charCodeAt(0))
546
+ : undefined,
547
+ revisions: Array.isArray(parsed.revisions)
548
+ ? parsed.revisions.map(mapDocxDiffRevision)
549
+ : undefined,
550
+ editScript: parsed.editScript !== undefined
551
+ ? parsed.editScript
552
+ : undefined,
553
+ semanticChanges: parsed.semanticChanges !== undefined
554
+ ? parsed.semanticChanges
555
+ : undefined,
556
+ };
557
+ }
558
+ /**
559
+ * Compare ONE baseline against MANY candidates, reading the baseline once (issue #617).
560
+ *
561
+ * The read is the single largest stage of a comparison, and a fan-out — one negotiated
562
+ * draft against every counterparty's markup — otherwise re-reads the baseline for each
563
+ * one. This reads it once and compares every candidate against that snapshot; each
564
+ * result is identical to what {@link docxDiffCompareProducts} returns for the same pair.
565
+ *
566
+ * A candidate that fails carries an `error` instead of products; the rest of the batch
567
+ * still comes back, because one malformed markup should not cost the other ninety-nine.
568
+ *
569
+ * @param baseline - The shared left-hand document.
570
+ * @param candidates - The documents to compare against it, in order.
571
+ * @param settings - Optional {@link DocxDiffSettings}; omit for engine defaults.
572
+ * @param products - Products to compute; omit for all four.
573
+ * @throws Error if the batch itself fails (a bad baseline, malformed settings).
574
+ */
575
+ export async function docxDiffCompareBatch(baseline, candidates, settings, products) {
576
+ const exports = ensureInitialized();
577
+ const baselineBytes = await toBytes(baseline);
578
+ const payload = [];
579
+ for (let i = 0; i < candidates.length; i++) {
580
+ const bytes = await toBytes(candidates[i].document);
581
+ payload.push({
582
+ name: candidates[i].name ?? String(i),
583
+ docB64: bytesToBase64(bytes),
584
+ });
585
+ }
586
+ await yieldToMain();
587
+ const result = exports.DocxDiffBridge.CompareBatchJson(baselineBytes, JSON.stringify(payload), docxDiffSettingsJson(settings), products ? JSON.stringify(products) : "");
588
+ if (isErrorResponse(result)) {
589
+ const error = parseError(result);
590
+ throw new Error(`Failed to compare batch: ${error.error}`);
591
+ }
592
+ const parsed = JSON.parse(result);
593
+ return (parsed.results ?? []).map((entry) => ({
594
+ name: String(entry.name ?? ""),
595
+ error: typeof entry.error === "string" ? entry.error : undefined,
596
+ redline: typeof entry.redlineB64 === "string"
597
+ ? Uint8Array.from(atob(entry.redlineB64), c => c.charCodeAt(0))
598
+ : undefined,
599
+ revisions: Array.isArray(entry.revisions)
600
+ ? entry.revisions.map(mapDocxDiffRevision)
601
+ : undefined,
602
+ editScript: entry.editScript !== undefined
603
+ ? entry.editScript
604
+ : undefined,
605
+ semanticChanges: entry.semanticChanges !== undefined
606
+ ? entry.semanticChanges
607
+ : undefined,
586
608
  }));
587
609
  }
588
610
  /**
@@ -609,6 +631,23 @@ export async function docxDiffGetEditScript(left, right, settings) {
609
631
  }
610
632
  return result;
611
633
  }
634
+ /**
635
+ * Compare two DOCX documents and return the stable, versioned semantic-change
636
+ * schema. This is the audit/verification surface; it classifies document meaning
637
+ * beyond the renderer's internal edit script and preserves unknown package changes.
638
+ */
639
+ export async function docxDiffGetSemanticChanges(left, right, settings) {
640
+ const exports = ensureInitialized();
641
+ const leftBytes = await toBytes(left);
642
+ const rightBytes = await toBytes(right);
643
+ await yieldToMain();
644
+ const result = exports.DocxDiffBridge.GetSemanticChangesJson(leftBytes, rightBytes, docxDiffSettingsJson(settings));
645
+ if (isErrorResponse(result)) {
646
+ const error = parseError(result);
647
+ throw new Error(`Failed to get semantic changes: ${error.error}`);
648
+ }
649
+ return JSON.parse(result);
650
+ }
612
651
  /**
613
652
  * Accept every tracked revision in a redlined DOCX and return the resulting bytes
614
653
  * (materializes the "right"/revised side). The byte-in, byte-out counterpart of
@@ -1055,6 +1094,7 @@ export async function getDocumentStructure(document) {
1055
1094
  // Convert from PascalCase to camelCase
1056
1095
  const convertElement = (el) => ({
1057
1096
  id: el.Id || el.id,
1097
+ anchorId: el.AnchorId || el.anchorId,
1058
1098
  type: el.Type || el.type,
1059
1099
  textPreview: el.TextPreview || el.textPreview,
1060
1100
  index: el.Index ?? el.index,
@@ -1066,8 +1106,12 @@ export async function getDocumentStructure(document) {
1066
1106
  });
1067
1107
  const convertTableColumn = (col) => ({
1068
1108
  tableId: col.TableId || col.tableId,
1109
+ anchorId: col.AnchorId || col.anchorId,
1110
+ tableAnchorId: col.TableAnchorId || col.tableAnchorId,
1111
+ isVirtual: col.IsVirtual ?? col.isVirtual ?? false,
1069
1112
  columnIndex: col.ColumnIndex ?? col.columnIndex,
1070
1113
  cellIds: col.CellIds || col.cellIds || [],
1114
+ cellAnchorIds: col.CellAnchorIds || col.cellAnchorIds || [],
1071
1115
  rowCount: col.RowCount ?? col.rowCount,
1072
1116
  });
1073
1117
  const root = convertElement(parsed.Root || parsed.root);
@@ -1168,6 +1212,7 @@ export async function getDocumentMetadata(document) {
1168
1212
  hasTrackedChanges: parsed.HasTrackedChanges ?? parsed.hasTrackedChanges,
1169
1213
  hasComments: parsed.HasComments ?? parsed.hasComments,
1170
1214
  estimatedPageCount: parsed.EstimatedPageCount ?? parsed.estimatedPageCount,
1215
+ estimatedPageCountSource: parsed.EstimatedPageCountSource ?? parsed.estimatedPageCountSource ?? "heuristic",
1171
1216
  };
1172
1217
  }
1173
1218
  /**