@arslankhan/doc-forge 0.0.0-stage → 0.1.1

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 (246) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE +13 -0
  3. package/LICENSING.md +80 -0
  4. package/README.md +547 -2
  5. package/RELEASE.md +50 -0
  6. package/dist/binding-path.d.ts +15 -0
  7. package/dist/binding-path.d.ts.map +1 -0
  8. package/dist/binding-path.js +37 -0
  9. package/dist/binding-path.js.map +1 -0
  10. package/dist/binding.d.ts +91 -0
  11. package/dist/binding.d.ts.map +1 -0
  12. package/dist/binding.js +229 -0
  13. package/dist/binding.js.map +1 -0
  14. package/dist/designer/Canvas.d.ts +51 -0
  15. package/dist/designer/Canvas.d.ts.map +1 -0
  16. package/dist/designer/Canvas.js +188 -0
  17. package/dist/designer/Canvas.js.map +1 -0
  18. package/dist/designer/DocumentDesigner.d.ts +14 -0
  19. package/dist/designer/DocumentDesigner.d.ts.map +1 -0
  20. package/dist/designer/DocumentDesigner.js +351 -0
  21. package/dist/designer/DocumentDesigner.js.map +1 -0
  22. package/dist/designer/HtmlPreview.d.ts +9 -0
  23. package/dist/designer/HtmlPreview.d.ts.map +1 -0
  24. package/dist/designer/HtmlPreview.js +20 -0
  25. package/dist/designer/HtmlPreview.js.map +1 -0
  26. package/dist/designer/ImageBindingFields.d.ts +11 -0
  27. package/dist/designer/ImageBindingFields.d.ts.map +1 -0
  28. package/dist/designer/ImageBindingFields.js +106 -0
  29. package/dist/designer/ImageBindingFields.js.map +1 -0
  30. package/dist/designer/InvoiceFields.d.ts +13 -0
  31. package/dist/designer/InvoiceFields.d.ts.map +1 -0
  32. package/dist/designer/InvoiceFields.js +85 -0
  33. package/dist/designer/InvoiceFields.js.map +1 -0
  34. package/dist/designer/PreviewDataDialog.d.ts +13 -0
  35. package/dist/designer/PreviewDataDialog.d.ts.map +1 -0
  36. package/dist/designer/PreviewDataDialog.js +16 -0
  37. package/dist/designer/PreviewDataDialog.js.map +1 -0
  38. package/dist/designer/PropertiesPanel.d.ts +15 -0
  39. package/dist/designer/PropertiesPanel.d.ts.map +1 -0
  40. package/dist/designer/PropertiesPanel.js +63 -0
  41. package/dist/designer/PropertiesPanel.js.map +1 -0
  42. package/dist/designer/ReportFields.d.ts +14 -0
  43. package/dist/designer/ReportFields.d.ts.map +1 -0
  44. package/dist/designer/ReportFields.js +257 -0
  45. package/dist/designer/ReportFields.js.map +1 -0
  46. package/dist/designer/StyleFields.d.ts +16 -0
  47. package/dist/designer/StyleFields.d.ts.map +1 -0
  48. package/dist/designer/StyleFields.js +138 -0
  49. package/dist/designer/StyleFields.js.map +1 -0
  50. package/dist/designer/TextBindingFields.d.ts +11 -0
  51. package/dist/designer/TextBindingFields.d.ts.map +1 -0
  52. package/dist/designer/TextBindingFields.js +105 -0
  53. package/dist/designer/TextBindingFields.js.map +1 -0
  54. package/dist/designer/Toolbar.d.ts +52 -0
  55. package/dist/designer/Toolbar.d.ts.map +1 -0
  56. package/dist/designer/Toolbar.js +62 -0
  57. package/dist/designer/Toolbar.js.map +1 -0
  58. package/dist/designer/Toolbox.d.ts +8 -0
  59. package/dist/designer/Toolbox.d.ts.map +1 -0
  60. package/dist/designer/Toolbox.js +43 -0
  61. package/dist/designer/Toolbox.js.map +1 -0
  62. package/dist/designer/border-preview.d.ts +15 -0
  63. package/dist/designer/border-preview.d.ts.map +1 -0
  64. package/dist/designer/border-preview.js +34 -0
  65. package/dist/designer/border-preview.js.map +1 -0
  66. package/dist/designer/history.d.ts +13 -0
  67. package/dist/designer/history.d.ts.map +1 -0
  68. package/dist/designer/history.js +51 -0
  69. package/dist/designer/history.js.map +1 -0
  70. package/dist/designer/index.d.ts +5 -0
  71. package/dist/designer/index.d.ts.map +1 -0
  72. package/dist/designer/index.js +3 -0
  73. package/dist/designer/index.js.map +1 -0
  74. package/dist/designer/page-geometry.d.ts +35 -0
  75. package/dist/designer/page-geometry.d.ts.map +1 -0
  76. package/dist/designer/page-geometry.js +123 -0
  77. package/dist/designer/page-geometry.js.map +1 -0
  78. package/dist/designer/styles.d.ts +3 -0
  79. package/dist/designer/styles.d.ts.map +1 -0
  80. package/dist/designer/styles.js +594 -0
  81. package/dist/designer/styles.js.map +1 -0
  82. package/dist/designer/template-edits.d.ts +26 -0
  83. package/dist/designer/template-edits.d.ts.map +1 -0
  84. package/dist/designer/template-edits.js +323 -0
  85. package/dist/designer/template-edits.js.map +1 -0
  86. package/dist/designer/theme.d.ts +31 -0
  87. package/dist/designer/theme.d.ts.map +1 -0
  88. package/dist/designer/theme.js +92 -0
  89. package/dist/designer/theme.js.map +1 -0
  90. package/dist/examples/bound-invoice.d.ts +143 -0
  91. package/dist/examples/bound-invoice.d.ts.map +1 -0
  92. package/dist/examples/bound-invoice.js +99 -0
  93. package/dist/examples/bound-invoice.js.map +1 -0
  94. package/dist/examples/full-invoice-data.d.ts +81 -0
  95. package/dist/examples/full-invoice-data.d.ts.map +1 -0
  96. package/dist/examples/full-invoice-data.js +86 -0
  97. package/dist/examples/full-invoice-data.js.map +1 -0
  98. package/dist/examples/full-invoice.d.ts +402 -0
  99. package/dist/examples/full-invoice.d.ts.map +1 -0
  100. package/dist/examples/full-invoice.js +199 -0
  101. package/dist/examples/full-invoice.js.map +1 -0
  102. package/dist/examples/invoice-data.d.ts +51 -0
  103. package/dist/examples/invoice-data.d.ts.map +1 -0
  104. package/dist/examples/invoice-data.js +51 -0
  105. package/dist/examples/invoice-data.js.map +1 -0
  106. package/dist/examples/invoice.d.ts +90 -0
  107. package/dist/examples/invoice.d.ts.map +1 -0
  108. package/dist/examples/invoice.js +62 -0
  109. package/dist/examples/invoice.js.map +1 -0
  110. package/dist/examples/jobs-report-data.d.ts +84 -0
  111. package/dist/examples/jobs-report-data.d.ts.map +1 -0
  112. package/dist/examples/jobs-report-data.js +79 -0
  113. package/dist/examples/jobs-report-data.js.map +1 -0
  114. package/dist/examples/jobs-report.d.ts +182 -0
  115. package/dist/examples/jobs-report.d.ts.map +1 -0
  116. package/dist/examples/jobs-report.js +96 -0
  117. package/dist/examples/jobs-report.js.map +1 -0
  118. package/dist/index.d.ts +31 -0
  119. package/dist/index.d.ts.map +1 -0
  120. package/dist/index.js +21 -0
  121. package/dist/index.js.map +1 -0
  122. package/dist/invoice/calculate.d.ts +59 -0
  123. package/dist/invoice/calculate.d.ts.map +1 -0
  124. package/dist/invoice/calculate.js +270 -0
  125. package/dist/invoice/calculate.js.map +1 -0
  126. package/dist/invoice/context.d.ts +15 -0
  127. package/dist/invoice/context.d.ts.map +1 -0
  128. package/dist/invoice/context.js +79 -0
  129. package/dist/invoice/context.js.map +1 -0
  130. package/dist/invoice/format.d.ts +10 -0
  131. package/dist/invoice/format.d.ts.map +1 -0
  132. package/dist/invoice/format.js +95 -0
  133. package/dist/invoice/format.js.map +1 -0
  134. package/dist/license/capabilities.d.ts +17 -0
  135. package/dist/license/capabilities.d.ts.map +1 -0
  136. package/dist/license/capabilities.js +46 -0
  137. package/dist/license/capabilities.js.map +1 -0
  138. package/dist/license/client.d.ts +109 -0
  139. package/dist/license/client.d.ts.map +1 -0
  140. package/dist/license/client.js +346 -0
  141. package/dist/license/client.js.map +1 -0
  142. package/dist/license/codec.d.ts +5 -0
  143. package/dist/license/codec.d.ts.map +1 -0
  144. package/dist/license/codec.js +26 -0
  145. package/dist/license/codec.js.map +1 -0
  146. package/dist/license/errors.d.ts +11 -0
  147. package/dist/license/errors.d.ts.map +1 -0
  148. package/dist/license/errors.js +19 -0
  149. package/dist/license/errors.js.map +1 -0
  150. package/dist/license/features.d.ts +20 -0
  151. package/dist/license/features.d.ts.map +1 -0
  152. package/dist/license/features.js +28 -0
  153. package/dist/license/features.js.map +1 -0
  154. package/dist/license/index.d.ts +16 -0
  155. package/dist/license/index.d.ts.map +1 -0
  156. package/dist/license/index.js +9 -0
  157. package/dist/license/index.js.map +1 -0
  158. package/dist/license/provider.d.ts +59 -0
  159. package/dist/license/provider.d.ts.map +1 -0
  160. package/dist/license/provider.js +150 -0
  161. package/dist/license/provider.js.map +1 -0
  162. package/dist/license/redact.d.ts +4 -0
  163. package/dist/license/redact.d.ts.map +1 -0
  164. package/dist/license/redact.js +15 -0
  165. package/dist/license/redact.js.map +1 -0
  166. package/dist/license/store.d.ts +26 -0
  167. package/dist/license/store.d.ts.map +1 -0
  168. package/dist/license/store.js +90 -0
  169. package/dist/license/store.js.map +1 -0
  170. package/dist/license/token.d.ts +45 -0
  171. package/dist/license/token.d.ts.map +1 -0
  172. package/dist/license/token.js +195 -0
  173. package/dist/license/token.js.map +1 -0
  174. package/dist/pdf/browser-blocked.d.ts +12 -0
  175. package/dist/pdf/browser-blocked.d.ts.map +1 -0
  176. package/dist/pdf/browser-blocked.js +25 -0
  177. package/dist/pdf/browser-blocked.js.map +1 -0
  178. package/dist/pdf/browser.d.ts +12 -0
  179. package/dist/pdf/browser.d.ts.map +1 -0
  180. package/dist/pdf/browser.js +69 -0
  181. package/dist/pdf/browser.js.map +1 -0
  182. package/dist/pdf/errors.d.ts +6 -0
  183. package/dist/pdf/errors.d.ts.map +1 -0
  184. package/dist/pdf/errors.js +11 -0
  185. package/dist/pdf/errors.js.map +1 -0
  186. package/dist/pdf/index.d.ts +10 -0
  187. package/dist/pdf/index.d.ts.map +1 -0
  188. package/dist/pdf/index.js +9 -0
  189. package/dist/pdf/index.js.map +1 -0
  190. package/dist/pdf/options.d.ts +143 -0
  191. package/dist/pdf/options.d.ts.map +1 -0
  192. package/dist/pdf/options.js +322 -0
  193. package/dist/pdf/options.js.map +1 -0
  194. package/dist/pdf/render-pdf.d.ts +13 -0
  195. package/dist/pdf/render-pdf.d.ts.map +1 -0
  196. package/dist/pdf/render-pdf.js +195 -0
  197. package/dist/pdf/render-pdf.js.map +1 -0
  198. package/dist/render/appearance.d.ts +25 -0
  199. package/dist/render/appearance.d.ts.map +1 -0
  200. package/dist/render/appearance.js +149 -0
  201. package/dist/render/appearance.js.map +1 -0
  202. package/dist/render/css.d.ts +17 -0
  203. package/dist/render/css.d.ts.map +1 -0
  204. package/dist/render/css.js +170 -0
  205. package/dist/render/css.js.map +1 -0
  206. package/dist/render/errors.d.ts +6 -0
  207. package/dist/render/errors.d.ts.map +1 -0
  208. package/dist/render/errors.js +11 -0
  209. package/dist/render/errors.js.map +1 -0
  210. package/dist/render/escape.d.ts +7 -0
  211. package/dist/render/escape.d.ts.map +1 -0
  212. package/dist/render/escape.js +27 -0
  213. package/dist/render/escape.js.map +1 -0
  214. package/dist/render/image-policy.d.ts +21 -0
  215. package/dist/render/image-policy.d.ts.map +1 -0
  216. package/dist/render/image-policy.js +62 -0
  217. package/dist/render/image-policy.js.map +1 -0
  218. package/dist/render/render-html.d.ts +28 -0
  219. package/dist/render/render-html.d.ts.map +1 -0
  220. package/dist/render/render-html.js +364 -0
  221. package/dist/render/render-html.js.map +1 -0
  222. package/dist/render/render-report.d.ts +23 -0
  223. package/dist/render/render-report.d.ts.map +1 -0
  224. package/dist/render/render-report.js +313 -0
  225. package/dist/render/render-report.js.map +1 -0
  226. package/dist/report/process.d.ts +48 -0
  227. package/dist/report/process.d.ts.map +1 -0
  228. package/dist/report/process.js +347 -0
  229. package/dist/report/process.js.map +1 -0
  230. package/dist/schema.d.ts +30 -0
  231. package/dist/schema.d.ts.map +1 -0
  232. package/dist/schema.js +8 -0
  233. package/dist/schema.js.map +1 -0
  234. package/dist/serialize.d.ts +13 -0
  235. package/dist/serialize.d.ts.map +1 -0
  236. package/dist/serialize.js +42 -0
  237. package/dist/serialize.js.map +1 -0
  238. package/dist/types.d.ts +335 -0
  239. package/dist/types.d.ts.map +1 -0
  240. package/dist/types.js +40 -0
  241. package/dist/types.js.map +1 -0
  242. package/dist/validate.d.ts +15 -0
  243. package/dist/validate.d.ts.map +1 -0
  244. package/dist/validate.js +977 -0
  245. package/dist/validate.js.map +1 -0
  246. package/package.json +105 -4
@@ -0,0 +1,977 @@
1
+ import { parseBindingPath } from "./binding-path.js";
2
+ import { DOCUMENT_TYPES, isSupportedSchemaVersion, SUPPORTED_SCHEMA_VERSIONS } from "./schema.js";
3
+ import { cssColor, cssFontFamily } from "./render/css.js";
4
+ import { AGGREGATES, ELEMENT_TYPES, FONT_STYLES, FONT_WEIGHT_NAMES, LAYOUT_MODES, LENGTH_UNITS, PAGE_ORIENTATIONS, PAPER_SIZES, REPORT_BAND_TYPES, SORT_DIRECTIONS, TEXT_ALIGNS, TEXT_DECORATIONS, VERTICAL_ALIGNS, } from "./types.js";
5
+ export class TemplateValidationError extends Error {
6
+ errors;
7
+ constructor(errors) {
8
+ super(formatIssues(errors));
9
+ this.name = "TemplateValidationError";
10
+ this.errors = errors;
11
+ Object.setPrototypeOf(this, new.target.prototype);
12
+ }
13
+ }
14
+ const TEMPLATE_KEYS = ["schemaVersion", "id", "name", "documentType", "page", "elements", "invoice", "report"];
15
+ const INVOICE_OPTION_KEYS = ["fractionDigits"];
16
+ const REPORT_OPTION_KEYS = [
17
+ "title",
18
+ "description",
19
+ "emptyText",
20
+ "showPageNumber",
21
+ "pageHeader",
22
+ "pageFooter",
23
+ "reportHeader",
24
+ "reportFooter",
25
+ ];
26
+ const REPORT_TEXT_BAND_KEYS = ["id", "type", "text", "binding"];
27
+ const REPORT_IMAGE_BAND_KEYS = ["id", "type", "src", "alt", "binding"];
28
+ const PAGE_KEYS = ["paperSize", "orientation", "dimensions", "margins", "layoutMode"];
29
+ const PAGE_DIMENSION_KEYS = ["width", "height", "unit"];
30
+ const MARGIN_KEYS = ["top", "right", "bottom", "left"];
31
+ const POSITION_KEYS = ["x", "y"];
32
+ const ELEMENT_DIMENSION_KEYS = ["width", "height"];
33
+ const BINDING_KEYS = ["path", "fallback", "prefix", "suffix", "format"];
34
+ const FORMAT_KEYS = ["style", "locale", "currency", "minimumFractionDigits", "maximumFractionDigits", "dateStyle"];
35
+ const FORMAT_STYLES = ["decimal", "currency", "percent", "date"];
36
+ const DATE_STYLES = ["iso", "short", "medium", "long"];
37
+ const ITEMS_KEYS = [
38
+ "id",
39
+ "type",
40
+ "position",
41
+ "dimensions",
42
+ "style",
43
+ "itemsPath",
44
+ "columns",
45
+ "showHeader",
46
+ "emptyText",
47
+ "headerStyle",
48
+ "padding",
49
+ ];
50
+ const COLUMN_KEYS = ["id", "header", "binding", "width", "align", "visible"];
51
+ const REPORT_TABLE_KEYS = [
52
+ "id",
53
+ "type",
54
+ "position",
55
+ "dimensions",
56
+ "style",
57
+ "collectionPath",
58
+ "columns",
59
+ "showHeader",
60
+ "emptyText",
61
+ "headerStyle",
62
+ "padding",
63
+ "alternateRowColor",
64
+ "oddRowColor",
65
+ "evenRowColor",
66
+ "stripeRows",
67
+ "bodyStyle",
68
+ "groupStyle",
69
+ "subtotalStyle",
70
+ "grandTotalStyle",
71
+ "tableAlign",
72
+ "headerHeight",
73
+ "rowRules",
74
+ "groupBy",
75
+ "sort",
76
+ "showGrandTotal",
77
+ "grandTotalLabel",
78
+ "pageBreakBefore",
79
+ ];
80
+ const REPORT_COLUMN_KEYS = ["id", "header", "binding", "width", "align", "visible", "aggregate", "headerStyle", "style"];
81
+ const ROW_RULE_KEYS = ["path", "operator", "value", "style"];
82
+ const ROW_RULE_OPERATORS = ["eq", "neq", "gt", "gte", "lt", "lte", "blank"];
83
+ const BOX_EDGE_KEYS = ["top", "right", "bottom", "left"];
84
+ const BORDER_EDGE_KEYS = ["width", "color", "style"];
85
+ const BORDER_LINE_STYLES = ["solid", "dashed", "dotted", "double", "none"];
86
+ const REPORT_GROUP_KEYS = ["path", "label"];
87
+ const REPORT_SORT_KEYS = ["path", "direction"];
88
+ const TOTALS_KEYS = ["id", "type", "position", "dimensions", "style", "rows"];
89
+ const TOTAL_ROW_KEYS = ["id", "label", "binding", "visible", "hideWhenZero"];
90
+ const TEXT_KEYS = ["id", "type", "position", "dimensions", "style", "text", "binding"];
91
+ const IMAGE_KEYS = ["id", "type", "position", "dimensions", "style", "src", "alt", "binding"];
92
+ const SHAPE_KEYS = ["id", "type", "position", "dimensions", "style"];
93
+ const STYLE_STRING_KEYS = ["color", "backgroundColor", "fontFamily", "borderColor", "strokeColor"];
94
+ const STYLE_COLOR_KEYS = ["color", "backgroundColor", "borderColor", "strokeColor"];
95
+ const STYLE_KEYS = [
96
+ ...STYLE_STRING_KEYS,
97
+ "fontSize",
98
+ "fontWeight",
99
+ "fontStyle",
100
+ "textAlign",
101
+ "verticalAlign",
102
+ "textDecoration",
103
+ "lineHeight",
104
+ "borderWidth",
105
+ "borderStyle",
106
+ "borderRadius",
107
+ "borderTop",
108
+ "borderRight",
109
+ "borderBottom",
110
+ "borderLeft",
111
+ "padding",
112
+ "opacity",
113
+ "strokeWidth",
114
+ ];
115
+ const IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_-]*$/;
116
+ function formatIssues(errors) {
117
+ if (errors.length === 0) {
118
+ return "Template is invalid.";
119
+ }
120
+ const lines = errors.map((error) => {
121
+ const location = error.path.length > 0 ? error.path : "(template)";
122
+ return `${location}: ${error.message}`;
123
+ });
124
+ return `Template is invalid.\n${lines.join("\n")}`;
125
+ }
126
+ function issue(path, code, message) {
127
+ return { path, code, message };
128
+ }
129
+ function isRecord(value) {
130
+ return typeof value === "object" && value !== null && !Array.isArray(value);
131
+ }
132
+ function isFiniteNumber(value) {
133
+ return typeof value === "number" && Number.isFinite(value);
134
+ }
135
+ function isOneOf(values, value) {
136
+ return typeof value === "string" && values.includes(value);
137
+ }
138
+ function rejectUnknown(value, allowed, path, errors) {
139
+ for (const key of Object.keys(value)) {
140
+ if (!allowed.includes(key)) {
141
+ const child = path.length > 0 ? `${path}.${key}` : key;
142
+ errors.push(issue(child, "unknown_property", `Unknown property "${key}".`));
143
+ }
144
+ }
145
+ }
146
+ export function validateTemplate(input) {
147
+ const errors = [];
148
+ if (!isRecord(input)) {
149
+ return {
150
+ valid: false,
151
+ errors: [issue("", "invalid_type", "Template must be an object.")],
152
+ };
153
+ }
154
+ rejectUnknown(input, TEMPLATE_KEYS, "", errors);
155
+ validateSchemaVersion(input.schemaVersion, errors);
156
+ validateIdentifier(input.id, "id", errors, "Template id");
157
+ validateName(input.name, errors);
158
+ validateDocumentType(input.documentType, errors);
159
+ validatePage(input.page, errors);
160
+ validateInvoiceOptions(input.invoice, errors);
161
+ validateReportOptions(input.report, errors);
162
+ validateElements(input.elements, errors);
163
+ return { valid: errors.length === 0, errors };
164
+ }
165
+ function validateSchemaVersion(value, errors) {
166
+ if (value === undefined) {
167
+ errors.push(issue("schemaVersion", "required", "Schema version is required."));
168
+ return;
169
+ }
170
+ if (typeof value !== "number" || !Number.isInteger(value) || !isSupportedSchemaVersion(value)) {
171
+ const supported = SUPPORTED_SCHEMA_VERSIONS.join(", ");
172
+ errors.push(issue("schemaVersion", "unsupported_schema_version", `Schema version ${String(value)} is not supported. Supported version: ${supported}.`));
173
+ }
174
+ }
175
+ function validateIdentifier(value, path, errors, label) {
176
+ if (value === undefined) {
177
+ errors.push(issue(path, "required", `${label} is required.`));
178
+ return;
179
+ }
180
+ if (typeof value !== "string" || !IDENTIFIER.test(value)) {
181
+ errors.push(issue(path, "invalid_id", `${label} must be a non-empty identifier without spaces.`));
182
+ }
183
+ }
184
+ function validateName(value, errors) {
185
+ if (value === undefined) {
186
+ errors.push(issue("name", "required", "Template name is required."));
187
+ return;
188
+ }
189
+ if (typeof value !== "string" || value.trim().length === 0) {
190
+ errors.push(issue("name", "invalid_name", "Template name must be a non-empty string."));
191
+ }
192
+ }
193
+ function validateDocumentType(value, errors) {
194
+ if (value === undefined) {
195
+ errors.push(issue("documentType", "required", "Document type is required."));
196
+ return;
197
+ }
198
+ if (!isOneOf(DOCUMENT_TYPES, value)) {
199
+ errors.push(issue("documentType", "invalid_document_type", 'Document type must be "generic", "invoice", or "report".'));
200
+ }
201
+ }
202
+ function validatePage(value, errors) {
203
+ if (value === undefined) {
204
+ errors.push(issue("page", "required", "Page configuration is required."));
205
+ return;
206
+ }
207
+ if (!isRecord(value)) {
208
+ errors.push(issue("page", "invalid_type", "Page configuration must be an object."));
209
+ return;
210
+ }
211
+ rejectUnknown(value, PAGE_KEYS, "page", errors);
212
+ if (value.paperSize === undefined) {
213
+ errors.push(issue("page.paperSize", "required", "Paper size is required."));
214
+ }
215
+ else if (!isOneOf(PAPER_SIZES, value.paperSize)) {
216
+ errors.push(issue("page.paperSize", "invalid_paper_size", 'Paper size must be "A4", "Letter", "Legal", or "custom".'));
217
+ }
218
+ if (value.orientation === undefined) {
219
+ errors.push(issue("page.orientation", "required", "Page orientation is required."));
220
+ }
221
+ else if (!isOneOf(PAGE_ORIENTATIONS, value.orientation)) {
222
+ errors.push(issue("page.orientation", "invalid_orientation", 'Orientation must be "portrait" or "landscape".'));
223
+ }
224
+ if (value.layoutMode === undefined) {
225
+ errors.push(issue("page.layoutMode", "required", "Layout mode is required."));
226
+ }
227
+ else if (!isOneOf(LAYOUT_MODES, value.layoutMode)) {
228
+ errors.push(issue("page.layoutMode", "invalid_layout_mode", 'Layout mode must be "fixed" or "flow".'));
229
+ }
230
+ if (value.dimensions === undefined) {
231
+ if (value.paperSize === "custom") {
232
+ errors.push(issue("page.dimensions", "required", "Custom paper sizes require dimensions."));
233
+ }
234
+ }
235
+ else {
236
+ validatePageDimensions(value.dimensions, errors);
237
+ }
238
+ if (value.margins === undefined) {
239
+ errors.push(issue("page.margins", "required", "Page margins are required."));
240
+ }
241
+ else {
242
+ validateMargins(value.margins, errors);
243
+ }
244
+ }
245
+ function validatePageDimensions(value, errors) {
246
+ const path = "page.dimensions";
247
+ if (!isRecord(value)) {
248
+ errors.push(issue(path, "invalid_type", "Page dimensions must be an object."));
249
+ return;
250
+ }
251
+ rejectUnknown(value, PAGE_DIMENSION_KEYS, path, errors);
252
+ validatePositiveNumber(value.width, `${path}.width`, errors, "Width");
253
+ validatePositiveNumber(value.height, `${path}.height`, errors, "Height");
254
+ if (value.unit === undefined) {
255
+ errors.push(issue(`${path}.unit`, "required", "Dimension unit is required."));
256
+ }
257
+ else if (!isOneOf(LENGTH_UNITS, value.unit)) {
258
+ errors.push(issue(`${path}.unit`, "invalid_unit", 'Unit must be "mm", "pt", "in", or "px".'));
259
+ }
260
+ }
261
+ function validateMargins(value, errors) {
262
+ const path = "page.margins";
263
+ if (!isRecord(value)) {
264
+ errors.push(issue(path, "invalid_type", "Page margins must be an object."));
265
+ return;
266
+ }
267
+ rejectUnknown(value, MARGIN_KEYS, path, errors);
268
+ for (const edge of MARGIN_KEYS) {
269
+ validateNonNegativeNumber(value[edge], `${path}.${edge}`, errors, edge);
270
+ }
271
+ }
272
+ function validatePositiveNumber(value, path, errors, label) {
273
+ if (value === undefined) {
274
+ errors.push(issue(path, "required", `${label} is required.`));
275
+ return;
276
+ }
277
+ if (!isFiniteNumber(value) || value <= 0) {
278
+ errors.push(issue(path, "invalid_number", `${label} must be a finite number greater than 0.`));
279
+ }
280
+ }
281
+ function validateNonNegativeNumber(value, path, errors, label) {
282
+ if (value === undefined) {
283
+ errors.push(issue(path, "required", `${label} is required.`));
284
+ return;
285
+ }
286
+ if (!isFiniteNumber(value) || value < 0) {
287
+ errors.push(issue(path, "invalid_number", `${label} must be a finite number greater than or equal to 0.`));
288
+ }
289
+ }
290
+ function validateElements(value, errors) {
291
+ if (value === undefined) {
292
+ errors.push(issue("elements", "required", "Elements are required."));
293
+ return;
294
+ }
295
+ if (!Array.isArray(value)) {
296
+ errors.push(issue("elements", "invalid_type", "Elements must be an array."));
297
+ return;
298
+ }
299
+ const seen = new Map();
300
+ value.forEach((element, index) => {
301
+ validateElement(element, index, seen, errors);
302
+ });
303
+ }
304
+ function validateElement(value, index, seen, errors) {
305
+ const path = `elements[${index}]`;
306
+ if (!isRecord(value)) {
307
+ errors.push(issue(path, "invalid_type", "Element must be an object."));
308
+ return;
309
+ }
310
+ const type = value.type;
311
+ const knownType = isElementType(type);
312
+ if (!knownType) {
313
+ if (type === undefined) {
314
+ errors.push(issue(`${path}.type`, "required", "Element type is required."));
315
+ }
316
+ else {
317
+ errors.push(issue(`${path}.type`, "invalid_element_type", 'Element type must be "text", "image", "line", or "rectangle".'));
318
+ }
319
+ }
320
+ else {
321
+ rejectUnknown(value, keysForElement(type), path, errors);
322
+ }
323
+ if (typeof value.id === "string" && IDENTIFIER.test(value.id)) {
324
+ const previous = seen.get(value.id);
325
+ if (previous === undefined) {
326
+ seen.set(value.id, index);
327
+ }
328
+ else {
329
+ errors.push(issue(`${path}.id`, "duplicate_element_id", `Element id "${value.id}" is duplicated (also used at elements[${previous}]).`));
330
+ }
331
+ }
332
+ else {
333
+ validateIdentifier(value.id, `${path}.id`, errors, "Element id");
334
+ }
335
+ validatePosition(value.position, `${path}.position`, errors);
336
+ if (!knownType) {
337
+ return;
338
+ }
339
+ validateElementDimensions(value.dimensions, `${path}.dimensions`, errors, type === "line");
340
+ validateStyle(value.style, `${path}.style`, errors);
341
+ if (type === "text") {
342
+ validateTextFields(value, path, errors);
343
+ }
344
+ else if (type === "image") {
345
+ validateImageFields(value, path, errors);
346
+ }
347
+ else if (type === "items") {
348
+ validateItemsFields(value, path, errors);
349
+ }
350
+ else if (type === "totals") {
351
+ validateTotalsFields(value, path, errors);
352
+ }
353
+ else if (type === "reportTable") {
354
+ validateReportTableFields(value, path, errors);
355
+ }
356
+ }
357
+ function isElementType(value) {
358
+ return isOneOf(ELEMENT_TYPES, value);
359
+ }
360
+ function keysForElement(type) {
361
+ if (type === "text") {
362
+ return TEXT_KEYS;
363
+ }
364
+ if (type === "image") {
365
+ return IMAGE_KEYS;
366
+ }
367
+ if (type === "items") {
368
+ return ITEMS_KEYS;
369
+ }
370
+ if (type === "totals") {
371
+ return TOTALS_KEYS;
372
+ }
373
+ if (type === "reportTable") {
374
+ return REPORT_TABLE_KEYS;
375
+ }
376
+ return SHAPE_KEYS;
377
+ }
378
+ function validatePosition(value, path, errors) {
379
+ if (value === undefined) {
380
+ errors.push(issue(path, "required", "Position is required."));
381
+ return;
382
+ }
383
+ if (!isRecord(value)) {
384
+ errors.push(issue(path, "invalid_type", "Position must be an object."));
385
+ return;
386
+ }
387
+ rejectUnknown(value, POSITION_KEYS, path, errors);
388
+ validateCoordinate(value.x, `${path}.x`, errors, "x");
389
+ validateCoordinate(value.y, `${path}.y`, errors, "y");
390
+ }
391
+ function validateCoordinate(value, path, errors, axis) {
392
+ if (value === undefined) {
393
+ errors.push(issue(path, "required", `Position ${axis} is required.`));
394
+ return;
395
+ }
396
+ if (!isFiniteNumber(value) || value < 0) {
397
+ errors.push(issue(path, "invalid_position", `Position ${axis} must be a finite number greater than or equal to 0.`));
398
+ }
399
+ }
400
+ function validateElementDimensions(value, path, errors, allowZeroSpan) {
401
+ if (value === undefined) {
402
+ errors.push(issue(path, "required", "Dimensions are required."));
403
+ return;
404
+ }
405
+ if (!isRecord(value)) {
406
+ errors.push(issue(path, "invalid_type", "Dimensions must be an object."));
407
+ return;
408
+ }
409
+ rejectUnknown(value, ELEMENT_DIMENSION_KEYS, path, errors);
410
+ const widthOk = validateSpan(value.width, `${path}.width`, errors, "Width", allowZeroSpan);
411
+ const heightOk = validateSpan(value.height, `${path}.height`, errors, "Height", allowZeroSpan);
412
+ if (allowZeroSpan && widthOk && heightOk && value.width === 0 && value.height === 0) {
413
+ errors.push(issue(path, "invalid_dimensions", "A line must have a non-zero width or height."));
414
+ }
415
+ }
416
+ function validateSpan(value, path, errors, label, allowZero) {
417
+ if (value === undefined) {
418
+ errors.push(issue(path, "required", `${label} is required.`));
419
+ return false;
420
+ }
421
+ const valid = allowZero ? isFiniteNumber(value) && value >= 0 : isFiniteNumber(value) && value > 0;
422
+ if (!valid) {
423
+ const comparison = allowZero ? "greater than or equal to 0" : "greater than 0";
424
+ errors.push(issue(path, "invalid_dimensions", `${label} must be a finite number ${comparison}.`));
425
+ return false;
426
+ }
427
+ return true;
428
+ }
429
+ function validateStyle(value, path, errors) {
430
+ if (value === undefined) {
431
+ return;
432
+ }
433
+ if (!isRecord(value)) {
434
+ errors.push(issue(path, "invalid_type", "Style must be an object."));
435
+ return;
436
+ }
437
+ rejectUnknown(value, STYLE_KEYS, path, errors);
438
+ for (const key of STYLE_STRING_KEYS) {
439
+ const property = value[key];
440
+ if (property !== undefined && typeof property !== "string") {
441
+ errors.push(issue(`${path}.${key}`, "invalid_style", `${key} must be a string.`));
442
+ }
443
+ }
444
+ for (const key of STYLE_COLOR_KEYS) {
445
+ const property = value[key];
446
+ if (typeof property === "string" && cssColor(property) === null) {
447
+ errors.push(issue(`${path}.${key}`, "invalid_color", `${key} must be a CSS color.`));
448
+ }
449
+ }
450
+ if (typeof value.fontFamily === "string" && value.fontFamily.trim().length > 0 && cssFontFamily(value.fontFamily) === null) {
451
+ errors.push(issue(`${path}.fontFamily`, "invalid_style", "fontFamily must be a plain font family list."));
452
+ }
453
+ validateOptionalPositive(value.fontSize, `${path}.fontSize`, errors, "fontSize");
454
+ validateOptionalPositive(value.lineHeight, `${path}.lineHeight`, errors, "lineHeight");
455
+ validateOptionalNonNegative(value.borderWidth, `${path}.borderWidth`, errors, "borderWidth");
456
+ validateOptionalNonNegative(value.borderRadius, `${path}.borderRadius`, errors, "borderRadius");
457
+ validateOptionalNonNegative(value.strokeWidth, `${path}.strokeWidth`, errors, "strokeWidth");
458
+ validateBoxEdges(value.padding, `${path}.padding`, errors);
459
+ for (const edge of ["borderTop", "borderRight", "borderBottom", "borderLeft"]) {
460
+ validateBorderEdge(value[edge], `${path}.${edge}`, errors);
461
+ }
462
+ if (value.opacity !== undefined && (!isFiniteNumber(value.opacity) || value.opacity < 0 || value.opacity > 1)) {
463
+ errors.push(issue(`${path}.opacity`, "invalid_style", "opacity must be a finite number from 0 to 1."));
464
+ }
465
+ if (value.fontWeight !== undefined && !isFontWeight(value.fontWeight)) {
466
+ errors.push(issue(`${path}.fontWeight`, "invalid_style", 'fontWeight must be "normal", "medium", "bold", "lighter", "bolder", or a finite number greater than 0.'));
467
+ }
468
+ if (value.fontStyle !== undefined && !isOneOf(FONT_STYLES, value.fontStyle)) {
469
+ errors.push(issue(`${path}.fontStyle`, "invalid_style", 'fontStyle must be "normal" or "italic".'));
470
+ }
471
+ if (value.textAlign !== undefined && !isOneOf(TEXT_ALIGNS, value.textAlign)) {
472
+ errors.push(issue(`${path}.textAlign`, "invalid_style", 'textAlign must be "left", "center", "right", or "justify".'));
473
+ }
474
+ if (value.verticalAlign !== undefined && !isOneOf(VERTICAL_ALIGNS, value.verticalAlign)) {
475
+ errors.push(issue(`${path}.verticalAlign`, "invalid_style", 'verticalAlign must be "top", "middle", or "bottom".'));
476
+ }
477
+ if (value.textDecoration !== undefined && !isOneOf(TEXT_DECORATIONS, value.textDecoration)) {
478
+ errors.push(issue(`${path}.textDecoration`, "invalid_style", 'textDecoration must be "none", "underline", or "line-through".'));
479
+ }
480
+ if (value.borderStyle !== undefined && !isOneOf(BORDER_LINE_STYLES, value.borderStyle)) {
481
+ errors.push(issue(`${path}.borderStyle`, "invalid_style", 'borderStyle must be "solid", "dashed", "dotted", "double", or "none".'));
482
+ }
483
+ }
484
+ function validateBoxEdges(value, path, errors) {
485
+ if (value === undefined) {
486
+ return;
487
+ }
488
+ if (!isRecord(value)) {
489
+ errors.push(issue(path, "invalid_style", "padding must be an object."));
490
+ return;
491
+ }
492
+ rejectUnknown(value, BOX_EDGE_KEYS, path, errors);
493
+ for (const edge of BOX_EDGE_KEYS) {
494
+ validateOptionalNonNegative(value[edge], `${path}.${edge}`, errors, edge);
495
+ }
496
+ }
497
+ function validateBorderEdge(value, path, errors) {
498
+ if (value === undefined) {
499
+ return;
500
+ }
501
+ if (!isRecord(value)) {
502
+ errors.push(issue(path, "invalid_style", "A border side must be an object."));
503
+ return;
504
+ }
505
+ rejectUnknown(value, BORDER_EDGE_KEYS, path, errors);
506
+ validateOptionalNonNegative(value.width, `${path}.width`, errors, "width");
507
+ if (value.color !== undefined && (typeof value.color !== "string" || cssColor(value.color) === null)) {
508
+ errors.push(issue(`${path}.color`, "invalid_color", "Border color must be a CSS color."));
509
+ }
510
+ if (value.style !== undefined && !isOneOf(BORDER_LINE_STYLES, value.style)) {
511
+ errors.push(issue(`${path}.style`, "invalid_style", 'Border style must be "solid", "dashed", "dotted", "double", or "none".'));
512
+ }
513
+ }
514
+ function isFontWeight(value) {
515
+ if (typeof value === "string") {
516
+ return isOneOf(FONT_WEIGHT_NAMES, value);
517
+ }
518
+ return isFiniteNumber(value) && value > 0;
519
+ }
520
+ function validateOptionalPositive(value, path, errors, label) {
521
+ if (value === undefined) {
522
+ return;
523
+ }
524
+ if (!isFiniteNumber(value) || value <= 0) {
525
+ errors.push(issue(path, "invalid_style", `${label} must be a finite number greater than 0.`));
526
+ }
527
+ }
528
+ function validateOptionalNonNegative(value, path, errors, label) {
529
+ if (value === undefined) {
530
+ return;
531
+ }
532
+ if (!isFiniteNumber(value) || value < 0) {
533
+ errors.push(issue(path, "invalid_style", `${label} must be a finite number greater than or equal to 0.`));
534
+ }
535
+ }
536
+ function validateTextFields(value, path, errors) {
537
+ if (value.text !== undefined && typeof value.text !== "string") {
538
+ errors.push(issue(`${path}.text`, "invalid_type", "Text content must be a string."));
539
+ }
540
+ if (value.binding !== undefined) {
541
+ validateBinding(value.binding, `${path}.binding`, errors);
542
+ }
543
+ }
544
+ function validateImageFields(value, path, errors) {
545
+ if (value.src !== undefined) {
546
+ if (typeof value.src !== "string" || value.src.trim().length === 0) {
547
+ errors.push(issue(`${path}.src`, "invalid_type", "Image src must be a non-empty string."));
548
+ }
549
+ else if (/^\s*javascript:/i.test(value.src)) {
550
+ errors.push(issue(`${path}.src`, "invalid_src", "Image src must not be a javascript: URL."));
551
+ }
552
+ }
553
+ if (value.alt !== undefined && typeof value.alt !== "string") {
554
+ errors.push(issue(`${path}.alt`, "invalid_type", "Image alt text must be a string."));
555
+ }
556
+ if (value.binding !== undefined) {
557
+ validateBinding(value.binding, `${path}.binding`, errors);
558
+ if (isRecord(value.binding) &&
559
+ typeof value.binding.fallback === "string" &&
560
+ /^\s*javascript:/i.test(value.binding.fallback)) {
561
+ errors.push(issue(`${path}.binding.fallback`, "invalid_src", "Image fallback must not be a javascript: URL."));
562
+ }
563
+ }
564
+ }
565
+ function validateBinding(value, path, errors) {
566
+ if (!isRecord(value)) {
567
+ errors.push(issue(path, "invalid_binding", "Binding must be an object with a path."));
568
+ return;
569
+ }
570
+ rejectUnknown(value, BINDING_KEYS, path, errors);
571
+ if (typeof value.path !== "string") {
572
+ errors.push(issue(`${path}.path`, "invalid_binding", 'Binding path must be a dot-separated identifier, such as "company.name" or "items.0.name".'));
573
+ }
574
+ else {
575
+ const parsed = parseBindingPath(value.path);
576
+ if (!parsed.valid) {
577
+ errors.push(issue(`${path}.path`, parsed.reason === "unsafe" ? "unsafe_binding" : "invalid_binding", parsed.reason === "unsafe"
578
+ ? "Binding path must not use __proto__, prototype, or constructor."
579
+ : 'Binding path must be a dot-separated identifier, such as "company.name" or "items.0.name".'));
580
+ }
581
+ }
582
+ for (const key of ["fallback", "prefix", "suffix"]) {
583
+ if (value[key] !== undefined && typeof value[key] !== "string") {
584
+ errors.push(issue(`${path}.${key}`, "invalid_binding", `Binding ${key} must be a string.`));
585
+ }
586
+ }
587
+ if (value.format !== undefined) {
588
+ validateFormat(value.format, `${path}.format`, errors);
589
+ }
590
+ }
591
+ function validateReportOptions(value, errors) {
592
+ if (value === undefined) {
593
+ return;
594
+ }
595
+ if (!isRecord(value)) {
596
+ errors.push(issue("report", "invalid_type", "Report options must be an object."));
597
+ return;
598
+ }
599
+ rejectUnknown(value, REPORT_OPTION_KEYS, "report", errors);
600
+ for (const key of ["title", "description", "emptyText"]) {
601
+ if (value[key] !== undefined && typeof value[key] !== "string") {
602
+ errors.push(issue(`report.${key}`, "invalid_type", `Report ${key} must be a string.`));
603
+ }
604
+ }
605
+ if (value.showPageNumber !== undefined && typeof value.showPageNumber !== "boolean") {
606
+ errors.push(issue("report.showPageNumber", "invalid_type", "showPageNumber must be a boolean."));
607
+ }
608
+ for (const key of ["pageHeader", "pageFooter", "reportHeader", "reportFooter"]) {
609
+ validateBandList(value[key], `report.${key}`, errors);
610
+ }
611
+ }
612
+ function validateBandList(value, path, errors) {
613
+ if (value === undefined) {
614
+ return;
615
+ }
616
+ if (!Array.isArray(value) || value.length > 8) {
617
+ errors.push(issue(path, "invalid_band", "A report band needs at most 8 entries."));
618
+ return;
619
+ }
620
+ const seen = new Set();
621
+ value.forEach((entry, index) => {
622
+ const entryPath = `${path}[${index}]`;
623
+ if (!isRecord(entry) || !isOneOf(REPORT_BAND_TYPES, entry.type)) {
624
+ errors.push(issue(entryPath, "invalid_band", 'A report band entry must be type "text" or "image".'));
625
+ return;
626
+ }
627
+ rejectUnknown(entry, entry.type === "text" ? REPORT_TEXT_BAND_KEYS : REPORT_IMAGE_BAND_KEYS, entryPath, errors);
628
+ validateIdentifier(entry.id, `${entryPath}.id`, errors, "Band id");
629
+ if (typeof entry.id === "string" && seen.has(entry.id)) {
630
+ errors.push(issue(`${entryPath}.id`, "duplicate_band_id", `Band id "${entry.id}" is duplicated.`));
631
+ }
632
+ if (typeof entry.id === "string") {
633
+ seen.add(entry.id);
634
+ }
635
+ if (entry.type === "text") {
636
+ if (entry.text !== undefined && typeof entry.text !== "string") {
637
+ errors.push(issue(`${entryPath}.text`, "invalid_type", "Band text must be a string."));
638
+ }
639
+ if (entry.binding !== undefined) {
640
+ validateBinding(entry.binding, `${entryPath}.binding`, errors);
641
+ }
642
+ return;
643
+ }
644
+ if (entry.src !== undefined && typeof entry.src !== "string") {
645
+ errors.push(issue(`${entryPath}.src`, "invalid_type", "Band image src must be a string."));
646
+ }
647
+ else if (typeof entry.src === "string" && /^\s*javascript:/i.test(entry.src)) {
648
+ errors.push(issue(`${entryPath}.src`, "invalid_src", "Band image src must not be a javascript: URL."));
649
+ }
650
+ if (entry.alt !== undefined && typeof entry.alt !== "string") {
651
+ errors.push(issue(`${entryPath}.alt`, "invalid_type", "Band image alt text must be a string."));
652
+ }
653
+ if (entry.binding !== undefined) {
654
+ validateBinding(entry.binding, `${entryPath}.binding`, errors);
655
+ if (isRecord(entry.binding) && typeof entry.binding.fallback === "string" && /^\s*javascript:/i.test(entry.binding.fallback)) {
656
+ errors.push(issue(`${entryPath}.binding.fallback`, "invalid_src", "Band image fallback must not be a javascript: URL."));
657
+ }
658
+ }
659
+ });
660
+ }
661
+ function validateReportTableFields(value, path, errors) {
662
+ validateBindingPathValue(value.collectionPath, `${path}.collectionPath`, errors, "Collection path");
663
+ if (value.showHeader !== undefined && typeof value.showHeader !== "boolean") {
664
+ errors.push(issue(`${path}.showHeader`, "invalid_type", "showHeader must be a boolean."));
665
+ }
666
+ if (value.emptyText !== undefined && typeof value.emptyText !== "string") {
667
+ errors.push(issue(`${path}.emptyText`, "invalid_type", "Empty report text must be a string."));
668
+ }
669
+ if (value.padding !== undefined && (!isFiniteNumber(value.padding) || value.padding < 0)) {
670
+ errors.push(issue(`${path}.padding`, "invalid_number", "Report padding must be a finite number greater than or equal to 0."));
671
+ }
672
+ if (value.headerStyle !== undefined) {
673
+ validateStyle(value.headerStyle, `${path}.headerStyle`, errors);
674
+ }
675
+ for (const key of ["alternateRowColor", "oddRowColor", "evenRowColor"]) {
676
+ if (value[key] !== undefined) {
677
+ if (typeof value[key] !== "string" || cssColor(value[key]) === null) {
678
+ errors.push(issue(`${path}.${key}`, "invalid_color", "Row color must be a CSS color."));
679
+ }
680
+ }
681
+ }
682
+ if (value.stripeRows !== undefined && typeof value.stripeRows !== "boolean") {
683
+ errors.push(issue(`${path}.stripeRows`, "invalid_type", "stripeRows must be a boolean."));
684
+ }
685
+ for (const key of ["bodyStyle", "groupStyle", "subtotalStyle", "grandTotalStyle"]) {
686
+ if (value[key] !== undefined) {
687
+ validateStyle(value[key], `${path}.${key}`, errors);
688
+ }
689
+ }
690
+ if (value.tableAlign !== undefined && value.tableAlign !== "left" && value.tableAlign !== "center" && value.tableAlign !== "right") {
691
+ errors.push(issue(`${path}.tableAlign`, "invalid_style", 'tableAlign must be "left", "center", or "right".'));
692
+ }
693
+ if (value.headerHeight !== undefined && (!isFiniteNumber(value.headerHeight) || value.headerHeight <= 0 || value.headerHeight > 200)) {
694
+ errors.push(issue(`${path}.headerHeight`, "invalid_number", "headerHeight must be a finite number greater than 0 and at most 200."));
695
+ }
696
+ validateRowRules(value.rowRules, `${path}.rowRules`, errors);
697
+ if (value.showGrandTotal !== undefined && typeof value.showGrandTotal !== "boolean") {
698
+ errors.push(issue(`${path}.showGrandTotal`, "invalid_type", "showGrandTotal must be a boolean."));
699
+ }
700
+ if (value.grandTotalLabel !== undefined && typeof value.grandTotalLabel !== "string") {
701
+ errors.push(issue(`${path}.grandTotalLabel`, "invalid_type", "Grand total label must be a string."));
702
+ }
703
+ if (value.pageBreakBefore !== undefined && typeof value.pageBreakBefore !== "boolean") {
704
+ errors.push(issue(`${path}.pageBreakBefore`, "invalid_type", "pageBreakBefore must be a boolean."));
705
+ }
706
+ validateReportColumns(value.columns, `${path}.columns`, errors);
707
+ validateGroupLevels(value.groupBy, `${path}.groupBy`, errors);
708
+ validateSorts(value.sort, `${path}.sort`, errors);
709
+ }
710
+ function validateBindingPathValue(value, path, errors, label) {
711
+ if (typeof value !== "string") {
712
+ errors.push(issue(path, "invalid_binding", `${label} must be a binding path.`));
713
+ return;
714
+ }
715
+ const parsed = parseBindingPath(value);
716
+ if (!parsed.valid) {
717
+ errors.push(issue(path, parsed.reason === "unsafe" ? "unsafe_binding" : "invalid_binding", `${label} must be a safe binding path.`));
718
+ }
719
+ }
720
+ function validateReportColumns(value, path, errors) {
721
+ if (!Array.isArray(value) || value.length === 0 || value.length > 12) {
722
+ errors.push(issue(path, "invalid_columns", "A report table needs from 1 to 12 columns."));
723
+ return;
724
+ }
725
+ const seen = new Set();
726
+ value.forEach((column, index) => {
727
+ const columnPath = `${path}[${index}]`;
728
+ if (!isRecord(column)) {
729
+ errors.push(issue(columnPath, "invalid_type", "A column must be an object."));
730
+ return;
731
+ }
732
+ rejectUnknown(column, REPORT_COLUMN_KEYS, columnPath, errors);
733
+ validateIdentifier(column.id, `${columnPath}.id`, errors, "Column id");
734
+ if (typeof column.id === "string" && seen.has(column.id)) {
735
+ errors.push(issue(`${columnPath}.id`, "duplicate_column_id", `Column id "${column.id}" is duplicated.`));
736
+ }
737
+ if (typeof column.id === "string") {
738
+ seen.add(column.id);
739
+ }
740
+ if (typeof column.header !== "string") {
741
+ errors.push(issue(`${columnPath}.header`, "invalid_type", "Column header must be a string."));
742
+ }
743
+ validateBinding(column.binding, `${columnPath}.binding`, errors);
744
+ if (column.width !== undefined && (!isFiniteNumber(column.width) || column.width <= 0)) {
745
+ errors.push(issue(`${columnPath}.width`, "invalid_number", "Column width must be a finite number greater than 0."));
746
+ }
747
+ if (column.align !== undefined && !isOneOf(TEXT_ALIGNS, column.align)) {
748
+ errors.push(issue(`${columnPath}.align`, "invalid_align", 'Column alignment must be "left", "center", "right", or "justify".'));
749
+ }
750
+ if (column.visible !== undefined && typeof column.visible !== "boolean") {
751
+ errors.push(issue(`${columnPath}.visible`, "invalid_type", "Column visible must be a boolean."));
752
+ }
753
+ if (column.aggregate !== undefined && !isOneOf(AGGREGATES, column.aggregate)) {
754
+ errors.push(issue(`${columnPath}.aggregate`, "invalid_aggregate", 'Aggregate must be "sum", "count", "min", or "max".'));
755
+ }
756
+ if (column.headerStyle !== undefined) {
757
+ validateStyle(column.headerStyle, `${columnPath}.headerStyle`, errors);
758
+ }
759
+ if (column.style !== undefined) {
760
+ validateStyle(column.style, `${columnPath}.style`, errors);
761
+ }
762
+ });
763
+ }
764
+ function validateRowRules(value, path, errors) {
765
+ if (value === undefined) {
766
+ return;
767
+ }
768
+ if (!Array.isArray(value) || value.length > 20) {
769
+ errors.push(issue(path, "invalid_style", "A report table supports at most 20 row rules."));
770
+ return;
771
+ }
772
+ value.forEach((rule, index) => {
773
+ const rulePath = `${path}[${index}]`;
774
+ if (!isRecord(rule)) {
775
+ errors.push(issue(rulePath, "invalid_type", "A row rule must be an object."));
776
+ return;
777
+ }
778
+ rejectUnknown(rule, ROW_RULE_KEYS, rulePath, errors);
779
+ validateBindingPathValue(rule.path, `${rulePath}.path`, errors, "Row rule path");
780
+ if (!isOneOf(ROW_RULE_OPERATORS, rule.operator)) {
781
+ errors.push(issue(`${rulePath}.operator`, "invalid_style", 'Row rule operator must be "eq", "neq", "gt", "gte", "lt", "lte", or "blank".'));
782
+ }
783
+ if (rule.value !== undefined && typeof rule.value !== "string" && typeof rule.value !== "number" && typeof rule.value !== "boolean") {
784
+ errors.push(issue(`${rulePath}.value`, "invalid_type", "Row rule value must be text, a number, or a boolean."));
785
+ }
786
+ if (typeof rule.value === "number" && !Number.isFinite(rule.value)) {
787
+ errors.push(issue(`${rulePath}.value`, "invalid_number", "Row rule value must be a finite number."));
788
+ }
789
+ if (!isRecord(rule.style)) {
790
+ errors.push(issue(`${rulePath}.style`, "invalid_style", "A row rule needs a style object."));
791
+ }
792
+ else {
793
+ validateStyle(rule.style, `${rulePath}.style`, errors);
794
+ }
795
+ });
796
+ }
797
+ function validateGroupLevels(value, path, errors) {
798
+ if (value === undefined) {
799
+ return;
800
+ }
801
+ if (!Array.isArray(value) || value.length > 3) {
802
+ errors.push(issue(path, "invalid_group", "A report table supports at most 3 group levels."));
803
+ return;
804
+ }
805
+ value.forEach((level, index) => {
806
+ const levelPath = `${path}[${index}]`;
807
+ if (!isRecord(level)) {
808
+ errors.push(issue(levelPath, "invalid_type", "A group level must be an object."));
809
+ return;
810
+ }
811
+ rejectUnknown(level, REPORT_GROUP_KEYS, levelPath, errors);
812
+ validateBindingPathValue(level.path, `${levelPath}.path`, errors, "Group path");
813
+ if (level.label !== undefined && typeof level.label !== "string") {
814
+ errors.push(issue(`${levelPath}.label`, "invalid_type", "Group label must be a string."));
815
+ }
816
+ });
817
+ }
818
+ function validateSorts(value, path, errors) {
819
+ if (value === undefined) {
820
+ return;
821
+ }
822
+ if (!Array.isArray(value) || value.length > 4) {
823
+ errors.push(issue(path, "invalid_sort", "A report table supports at most 4 sort fields."));
824
+ return;
825
+ }
826
+ value.forEach((sort, index) => {
827
+ const sortPath = `${path}[${index}]`;
828
+ if (!isRecord(sort)) {
829
+ errors.push(issue(sortPath, "invalid_type", "A sort must be an object."));
830
+ return;
831
+ }
832
+ rejectUnknown(sort, REPORT_SORT_KEYS, sortPath, errors);
833
+ validateBindingPathValue(sort.path, `${sortPath}.path`, errors, "Sort path");
834
+ if (!isOneOf(SORT_DIRECTIONS, sort.direction)) {
835
+ errors.push(issue(`${sortPath}.direction`, "invalid_sort", 'Sort direction must be "asc" or "desc".'));
836
+ }
837
+ });
838
+ }
839
+ function validateInvoiceOptions(value, errors) {
840
+ if (value === undefined) {
841
+ return;
842
+ }
843
+ if (!isRecord(value)) {
844
+ errors.push(issue("invoice", "invalid_type", "Invoice options must be an object."));
845
+ return;
846
+ }
847
+ rejectUnknown(value, INVOICE_OPTION_KEYS, "invoice", errors);
848
+ if (value.fractionDigits !== undefined && (!Number.isInteger(value.fractionDigits) || value.fractionDigits < 0 || value.fractionDigits > 6)) {
849
+ errors.push(issue("invoice.fractionDigits", "invalid_number", "Invoice fraction digits must be an integer from 0 to 6."));
850
+ }
851
+ }
852
+ function validateFormat(value, path, errors) {
853
+ if (!isRecord(value)) {
854
+ errors.push(issue(path, "invalid_format", "Format must be an object."));
855
+ return;
856
+ }
857
+ rejectUnknown(value, FORMAT_KEYS, path, errors);
858
+ if (!isOneOf(FORMAT_STYLES, value.style)) {
859
+ errors.push(issue(`${path}.style`, "invalid_format", 'Format style must be "decimal", "currency", "percent", or "date".'));
860
+ }
861
+ if (value.locale !== undefined && (typeof value.locale !== "string" || value.locale.trim().length === 0)) {
862
+ errors.push(issue(`${path}.locale`, "invalid_format", "Format locale must be a non-empty string."));
863
+ }
864
+ if (value.currency !== undefined && (typeof value.currency !== "string" || !/^[A-Za-z]{3}$/.test(value.currency))) {
865
+ errors.push(issue(`${path}.currency`, "invalid_format", "Format currency must be a 3-letter code."));
866
+ }
867
+ validateFractionDigits(value.minimumFractionDigits, `${path}.minimumFractionDigits`, errors);
868
+ validateFractionDigits(value.maximumFractionDigits, `${path}.maximumFractionDigits`, errors);
869
+ if (typeof value.minimumFractionDigits === "number" &&
870
+ typeof value.maximumFractionDigits === "number" &&
871
+ value.minimumFractionDigits > value.maximumFractionDigits) {
872
+ errors.push(issue(path, "invalid_format", "Minimum fraction digits cannot exceed maximum fraction digits."));
873
+ }
874
+ if (value.dateStyle !== undefined && !isOneOf(DATE_STYLES, value.dateStyle)) {
875
+ errors.push(issue(`${path}.dateStyle`, "invalid_format", 'Date style must be "iso", "short", "medium", or "long".'));
876
+ }
877
+ }
878
+ function validateFractionDigits(value, path, errors) {
879
+ if (value === undefined) {
880
+ return;
881
+ }
882
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 0 || value > 20) {
883
+ errors.push(issue(path, "invalid_format", "Fraction digits must be an integer from 0 to 20."));
884
+ }
885
+ }
886
+ function validateItemsFields(value, path, errors) {
887
+ if (typeof value.itemsPath !== "string") {
888
+ errors.push(issue(`${path}.itemsPath`, "invalid_binding", 'Items path must be a binding path such as "invoice.items".'));
889
+ }
890
+ else {
891
+ const parsed = parseBindingPath(value.itemsPath);
892
+ if (!parsed.valid) {
893
+ errors.push(issue(`${path}.itemsPath`, parsed.reason === "unsafe" ? "unsafe_binding" : "invalid_binding", 'Items path must be a binding path such as "invoice.items".'));
894
+ }
895
+ }
896
+ if (value.showHeader !== undefined && typeof value.showHeader !== "boolean") {
897
+ errors.push(issue(`${path}.showHeader`, "invalid_type", "showHeader must be a boolean."));
898
+ }
899
+ if (value.emptyText !== undefined && typeof value.emptyText !== "string") {
900
+ errors.push(issue(`${path}.emptyText`, "invalid_type", "Empty items text must be a string."));
901
+ }
902
+ if (value.padding !== undefined && (!isFiniteNumber(value.padding) || value.padding < 0)) {
903
+ errors.push(issue(`${path}.padding`, "invalid_number", "Items padding must be a finite number greater than or equal to 0."));
904
+ }
905
+ if (value.headerStyle !== undefined) {
906
+ validateStyle(value.headerStyle, `${path}.headerStyle`, errors);
907
+ }
908
+ validateColumns(value.columns, `${path}.columns`, errors);
909
+ }
910
+ function validateColumns(value, path, errors) {
911
+ if (!Array.isArray(value) || value.length === 0 || value.length > 12) {
912
+ errors.push(issue(path, "invalid_columns", "An items table needs from 1 to 12 columns."));
913
+ return;
914
+ }
915
+ const seen = new Set();
916
+ value.forEach((column, index) => {
917
+ const columnPath = `${path}[${index}]`;
918
+ if (!isRecord(column)) {
919
+ errors.push(issue(columnPath, "invalid_type", "A column must be an object."));
920
+ return;
921
+ }
922
+ rejectUnknown(column, COLUMN_KEYS, columnPath, errors);
923
+ validateIdentifier(column.id, `${columnPath}.id`, errors, "Column id");
924
+ if (typeof column.id === "string" && seen.has(column.id)) {
925
+ errors.push(issue(`${columnPath}.id`, "duplicate_column_id", `Column id "${column.id}" is duplicated.`));
926
+ }
927
+ if (typeof column.id === "string") {
928
+ seen.add(column.id);
929
+ }
930
+ if (typeof column.header !== "string") {
931
+ errors.push(issue(`${columnPath}.header`, "invalid_type", "Column header must be a string."));
932
+ }
933
+ validateBinding(column.binding, `${columnPath}.binding`, errors);
934
+ if (column.width !== undefined && (!isFiniteNumber(column.width) || column.width <= 0)) {
935
+ errors.push(issue(`${columnPath}.width`, "invalid_number", "Column width must be a finite number greater than 0."));
936
+ }
937
+ if (column.align !== undefined && !isOneOf(TEXT_ALIGNS, column.align)) {
938
+ errors.push(issue(`${columnPath}.align`, "invalid_align", 'Column alignment must be "left", "center", "right", or "justify".'));
939
+ }
940
+ if (column.visible !== undefined && typeof column.visible !== "boolean") {
941
+ errors.push(issue(`${columnPath}.visible`, "invalid_type", "Column visible must be a boolean."));
942
+ }
943
+ });
944
+ }
945
+ function validateTotalsFields(value, path, errors) {
946
+ if (!Array.isArray(value.rows) || value.rows.length === 0 || value.rows.length > 12) {
947
+ errors.push(issue(`${path}.rows`, "invalid_rows", "A totals block needs from 1 to 12 rows."));
948
+ return;
949
+ }
950
+ const seen = new Set();
951
+ value.rows.forEach((row, index) => {
952
+ const rowPath = `${path}.rows[${index}]`;
953
+ if (!isRecord(row)) {
954
+ errors.push(issue(rowPath, "invalid_type", "A totals row must be an object."));
955
+ return;
956
+ }
957
+ rejectUnknown(row, TOTAL_ROW_KEYS, rowPath, errors);
958
+ validateIdentifier(row.id, `${rowPath}.id`, errors, "Totals row id");
959
+ if (typeof row.id === "string" && seen.has(row.id)) {
960
+ errors.push(issue(`${rowPath}.id`, "duplicate_row_id", `Totals row id "${row.id}" is duplicated.`));
961
+ }
962
+ if (typeof row.id === "string") {
963
+ seen.add(row.id);
964
+ }
965
+ if (typeof row.label !== "string") {
966
+ errors.push(issue(`${rowPath}.label`, "invalid_type", "Totals label must be a string."));
967
+ }
968
+ validateBinding(row.binding, `${rowPath}.binding`, errors);
969
+ if (row.visible !== undefined && typeof row.visible !== "boolean") {
970
+ errors.push(issue(`${rowPath}.visible`, "invalid_type", "Totals visible must be a boolean."));
971
+ }
972
+ if (row.hideWhenZero !== undefined && typeof row.hideWhenZero !== "boolean") {
973
+ errors.push(issue(`${rowPath}.hideWhenZero`, "invalid_type", "hideWhenZero must be a boolean."));
974
+ }
975
+ });
976
+ }
977
+ //# sourceMappingURL=validate.js.map