@stll/folio-core 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (194) hide show
  1. package/dist/ai-edits/apply.js +602 -50
  2. package/dist/ai-edits/block-identity.d.ts +4 -0
  3. package/dist/ai-edits/block-identity.js +9 -0
  4. package/dist/ai-edits/blockRange.d.ts +28 -1
  5. package/dist/ai-edits/blockRange.js +67 -1
  6. package/dist/ai-edits/headless.d.ts +4 -4
  7. package/dist/ai-edits/headless.js +9 -9
  8. package/dist/ai-edits/index.d.ts +2 -2
  9. package/dist/ai-edits/index.js +2 -2
  10. package/dist/ai-edits/read.d.ts +7 -7
  11. package/dist/ai-edits/read.js +110 -3
  12. package/dist/ai-edits/table-cell-mutations.d.ts +48 -0
  13. package/dist/ai-edits/table-cell-mutations.js +237 -0
  14. package/dist/ai-edits/table-mutation-plan.d.ts +38 -0
  15. package/dist/ai-edits/table-mutation-plan.js +90 -0
  16. package/dist/ai-edits/table-row-column-mutations.d.ts +100 -0
  17. package/dist/ai-edits/table-row-column-mutations.js +300 -0
  18. package/dist/ai-edits/table-targets.d.ts +32 -0
  19. package/dist/ai-edits/table-targets.js +70 -0
  20. package/dist/ai-edits/types.d.ts +34 -0
  21. package/dist/compat/eigenpal.js +1 -1
  22. package/dist/content-controls/mutateContentControls.js +1 -1
  23. package/dist/controller/activeEditorStory.d.ts +36 -0
  24. package/dist/controller/activeEditorStory.js +30 -0
  25. package/dist/controller/contentControlWidgetController.d.ts +55 -0
  26. package/dist/controller/contentControlWidgetController.js +141 -0
  27. package/dist/controller/fontReadiness.js +2 -0
  28. package/dist/controller/headerFooterEditorManager.js +1 -1
  29. package/dist/controller/hiddenEditorApi.js +2 -2
  30. package/dist/controller/hiddenEditorManager.d.ts +12 -1
  31. package/dist/controller/hiddenEditorManager.js +32 -11
  32. package/dist/controller/layoutPipeline.d.ts +2 -0
  33. package/dist/controller/layoutPipeline.js +16 -1
  34. package/dist/controller/noteEditorManager.d.ts +36 -0
  35. package/dist/controller/noteEditorManager.js +210 -0
  36. package/dist/document-operations.d.ts +35 -3
  37. package/dist/document-operations.js +161 -3
  38. package/dist/docx/blockContentParser.js +2 -2
  39. package/dist/docx/commentParser.js +6 -1
  40. package/dist/docx/conformance.js +8 -1
  41. package/dist/docx/documentGrid.d.ts +8 -0
  42. package/dist/docx/documentGrid.js +8 -0
  43. package/dist/docx/drawingUtils.js +1 -1
  44. package/dist/docx/ensureParaIds.js +1 -1
  45. package/dist/docx/fieldParser.js +1 -1
  46. package/dist/docx/notePropertiesParser.js +1 -1
  47. package/dist/docx/numberingParser.js +1 -1
  48. package/dist/docx/paragraphParser.js +63 -58
  49. package/dist/docx/paragraphTextBoxEnrichment.d.ts +2 -1
  50. package/dist/docx/paragraphTextBoxEnrichment.js +67 -13
  51. package/dist/docx/paragraphTraversal.js +1 -1
  52. package/dist/docx/parser.js +3 -3
  53. package/dist/docx/rezip.js +1 -1
  54. package/dist/docx/runParser.js +4 -2
  55. package/dist/docx/sdtProperties.js +1 -1
  56. package/dist/docx/sectionParser.js +1 -1
  57. package/dist/docx/selectiveSave.js +1 -1
  58. package/dist/docx/serializer/commentSerializer.js +1 -2
  59. package/dist/docx/serializer/documentSerializer.js +2 -2
  60. package/dist/docx/serializer/headerFooterSerializer.js +2 -2
  61. package/dist/docx/serializer/noteSerializer.js +2 -2
  62. package/dist/docx/serializer/paragraphSerializer.js +7 -1
  63. package/dist/docx/serializer/runSerializer.js +4 -2
  64. package/dist/docx/serializer/stylesSerializer.js +1 -1
  65. package/dist/docx/serializer/tableSerializer.d.ts +5 -4
  66. package/dist/docx/serializer/tableSerializer.js +17 -13
  67. package/dist/docx/server/applyDocxXmlPatchProposal.d.ts +87 -0
  68. package/dist/docx/server/applyDocxXmlPatchProposal.js +204 -0
  69. package/dist/docx/server/boundedArchive.d.ts +14 -3
  70. package/dist/docx/server/boundedArchive.js +56 -11
  71. package/dist/docx/server/evaluateDocxXmlPatchProposal.d.ts +85 -0
  72. package/dist/docx/server/evaluateDocxXmlPatchProposal.js +361 -0
  73. package/dist/docx/server/inspectDocxPackage.d.ts +70 -0
  74. package/dist/docx/server/inspectDocxPackage.js +217 -0
  75. package/dist/docx/server/validateDocxConformance.d.ts +41 -0
  76. package/dist/docx/server/validateDocxConformance.js +336 -0
  77. package/dist/docx/settingsParser.js +11 -1
  78. package/dist/docx/shapeParser.js +1 -1
  79. package/dist/docx/styleParser.js +4 -2
  80. package/dist/docx/tableParser.js +18 -7
  81. package/dist/docx/textBoxParser.d.ts +3 -3
  82. package/dist/docx/textBoxParser.js +16 -12
  83. package/dist/docx/xmlSafety.d.ts +5 -0
  84. package/dist/docx/xmlSafety.js +9 -0
  85. package/dist/fields/resolveFieldValues.js +1 -3
  86. package/dist/i18n/messages/catalogs.gen.d.ts +17 -0
  87. package/dist/i18n/messages/catalogs.gen.js +17 -0
  88. package/dist/i18n/messages/messages.gen.d.ts +1 -0
  89. package/dist/index.js +1 -1
  90. package/dist/layout-bridge/convert/footnoteLayout.d.ts +1 -0
  91. package/dist/layout-bridge/convert/footnoteLayout.js +47 -40
  92. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +1 -0
  93. package/dist/layout-bridge/convert/headerFooterLayout.js +3 -0
  94. package/dist/layout-bridge/convert/templatePreviewFlow.js +3 -3
  95. package/dist/layout-bridge/convert/toFlowBlocks.d.ts +4 -2
  96. package/dist/layout-bridge/convert/toFlowBlocks.js +136 -20
  97. package/dist/layout-bridge/dom/noteStoryDom.d.ts +6 -0
  98. package/dist/layout-bridge/dom/noteStoryDom.js +17 -0
  99. package/dist/layout-bridge/engine/clickToPosition.js +2 -2
  100. package/dist/layout-engine/index.d.ts +2 -2
  101. package/dist/layout-engine/index.js +43 -28
  102. package/dist/layout-engine/justificationCompatibility.d.ts +6 -0
  103. package/dist/layout-engine/justificationCompatibility.js +8 -0
  104. package/dist/layout-engine/keep-together.js +11 -1
  105. package/dist/layout-engine/measure/cache.js +21 -5
  106. package/dist/layout-engine/measure/lineBreakProvider.js +53 -34
  107. package/dist/layout-engine/measure/measureBlocks.js +6 -3
  108. package/dist/layout-engine/measure/measureContainer.js +1 -1
  109. package/dist/layout-engine/measure/measureHelpers.d.ts +1 -1
  110. package/dist/layout-engine/measure/measureHelpers.js +3 -3
  111. package/dist/layout-engine/measure/measureParagraph.js +69 -25
  112. package/dist/layout-engine/measure/measureProvider.js +4 -3
  113. package/dist/layout-engine/measure/tableCellFlow.js +5 -2
  114. package/dist/layout-engine/measure/textBoxParagraphLayout.d.ts +9 -7
  115. package/dist/layout-engine/measure/textBoxParagraphLayout.js +22 -6
  116. package/dist/layout-engine/measure/textMeasurementPolicy.d.ts +3 -1
  117. package/dist/layout-engine/measure/textMeasurementPolicy.js +7 -1
  118. package/dist/layout-engine/paginator.js +5 -1
  119. package/dist/layout-engine/paragraphSpacing.d.ts +28 -4
  120. package/dist/layout-engine/paragraphSpacing.js +107 -13
  121. package/dist/layout-engine/renderedBreakReconciliation.js +1 -1
  122. package/dist/layout-engine/types.d.ts +26 -13
  123. package/dist/layout-painter/registry/modules/textBox.js +5 -1
  124. package/dist/layout-painter/renderPage.d.ts +5 -2
  125. package/dist/layout-painter/renderPage.js +56 -12
  126. package/dist/layout-painter/renderParagraph.js +22 -6
  127. package/dist/layout-painter/renderTable.d.ts +5 -1
  128. package/dist/layout-painter/renderTable.js +5 -2
  129. package/dist/layout-painter/renderTextBox.d.ts +2 -1
  130. package/dist/layout-painter/renderTextBox.js +25 -14
  131. package/dist/managers/DocumentLoaderManager.js +1 -1
  132. package/dist/model.js +1 -1
  133. package/dist/prosemirror/attrs/index.d.ts +4 -2
  134. package/dist/prosemirror/attrs/index.js +154 -18
  135. package/dist/prosemirror/commands/comments.d.ts +3 -3
  136. package/dist/prosemirror/commands/comments.js +248 -12
  137. package/dist/prosemirror/commands/contentControls.js +1 -1
  138. package/dist/prosemirror/commands/index.js +1 -1
  139. package/dist/prosemirror/commands/pageBreak.js +1 -1
  140. package/dist/prosemirror/commands/propertyChangeScope.d.ts +2 -2
  141. package/dist/prosemirror/commands/propertyChangeScope.js +3 -2
  142. package/dist/prosemirror/commands/tableCellMergeResolution.d.ts +9 -0
  143. package/dist/prosemirror/commands/tableCellMergeResolution.js +181 -0
  144. package/dist/prosemirror/conversion/fromProseDoc.d.ts +2 -1
  145. package/dist/prosemirror/conversion/fromProseDoc.js +286 -61
  146. package/dist/prosemirror/conversion/sdtAttrs.d.ts +9 -0
  147. package/dist/prosemirror/conversion/sdtAttrs.js +49 -0
  148. package/dist/prosemirror/conversion/toProseDoc.d.ts +2 -1
  149. package/dist/prosemirror/conversion/toProseDoc.js +347 -98
  150. package/dist/prosemirror/extensions/ExtensionManager.js +1 -1
  151. package/dist/prosemirror/extensions/StarterKit.js +6 -1
  152. package/dist/prosemirror/extensions/core/ParagraphExtension.js +4 -1
  153. package/dist/prosemirror/extensions/features/BaseKeymapExtension.js +1 -0
  154. package/dist/prosemirror/extensions/features/ListExtension.js +1 -1
  155. package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.d.ts +10 -1
  156. package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +93 -2
  157. package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.d.ts +2 -1
  158. package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.js +6 -2
  159. package/dist/prosemirror/extensions/marks/CharacterSpacingExtension.js +1 -1
  160. package/dist/prosemirror/extensions/marks/TrackedChangeExtensions.d.ts +9 -1
  161. package/dist/prosemirror/extensions/marks/TrackedChangeExtensions.js +24 -1
  162. package/dist/prosemirror/extensions/nodes/FieldExtension.js +1 -1
  163. package/dist/prosemirror/extensions/nodes/RenderedPageBreakExtension.d.ts +7 -0
  164. package/dist/prosemirror/extensions/nodes/RenderedPageBreakExtension.js +22 -0
  165. package/dist/prosemirror/extensions/nodes/TableExtension.js +9 -3
  166. package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.d.ts +13 -0
  167. package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.js +41 -0
  168. package/dist/prosemirror/extensions/nodes/TextBoxExtension.d.ts +5 -2
  169. package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +4 -1
  170. package/dist/prosemirror/plugins/contentControlWidgets.d.ts +34 -8
  171. package/dist/prosemirror/plugins/contentControlWidgets.js +125 -91
  172. package/dist/prosemirror/plugins/selectionTracker.js +1 -0
  173. package/dist/prosemirror/plugins/suggestionMode.js +1 -1
  174. package/dist/prosemirror/schema/index.d.ts +2 -2
  175. package/dist/prosemirror/schema/marks.d.ts +5 -1
  176. package/dist/prosemirror/schema/nodes.d.ts +64 -7
  177. package/dist/prosemirror/selectionState.js +1 -0
  178. package/dist/prosemirror/styles/resolvedStyleAttrs.js +1 -0
  179. package/dist/prosemirror/tableCellMergeRevision.d.ts +9 -0
  180. package/dist/prosemirror/tableCellMergeRevision.js +22 -0
  181. package/dist/prosemirror/textBoxAnchorAttrs.d.ts +9 -0
  182. package/dist/prosemirror/textBoxAnchorAttrs.js +33 -0
  183. package/dist/prosemirror/utils/extractTrackedChanges.d.ts +1 -1
  184. package/dist/prosemirror/utils/extractTrackedChanges.js +62 -14
  185. package/dist/prosemirror/validation.js +9 -1
  186. package/dist/server.d.ts +6 -2
  187. package/dist/server.js +5 -1
  188. package/dist/utils/fontFamilyMerge.js +9 -11
  189. package/dist/utils/fontLoader.js +3 -2
  190. package/dist/utils/fontResolver.js +22 -4
  191. package/dist/utils/fontWeights.d.ts +1 -1
  192. package/dist/utils/fontWeights.js +1 -1
  193. package/dist/utils/paragraphFormattingMerge.js +1 -0
  194. package/package.json +1 -1
@@ -0,0 +1,204 @@
1
+ import { loadDocxArchive } from "./boundedArchive.js";
2
+ import { FOLIO_DOCX_XML_PATCH_PROPOSAL_PROFILE, InvalidFolioDocxXmlPatchProposalError, evaluateDocxXmlPatchProposal, parseFolioDocxXmlPatchProposal } from "./evaluateDocxXmlPatchProposal.js";
3
+ import { FOLIO_DOCX_CONFORMANCE_PROFILE, validateDocxConformance } from "./validateDocxConformance.js";
4
+ import { TaggedError, panic } from "better-result";
5
+ import JSZip from "jszip";
6
+ import { createHash } from "node:crypto";
7
+ //#region src/docx/server/applyDocxXmlPatchProposal.ts
8
+ const FOLIO_DOCX_XML_PATCH_APPLICATION_VERSION = 1;
9
+ const FOLIO_DOCX_XML_PATCH_APPLICATION_PROFILE = "folio-xml-patch-application-v1";
10
+ const isConformantReport = (report) => report.status === "conformant";
11
+ var UnsupportedFolioDocxXmlPatchApplicationProfileError = class extends TaggedError("UnsupportedFolioDocxXmlPatchApplicationProfileError")() {};
12
+ var FolioDocxXmlPatchApplicationError = class extends TaggedError("FolioDocxXmlPatchApplicationError")() {};
13
+ const sha256 = (bytes) => createHash("sha256").update(bytes).digest("hex");
14
+ const snapshotInput = (bytes) => Uint8Array.from(bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes));
15
+ const snapshotArchiveOptions = (archive) => {
16
+ if (archive === void 0) return;
17
+ return {
18
+ ...archive.maxInputBytes === void 0 ? {} : { maxInputBytes: archive.maxInputBytes },
19
+ ...archive.maxEntryBytes === void 0 ? {} : { maxEntryBytes: archive.maxEntryBytes },
20
+ ...archive.maxTotalBytes === void 0 ? {} : { maxTotalBytes: archive.maxTotalBytes },
21
+ ...archive.maxEntries === void 0 ? {} : { maxEntries: archive.maxEntries }
22
+ };
23
+ };
24
+ const snapshotProposalLimits = (limits) => {
25
+ if (limits === void 0) return;
26
+ return {
27
+ ...limits.maxReplacements === void 0 ? {} : { maxReplacements: limits.maxReplacements },
28
+ ...limits.maxPartBytes === void 0 ? {} : { maxPartBytes: limits.maxPartBytes },
29
+ ...limits.maxTotalBytes === void 0 ? {} : { maxTotalBytes: limits.maxTotalBytes }
30
+ };
31
+ };
32
+ const evaluateProposal = async ({ bytes, proposal, allowedParts, archive, limits }) => await evaluateDocxXmlPatchProposal({
33
+ bytes,
34
+ proposal,
35
+ allowedParts,
36
+ ...archive === void 0 ? {} : { archive },
37
+ ...limits === void 0 ? {} : { limits }
38
+ });
39
+ const proposalRejected = ({ evaluation }) => ({
40
+ version: 1,
41
+ profile: FOLIO_DOCX_XML_PATCH_APPLICATION_PROFILE,
42
+ status: "proposal-rejected",
43
+ producesOutput: false,
44
+ evaluation,
45
+ conformance: null,
46
+ receipt: null
47
+ });
48
+ const verifyArchiveContents = async (bytes, options) => {
49
+ let archive;
50
+ try {
51
+ archive = await loadDocxArchive(bytes, options);
52
+ } catch (cause) {
53
+ throw new FolioDocxXmlPatchApplicationError({
54
+ message: "The evaluated package could not be verified within the archive limits.",
55
+ stage: "verify",
56
+ cause
57
+ });
58
+ }
59
+ for (const entry of archive.entryMetadata.toSorted(({ path: left }, { path: right }) => left < right ? -1 : Number(left > right))) {
60
+ if (entry.directory) continue;
61
+ try {
62
+ if (await archive.readEntryUint8(entry.path) === null) throw new FolioDocxXmlPatchApplicationError({
63
+ message: "An archive entry disappeared during verification.",
64
+ stage: "verify",
65
+ part: entry.path
66
+ });
67
+ } catch (cause) {
68
+ if (cause instanceof FolioDocxXmlPatchApplicationError) throw cause;
69
+ throw new FolioDocxXmlPatchApplicationError({
70
+ message: "An archive entry could not be verified within the configured limits.",
71
+ stage: "verify",
72
+ part: entry.path,
73
+ cause
74
+ });
75
+ }
76
+ }
77
+ };
78
+ const applyReplacements = async ({ bytes, proposal }) => {
79
+ let zip;
80
+ try {
81
+ zip = await JSZip.loadAsync(bytes);
82
+ } catch (cause) {
83
+ throw new FolioDocxXmlPatchApplicationError({
84
+ message: "The evaluated package could not be loaded for replacement.",
85
+ stage: "load",
86
+ cause
87
+ });
88
+ }
89
+ for (const replacement of proposal.replacements) {
90
+ const current = zip.file(replacement.path);
91
+ if (current === null) throw new FolioDocxXmlPatchApplicationError({
92
+ message: "An evaluated package part was unavailable during replacement.",
93
+ stage: "replace"
94
+ });
95
+ zip.file(replacement.path, new TextEncoder().encode(replacement.replacementXml), {
96
+ binary: true,
97
+ date: current.date,
98
+ comment: current.comment,
99
+ createFolders: false,
100
+ unixPermissions: current.unixPermissions,
101
+ dosPermissions: current.dosPermissions,
102
+ compression: "DEFLATE",
103
+ compressionOptions: { level: 6 }
104
+ });
105
+ }
106
+ try {
107
+ return await zip.generateAsync({
108
+ type: "uint8array",
109
+ compression: "DEFLATE",
110
+ compressionOptions: { level: 6 }
111
+ });
112
+ } catch (cause) {
113
+ throw new FolioDocxXmlPatchApplicationError({
114
+ message: "The replacement package could not be generated.",
115
+ stage: "generate",
116
+ cause
117
+ });
118
+ }
119
+ };
120
+ const outputRejected = ({ evaluation, conformance }) => ({
121
+ version: 1,
122
+ profile: FOLIO_DOCX_XML_PATCH_APPLICATION_PROFILE,
123
+ status: "output-rejected",
124
+ producesOutput: false,
125
+ evaluation,
126
+ conformance,
127
+ receipt: null
128
+ });
129
+ const applied = ({ input, output, evaluation, conformance }) => ({
130
+ version: 1,
131
+ profile: FOLIO_DOCX_XML_PATCH_APPLICATION_PROFILE,
132
+ status: "applied",
133
+ producesOutput: true,
134
+ evaluation,
135
+ conformance,
136
+ receipt: {
137
+ version: 1,
138
+ profile: FOLIO_DOCX_XML_PATCH_APPLICATION_PROFILE,
139
+ proposalProfile: FOLIO_DOCX_XML_PATCH_PROPOSAL_PROFILE,
140
+ validationProfile: FOLIO_DOCX_CONFORMANCE_PROFILE,
141
+ input: {
142
+ sha256: sha256(input),
143
+ byteLength: input.byteLength
144
+ },
145
+ output: {
146
+ sha256: sha256(output),
147
+ byteLength: output.byteLength
148
+ },
149
+ replacements: evaluation.replacements
150
+ },
151
+ bytes: output
152
+ });
153
+ /** Apply a guarded XML proposal and return output only after full profile validation. */
154
+ const applyDocxXmlPatchProposal = async ({ bytes, proposal: rawProposal, allowedParts, validationProfile, archive, limits }) => {
155
+ if (validationProfile !== "folio-supported-v1") throw new UnsupportedFolioDocxXmlPatchApplicationProfileError({
156
+ message: "The requested package validation profile is not supported.",
157
+ profile: validationProfile
158
+ });
159
+ const input = snapshotInput(bytes);
160
+ const policyAllowedParts = [...allowedParts];
161
+ const policyArchive = snapshotArchiveOptions(archive);
162
+ const policyLimits = snapshotProposalLimits(limits);
163
+ let proposal;
164
+ try {
165
+ proposal = parseFolioDocxXmlPatchProposal(rawProposal);
166
+ } catch (error) {
167
+ if (!(error instanceof InvalidFolioDocxXmlPatchProposalError)) throw error;
168
+ const evaluation = await evaluateProposal({
169
+ bytes: input,
170
+ proposal: rawProposal,
171
+ allowedParts: policyAllowedParts,
172
+ archive: policyArchive,
173
+ limits: policyLimits
174
+ });
175
+ if (evaluation.status !== "rejected") return panic("An invalid XML proposal produced an accepted evaluation");
176
+ return proposalRejected({ evaluation });
177
+ }
178
+ const evaluation = await evaluateProposal({
179
+ bytes: input,
180
+ proposal,
181
+ allowedParts: policyAllowedParts,
182
+ archive: policyArchive,
183
+ limits: policyLimits
184
+ });
185
+ if (evaluation.status === "rejected") return proposalRejected({ evaluation });
186
+ await verifyArchiveContents(input, policyArchive);
187
+ const output = await applyReplacements({
188
+ bytes: input,
189
+ proposal
190
+ });
191
+ const conformance = await validateDocxConformance(output, { ...policyArchive === void 0 ? {} : { archive: policyArchive } });
192
+ if (!isConformantReport(conformance)) return outputRejected({
193
+ evaluation,
194
+ conformance
195
+ });
196
+ return applied({
197
+ input,
198
+ output,
199
+ evaluation,
200
+ conformance
201
+ });
202
+ };
203
+ //#endregion
204
+ export { FOLIO_DOCX_XML_PATCH_APPLICATION_PROFILE, FOLIO_DOCX_XML_PATCH_APPLICATION_VERSION, FolioDocxXmlPatchApplicationError, UnsupportedFolioDocxXmlPatchApplicationProfileError, applyDocxXmlPatchProposal };
@@ -2,23 +2,34 @@
2
2
  declare const DOCX_MAX_ENTRY_BYTES: number;
3
3
  declare const DOCX_MAX_TOTAL_BYTES: number;
4
4
  declare const DOCX_MAX_ENTRIES = 4096;
5
+ declare const DOCX_MAX_INPUT_BYTES: number;
5
6
  declare const DocxArchiveError_base: import("better-result").TaggedErrorClass<"DocxArchiveError", {
6
7
  message: string;
7
- reason: "load-failed" | "too-many-entries" | "entry-too-large" | "total-too-large";
8
+ reason: "load-failed" | "input-too-large" | "too-many-entries" | "entry-too-large" | "total-too-large" | "invalid-options";
8
9
  cause?: unknown;
9
10
  }>;
10
11
  /** Error raised when a DOCX archive cannot be loaded within configured limits. */
11
12
  declare class DocxArchiveError extends DocxArchiveError_base {}
12
13
  type DocxArchiveOptions = {
14
+ maxInputBytes?: number;
13
15
  maxEntryBytes?: number;
14
16
  maxTotalBytes?: number;
15
17
  maxEntries?: number;
16
18
  };
19
+ type DocxArchiveEntry = {
20
+ readonly path: string;
21
+ readonly directory: boolean;
22
+ readonly declaredUncompressedBytes: number | null;
23
+ };
24
+ type DocxArchiveReadOptions = {
25
+ maxBytes?: number;
26
+ };
17
27
  type DocxArchive = {
18
28
  entries: readonly string[];
29
+ entryMetadata: readonly DocxArchiveEntry[];
19
30
  readEntryString: (path: string) => Promise<string | null>;
20
- readEntryUint8: (path: string) => Promise<Uint8Array | null>;
31
+ readEntryUint8: (path: string, options?: DocxArchiveReadOptions) => Promise<Uint8Array | null>;
21
32
  };
22
33
  declare const loadDocxArchive: (bytes: ArrayBuffer | Uint8Array, options?: DocxArchiveOptions) => Promise<DocxArchive>;
23
34
  //#endregion
24
- export { DOCX_MAX_ENTRIES, DOCX_MAX_ENTRY_BYTES, DOCX_MAX_TOTAL_BYTES, DocxArchive, DocxArchiveError, DocxArchiveOptions, loadDocxArchive };
35
+ export { DOCX_MAX_ENTRIES, DOCX_MAX_ENTRY_BYTES, DOCX_MAX_INPUT_BYTES, DOCX_MAX_TOTAL_BYTES, DocxArchive, DocxArchiveEntry, DocxArchiveError, DocxArchiveOptions, DocxArchiveReadOptions, loadDocxArchive };
@@ -4,6 +4,7 @@ import JSZip from "jszip";
4
4
  const DOCX_MAX_ENTRY_BYTES = 128 * 1024 * 1024;
5
5
  const DOCX_MAX_TOTAL_BYTES = 256 * 1024 * 1024;
6
6
  const DOCX_MAX_ENTRIES = 4096;
7
+ const DOCX_MAX_INPUT_BYTES = 50 * 1024 * 1024;
7
8
  /** Error raised when a DOCX archive cannot be loaded within configured limits. */
8
9
  var DocxArchiveError = class extends TaggedError("DocxArchiveError")() {};
9
10
  const collectStream = async ({ stream, maxEntryBytes, remainingBytes, maxTotalBytes, path }) => await new Promise((resolve, reject) => {
@@ -33,10 +34,44 @@ const collectStream = async ({ stream, maxEntryBytes, remainingBytes, maxTotalBy
33
34
  stream.on("end", () => resolve(Buffer.concat(chunks)));
34
35
  stream.on("error", reject);
35
36
  });
37
+ const getDeclaredUncompressedBytes = (entry) => {
38
+ const data = "_data" in entry ? entry._data : void 0;
39
+ const declaredBytes = typeof data === "object" && data !== null && "uncompressedSize" in data ? data.uncompressedSize : void 0;
40
+ return typeof declaredBytes === "number" && Number.isFinite(declaredBytes) ? declaredBytes : null;
41
+ };
42
+ const resolveByteLimit = ({ value, fallback, name }) => {
43
+ const limit = value ?? fallback;
44
+ if (!Number.isSafeInteger(limit) || limit < 0) throw new DocxArchiveError({
45
+ message: `${name} must be a non-negative safe integer`,
46
+ reason: "invalid-options"
47
+ });
48
+ return limit;
49
+ };
36
50
  const loadDocxArchive = async (bytes, options = {}) => {
37
- const maxEntryBytes = options.maxEntryBytes ?? 134217728;
38
- const maxTotalBytes = options.maxTotalBytes ?? 268435456;
39
- const maxEntries = options.maxEntries ?? 4096;
51
+ const maxInputBytes = resolveByteLimit({
52
+ value: options.maxInputBytes,
53
+ fallback: DOCX_MAX_INPUT_BYTES,
54
+ name: "DOCX input byte limit"
55
+ });
56
+ const maxEntryBytes = resolveByteLimit({
57
+ value: options.maxEntryBytes,
58
+ fallback: DOCX_MAX_ENTRY_BYTES,
59
+ name: "DOCX entry byte limit"
60
+ });
61
+ const maxTotalBytes = resolveByteLimit({
62
+ value: options.maxTotalBytes,
63
+ fallback: DOCX_MAX_TOTAL_BYTES,
64
+ name: "DOCX cumulative byte limit"
65
+ });
66
+ const maxEntries = resolveByteLimit({
67
+ value: options.maxEntries,
68
+ fallback: DOCX_MAX_ENTRIES,
69
+ name: "DOCX entry limit"
70
+ });
71
+ if (bytes.byteLength > maxInputBytes) throw new DocxArchiveError({
72
+ message: `DOCX input contains ${bytes.byteLength} bytes (max ${maxInputBytes})`,
73
+ reason: "input-too-large"
74
+ });
40
75
  let zip;
41
76
  try {
42
77
  zip = await JSZip.loadAsync(bytes);
@@ -54,9 +89,9 @@ const loadDocxArchive = async (bytes, options = {}) => {
54
89
  });
55
90
  let declaredTotalBytes = 0;
56
91
  for (const entry of archiveEntries) {
57
- const data = "_data" in entry ? entry._data : void 0;
58
- const declaredBytes = typeof data === "object" && data !== null && "uncompressedSize" in data ? data.uncompressedSize : void 0;
59
- if (typeof declaredBytes !== "number" || !Number.isFinite(declaredBytes)) {
92
+ if (entry.dir) continue;
93
+ const declaredBytes = getDeclaredUncompressedBytes(entry);
94
+ if (declaredBytes === null) {
60
95
  declaredTotalBytes = NaN;
61
96
  break;
62
97
  }
@@ -72,13 +107,18 @@ const loadDocxArchive = async (bytes, options = {}) => {
72
107
  });
73
108
  let totalBytesRead = 0;
74
109
  let readChain = Promise.resolve();
75
- const readEntry = async (path) => {
110
+ const readEntry = async (path, readOptions = {}) => {
111
+ const requestedMaxBytes = resolveByteLimit({
112
+ value: readOptions.maxBytes,
113
+ fallback: maxEntryBytes,
114
+ name: "DOCX entry read byte limit"
115
+ });
76
116
  const work = async () => {
77
117
  const entry = zip.file(path);
78
118
  if (!entry) return null;
79
119
  const buffer = await collectStream({
80
120
  stream: entry.nodeStream("nodebuffer"),
81
- maxEntryBytes,
121
+ maxEntryBytes: Math.min(requestedMaxBytes, maxEntryBytes),
82
122
  remainingBytes: maxTotalBytes - totalBytesRead,
83
123
  maxTotalBytes,
84
124
  path
@@ -92,15 +132,20 @@ const loadDocxArchive = async (bytes, options = {}) => {
92
132
  };
93
133
  return {
94
134
  entries: Object.freeze(archiveEntries.map(({ name }) => name)),
135
+ entryMetadata: Object.freeze(archiveEntries.map((entry) => ({
136
+ path: entry.name,
137
+ directory: entry.dir,
138
+ declaredUncompressedBytes: getDeclaredUncompressedBytes(entry)
139
+ }))),
95
140
  async readEntryString(path) {
96
141
  const buffer = await readEntry(path);
97
142
  return buffer === null ? null : buffer.toString("utf-8");
98
143
  },
99
- async readEntryUint8(path) {
100
- const buffer = await readEntry(path);
144
+ async readEntryUint8(path, readOptions) {
145
+ const buffer = await readEntry(path, readOptions);
101
146
  return buffer === null ? null : new Uint8Array(buffer);
102
147
  }
103
148
  };
104
149
  };
105
150
  //#endregion
106
- export { DOCX_MAX_ENTRIES, DOCX_MAX_ENTRY_BYTES, DOCX_MAX_TOTAL_BYTES, DocxArchiveError, loadDocxArchive };
151
+ export { DOCX_MAX_ENTRIES, DOCX_MAX_ENTRY_BYTES, DOCX_MAX_INPUT_BYTES, DOCX_MAX_TOTAL_BYTES, DocxArchiveError, loadDocxArchive };
@@ -0,0 +1,85 @@
1
+ import { DocxArchiveOptions } from "./boundedArchive.js";
2
+
3
+ //#region src/docx/server/evaluateDocxXmlPatchProposal.d.ts
4
+ declare const FOLIO_DOCX_XML_PATCH_PROPOSAL_VERSION: 1;
5
+ declare const FOLIO_DOCX_XML_PATCH_PROPOSAL_PROFILE: "folio-xml-patch-proposal-v1";
6
+ declare const FOLIO_DOCX_XML_PATCH_PROPOSAL_DEFAULTS: Readonly<{
7
+ readonly maxReplacements: 16;
8
+ readonly maxPartBytes: number;
9
+ readonly maxTotalBytes: number;
10
+ }>;
11
+ declare const FOLIO_DOCX_XML_PATCH_PROPOSAL_ISSUE_CODES: readonly ["invalid-proposal", "unsupported-version", "too-many-replacements", "duplicate-part", "invalid-part-path", "invalid-base-sha256", "part-not-allowed", "part-not-found", "part-not-xml", "replacement-too-large", "replacements-too-large", "xml-doctype-forbidden", "xml-not-well-formed", "xml-encoding-mismatch", "base-hash-mismatch", "package-inspection-failed"];
12
+ type FolioDocxXmlPatchProposalIssueCode = (typeof FOLIO_DOCX_XML_PATCH_PROPOSAL_ISSUE_CODES)[number];
13
+ type FolioDocxXmlReplacement = {
14
+ readonly path: string;
15
+ readonly baseSha256: string;
16
+ readonly replacementXml: string;
17
+ };
18
+ type FolioDocxXmlPatchProposal = {
19
+ readonly version: typeof FOLIO_DOCX_XML_PATCH_PROPOSAL_VERSION;
20
+ readonly replacements: readonly [FolioDocxXmlReplacement, ...FolioDocxXmlReplacement[]];
21
+ };
22
+ type FolioDocxXmlPatchProposalLimits = {
23
+ readonly maxReplacements?: number;
24
+ readonly maxPartBytes?: number;
25
+ readonly maxTotalBytes?: number;
26
+ };
27
+ type EvaluateDocxXmlPatchProposalArgs = {
28
+ readonly bytes: ArrayBuffer | Uint8Array;
29
+ readonly proposal: unknown; /** Exact package paths authorized by the server-side caller. */
30
+ readonly allowedParts: readonly string[];
31
+ readonly archive?: DocxArchiveOptions;
32
+ readonly limits?: FolioDocxXmlPatchProposalLimits;
33
+ };
34
+ type FolioDocxXmlPatchProposalIssue = {
35
+ readonly code: FolioDocxXmlPatchProposalIssueCode;
36
+ readonly message: string;
37
+ readonly proposalPath?: string;
38
+ readonly part?: string;
39
+ };
40
+ type FolioDocxPreparedXmlReplacement = {
41
+ readonly path: string;
42
+ readonly baseSha256: string;
43
+ readonly currentSha256: string;
44
+ readonly replacementSha256: string;
45
+ readonly replacementByteLength: number;
46
+ readonly encoding: "utf-8";
47
+ };
48
+ type FolioDocxXmlPatchProposalEvaluation = {
49
+ readonly version: typeof FOLIO_DOCX_XML_PATCH_PROPOSAL_VERSION;
50
+ readonly profile: typeof FOLIO_DOCX_XML_PATCH_PROPOSAL_PROFILE;
51
+ readonly status: "accepted";
52
+ readonly producesOutput: false;
53
+ readonly issues: readonly [];
54
+ readonly replacements: readonly [FolioDocxPreparedXmlReplacement, ...FolioDocxPreparedXmlReplacement[]];
55
+ readonly limits: Required<FolioDocxXmlPatchProposalLimits>;
56
+ } | {
57
+ readonly version: typeof FOLIO_DOCX_XML_PATCH_PROPOSAL_VERSION;
58
+ readonly profile: typeof FOLIO_DOCX_XML_PATCH_PROPOSAL_PROFILE;
59
+ readonly status: "rejected";
60
+ readonly producesOutput: false;
61
+ readonly issues: readonly [FolioDocxXmlPatchProposalIssue, ...FolioDocxXmlPatchProposalIssue[]];
62
+ readonly replacements: readonly [];
63
+ readonly limits: Required<FolioDocxXmlPatchProposalLimits>;
64
+ };
65
+ declare const InvalidFolioDocxXmlPatchProposalError_base: import("better-result").TaggedErrorClass<"InvalidFolioDocxXmlPatchProposalError", {
66
+ message: string;
67
+ path: string;
68
+ reason: string;
69
+ }>;
70
+ declare class InvalidFolioDocxXmlPatchProposalError extends InvalidFolioDocxXmlPatchProposalError_base {}
71
+ declare const InvalidFolioDocxXmlPatchProposalOptionsError_base: import("better-result").TaggedErrorClass<"InvalidFolioDocxXmlPatchProposalOptionsError", {
72
+ message: string;
73
+ path: string;
74
+ }>;
75
+ declare class InvalidFolioDocxXmlPatchProposalOptionsError extends InvalidFolioDocxXmlPatchProposalOptionsError_base {}
76
+ declare const parseFolioDocxXmlPatchProposal: (value: unknown) => FolioDocxXmlPatchProposal;
77
+ declare const evaluateDocxXmlPatchProposal: ({
78
+ bytes,
79
+ proposal: rawProposal,
80
+ allowedParts,
81
+ archive,
82
+ limits: rawLimits
83
+ }: EvaluateDocxXmlPatchProposalArgs) => Promise<FolioDocxXmlPatchProposalEvaluation>;
84
+ //#endregion
85
+ export { EvaluateDocxXmlPatchProposalArgs, FOLIO_DOCX_XML_PATCH_PROPOSAL_DEFAULTS, FOLIO_DOCX_XML_PATCH_PROPOSAL_ISSUE_CODES, FOLIO_DOCX_XML_PATCH_PROPOSAL_PROFILE, FOLIO_DOCX_XML_PATCH_PROPOSAL_VERSION, FolioDocxPreparedXmlReplacement, FolioDocxXmlPatchProposal, FolioDocxXmlPatchProposalEvaluation, FolioDocxXmlPatchProposalIssue, FolioDocxXmlPatchProposalIssueCode, FolioDocxXmlPatchProposalLimits, FolioDocxXmlReplacement, InvalidFolioDocxXmlPatchProposalError, InvalidFolioDocxXmlPatchProposalOptionsError, evaluateDocxXmlPatchProposal, parseFolioDocxXmlPatchProposal };