@fin.cx/einvoice 5.1.4 → 5.2.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 (118) hide show
  1. package/.smartconfig.json +59 -0
  2. package/dist_ts/00_commitinfo_data.js +1 -1
  3. package/dist_ts/einvoice.d.ts +14 -5
  4. package/dist_ts/einvoice.js +173 -66
  5. package/dist_ts/errors.js +14 -4
  6. package/dist_ts/formats/base/base.decoder.d.ts +4 -0
  7. package/dist_ts/formats/base/base.decoder.js +9 -1
  8. package/dist_ts/formats/base/base.validator.js +3 -2
  9. package/dist_ts/formats/cii/cii.decoder.d.ts +1 -0
  10. package/dist_ts/formats/cii/cii.decoder.js +20 -10
  11. package/dist_ts/formats/cii/cii.encoder.js +2 -5
  12. package/dist_ts/formats/cii/cii.validator.d.ts +1 -1
  13. package/dist_ts/formats/cii/cii.validator.js +19 -12
  14. package/dist_ts/formats/cii/facturx/facturx.encoder.js +25 -16
  15. package/dist_ts/formats/converters/xml-to-einvoice.converter.js +3 -3
  16. package/dist_ts/formats/factories/decoder.factory.d.ts +3 -1
  17. package/dist_ts/formats/factories/decoder.factory.js +4 -3
  18. package/dist_ts/formats/factories/validator.factory.d.ts +4 -1
  19. package/dist_ts/formats/factories/validator.factory.js +11 -8
  20. package/dist_ts/formats/pdf/extractors/associated.extractor.js +3 -2
  21. package/dist_ts/formats/pdf/extractors/base.extractor.d.ts +1 -1
  22. package/dist_ts/formats/pdf/extractors/base.extractor.js +50 -52
  23. package/dist_ts/formats/pdf/extractors/standard.extractor.js +3 -2
  24. package/dist_ts/formats/pdf/extractors/text.extractor.js +19 -22
  25. package/dist_ts/formats/pdf/pdf.embedder.js +3 -2
  26. package/dist_ts/formats/pdf/pdf.extractor.d.ts +2 -1
  27. package/dist_ts/formats/pdf/pdf.extractor.js +20 -10
  28. package/dist_ts/formats/semantic/semantic.adapter.js +42 -12
  29. package/dist_ts/formats/semantic/semantic.validator.js +3 -1
  30. package/dist_ts/formats/ubl/en16931.ubl.validator.js +5 -3
  31. package/dist_ts/formats/ubl/generic/ubl.encoder.js +11 -7
  32. package/dist_ts/formats/ubl/ubl.decoder.d.ts +1 -0
  33. package/dist_ts/formats/ubl/ubl.decoder.js +18 -8
  34. package/dist_ts/formats/ubl/ubl.validator.d.ts +1 -1
  35. package/dist_ts/formats/ubl/ubl.validator.js +17 -10
  36. package/dist_ts/formats/ubl/xrechnung/xrechnung.decoder.js +37 -29
  37. package/dist_ts/formats/utils/currency.calculator.decimal.js +4 -1
  38. package/dist_ts/formats/utils/currency.utils.js +4 -1
  39. package/dist_ts/formats/utils/decimal.js +9 -2
  40. package/dist_ts/formats/utils/format.detector.js +47 -17
  41. package/dist_ts/formats/validation/codelist.validator.js +2 -4
  42. package/dist_ts/formats/validation/conformance.harness.js +13 -6
  43. package/dist_ts/formats/validation/en16931.business-rules.validator.js +4 -4
  44. package/dist_ts/formats/validation/en16931.validator.d.ts +6 -0
  45. package/dist_ts/formats/validation/en16931.validator.js +18 -12
  46. package/dist_ts/formats/validation/facturx.validator.js +147 -148
  47. package/dist_ts/formats/validation/integrated.validator.js +12 -4
  48. package/dist_ts/formats/validation/peppol.validator.js +2 -1
  49. package/dist_ts/formats/validation/schematron.downloader.d.ts +0 -1
  50. package/dist_ts/formats/validation/schematron.downloader.js +12 -7
  51. package/dist_ts/formats/validation/schematron.integration.js +16 -6
  52. package/dist_ts/formats/validation/schematron.validator.d.ts +9 -11
  53. package/dist_ts/formats/validation/schematron.validator.js +70 -72
  54. package/dist_ts/formats/validation/schematron.worker.d.ts +7 -6
  55. package/dist_ts/formats/validation/schematron.worker.js +48 -42
  56. package/dist_ts/formats/validation/vat-categories.validator.js +3 -4
  57. package/dist_ts/formats/validation/xrechnung.validator.js +7 -7
  58. package/dist_ts/index.js +3 -2
  59. package/dist_ts/interfaces/common.js +1 -2
  60. package/dist_ts/plugins.d.ts +3 -6
  61. package/dist_ts/plugins.js +3 -12
  62. package/dist_ts/vendor/pako.d.ts +5 -0
  63. package/dist_ts/vendor/pako.js +3 -0
  64. package/dist_ts/vendor/pdflib.d.ts +2 -0
  65. package/dist_ts/vendor/pdflib.js +9 -0
  66. package/dist_ts/vendor/saxonjs.d.ts +23 -0
  67. package/dist_ts/vendor/saxonjs.js +9 -0
  68. package/dist_ts/vendor/xmldom.d.ts +20 -0
  69. package/dist_ts/vendor/xmldom.js +5 -0
  70. package/dist_ts_install/download-schematron.d.ts +0 -3
  71. package/dist_ts_install/download-schematron.js +20 -11
  72. package/dist_ts_install/download-test-samples.d.ts +0 -3
  73. package/dist_ts_install/download-test-samples.js +4 -2
  74. package/dist_ts_install/download-xrechnung-rules.js +2 -1
  75. package/dist_ts_install/index.d.ts +0 -5
  76. package/dist_ts_install/index.js +26 -12
  77. package/license +21 -0
  78. package/package.json +21 -15
  79. package/readme.hints.md +24 -8
  80. package/readme.md +195 -328
  81. package/ts/00_commitinfo_data.ts +1 -1
  82. package/ts/einvoice.ts +156 -33
  83. package/ts/formats/base/base.decoder.ts +7 -0
  84. package/ts/formats/cii/cii.decoder.ts +17 -8
  85. package/ts/formats/cii/cii.validator.ts +18 -13
  86. package/ts/formats/cii/facturx/facturx.encoder.ts +25 -15
  87. package/ts/formats/converters/xml-to-einvoice.converter.ts +4 -5
  88. package/ts/formats/factories/decoder.factory.ts +7 -2
  89. package/ts/formats/factories/validator.factory.ts +14 -8
  90. package/ts/formats/pdf/extractors/associated.extractor.ts +2 -1
  91. package/ts/formats/pdf/extractors/base.extractor.ts +3 -2
  92. package/ts/formats/pdf/extractors/standard.extractor.ts +2 -1
  93. package/ts/formats/pdf/pdf.embedder.ts +6 -3
  94. package/ts/formats/pdf/pdf.extractor.ts +24 -16
  95. package/ts/formats/semantic/semantic.adapter.ts +49 -15
  96. package/ts/formats/ubl/en16931.ubl.validator.ts +5 -3
  97. package/ts/formats/ubl/generic/ubl.encoder.ts +12 -7
  98. package/ts/formats/ubl/ubl.decoder.ts +18 -9
  99. package/ts/formats/ubl/ubl.validator.ts +17 -12
  100. package/ts/formats/ubl/xrechnung/xrechnung.decoder.ts +41 -28
  101. package/ts/formats/utils/format.detector.ts +57 -19
  102. package/ts/formats/validation/conformance.harness.ts +6 -4
  103. package/ts/formats/validation/en16931.validator.ts +20 -10
  104. package/ts/formats/validation/integrated.validator.ts +5 -3
  105. package/ts/formats/validation/schematron.downloader.ts +11 -8
  106. package/ts/formats/validation/schematron.integration.ts +11 -5
  107. package/ts/formats/validation/schematron.validator.ts +79 -79
  108. package/ts/formats/validation/schematron.worker.ts +59 -41
  109. package/ts/index.ts +2 -1
  110. package/ts/interfaces/common.ts +1 -3
  111. package/ts/plugins.ts +2 -29
  112. package/ts/readme.md +76 -0
  113. package/ts/vendor/modules.d.ts +32 -0
  114. package/ts/vendor/pako.ts +8 -0
  115. package/ts/vendor/pdflib.ts +12 -0
  116. package/ts/vendor/saxonjs.ts +36 -0
  117. package/ts/vendor/xmldom.ts +27 -0
  118. package/npmextra.json +0 -35
@@ -27,11 +27,11 @@ export class EN16931Validator {
27
27
  ];
28
28
 
29
29
  /**
30
- * Validates that an invoice object contains all mandatory EN16931 fields
30
+ * Collects mandatory EN16931 field errors without throwing.
31
31
  * @param invoice The invoice object to validate
32
- * @throws Error if mandatory fields are missing
32
+ * @returns List of validation error messages
33
33
  */
34
- public static validateMandatoryFields(invoice: any): void {
34
+ public static collectMandatoryFieldErrors(invoice: any): string[] {
35
35
  const errors: string[] = [];
36
36
 
37
37
  // BR-01: Invoice number is mandatory
@@ -49,7 +49,7 @@ export class EN16931Validator {
49
49
  errors.push('BR-06: Seller name is mandatory');
50
50
  }
51
51
 
52
- // BR-07: Buyer name is mandatory
52
+ // BR-07: Buyer name is mandatory
53
53
  if (!invoice.to?.name) {
54
54
  errors.push('BR-07: Buyer name is mandatory');
55
55
  }
@@ -67,11 +67,10 @@ export class EN16931Validator {
67
67
  // BR-05: Invoice currency code is mandatory
68
68
  if (!invoice.currency) {
69
69
  errors.push('BR-05: Invoice currency code is mandatory');
70
- } else {
71
- // Validate currency format
72
- if (!this.VALID_CURRENCIES.includes(invoice.currency.toUpperCase())) {
73
- errors.push(`Invalid currency code: ${invoice.currency}. Must be a valid ISO 4217 currency code`);
74
- }
70
+ } else if (!this.VALID_CURRENCIES.includes(invoice.currency.toUpperCase())) {
71
+ errors.push(
72
+ `BR-05: Invalid currency code: ${invoice.currency}. Must be a valid ISO 4217 currency code`
73
+ );
75
74
  }
76
75
 
77
76
  // BR-08: Seller postal address is mandatory
@@ -84,6 +83,17 @@ export class EN16931Validator {
84
83
  errors.push('BR-10: Buyer postal address (city, postal code, country) is mandatory');
85
84
  }
86
85
 
86
+ return errors;
87
+ }
88
+
89
+ /**
90
+ * Validates that an invoice object contains all mandatory EN16931 fields
91
+ * @param invoice The invoice object to validate
92
+ * @throws Error if mandatory fields are missing
93
+ */
94
+ public static validateMandatoryFields(invoice: any): void {
95
+ const errors = this.collectMandatoryFieldErrors(invoice);
96
+
87
97
  if (errors.length > 0) {
88
98
  throw new Error(`EN16931 validation failed:\n${errors.join('\n')}`);
89
99
  }
@@ -132,4 +142,4 @@ export class EN16931Validator {
132
142
  throw new Error(`EN16931 XML validation failed:\n${errors.join('\n')}`);
133
143
  }
134
144
  }
135
- }
145
+ }
@@ -60,7 +60,8 @@ export class MainValidator {
60
60
  this.schematronEnabled = true;
61
61
  console.log(`Schematron validation enabled for ${standard} ${format}`);
62
62
  } catch (error) {
63
- console.warn(`Failed to initialize Schematron: ${error.message}`);
63
+ const errorMessage = error instanceof Error ? error.message : String(error);
64
+ console.warn(`Failed to initialize Schematron: ${errorMessage}`);
64
65
  }
65
66
  }
66
67
 
@@ -121,7 +122,8 @@ export class MainValidator {
121
122
  );
122
123
  results.push(...schematronResults);
123
124
  } catch (error) {
124
- console.warn(`Schematron validation error: ${error.message}`);
125
+ const errorMessage = error instanceof Error ? error.message : String(error);
126
+ console.warn(`Schematron validation error: ${errorMessage}`);
125
127
  }
126
128
  }
127
129
 
@@ -402,4 +404,4 @@ export async function createValidator(
402
404
  }
403
405
 
404
406
  // Export for convenience
405
- export type { ValidationReport, ValidationResult, ValidationOptions } from './validation.types.js';
407
+ export type { ValidationReport, ValidationResult, ValidationOptions } from './validation.types.js';
@@ -79,7 +79,6 @@ export const SCHEMATRON_SOURCES: Record<string, SchematronSource[]> = {
79
79
  */
80
80
  export class SchematronDownloader {
81
81
  private cacheDir: string;
82
- private smartfile: any;
83
82
 
84
83
  constructor(cacheDir: string = 'assets_downloaded/schematron') {
85
84
  this.cacheDir = cacheDir;
@@ -90,7 +89,6 @@ export class SchematronDownloader {
90
89
  */
91
90
  public async initialize(): Promise<void> {
92
91
  // Ensure cache directory exists
93
- this.smartfile = await import('@push.rocks/smartfile');
94
92
  await fs.mkdir(this.cacheDir, { recursive: true });
95
93
  }
96
94
 
@@ -139,7 +137,8 @@ export class SchematronDownloader {
139
137
  console.log(`Successfully downloaded: ${fileName}`);
140
138
  return filePath;
141
139
  } catch (error) {
142
- throw new Error(`Failed to download ${source.name}: ${error.message}`);
140
+ const errorMessage = error instanceof Error ? error.message : String(error);
141
+ throw new Error(`Failed to download ${source.name}: ${errorMessage}`);
143
142
  }
144
143
  }
145
144
 
@@ -160,7 +159,8 @@ export class SchematronDownloader {
160
159
  const path = await this.download(source);
161
160
  paths.push(path);
162
161
  } catch (error) {
163
- console.warn(`Failed to download ${source.name}: ${error.message}`);
162
+ const errorMessage = error instanceof Error ? error.message : String(error);
163
+ console.warn(`Failed to download ${source.name}: ${errorMessage}`);
164
164
  }
165
165
  }
166
166
 
@@ -209,7 +209,8 @@ export class SchematronDownloader {
209
209
  }
210
210
  }
211
211
  } catch (error) {
212
- console.warn(`Failed to list cached files: ${error.message}`);
212
+ const errorMessage = error instanceof Error ? error.message : String(error);
213
+ console.warn(`Failed to list cached files: ${errorMessage}`);
213
214
  }
214
215
 
215
216
  return files;
@@ -230,7 +231,8 @@ export class SchematronDownloader {
230
231
 
231
232
  console.log('Schematron cache cleared');
232
233
  } catch (error) {
233
- console.warn(`Failed to clear cache: ${error.message}`);
234
+ const errorMessage = error instanceof Error ? error.message : String(error);
235
+ console.warn(`Failed to clear cache: ${errorMessage}`);
234
236
  }
235
237
  }
236
238
 
@@ -296,9 +298,10 @@ export async function downloadISOSkeletons(targetDir: string = 'assets_downloade
296
298
 
297
299
  console.log(`Downloaded: ${name}`);
298
300
  } catch (error) {
299
- console.warn(`Failed to download ${name}: ${error.message}`);
301
+ const errorMessage = error instanceof Error ? error.message : String(error);
302
+ console.warn(`Failed to download ${name}: ${errorMessage}`);
300
303
  }
301
304
  }
302
305
 
303
306
  console.log('ISO Schematron skeleton download complete');
304
- }
307
+ }
@@ -61,10 +61,13 @@ export class IntegratedValidator {
61
61
  try {
62
62
  await fs.access(schematronPath);
63
63
  } catch {
64
- throw new Error(`Schematron file not found: ${schematronPath}. Run 'npm run download-schematron' first.`);
64
+ throw new Error(`Schematron file not found: ${schematronPath}. Run 'pnpm run download-schematron' first.`);
65
65
  }
66
66
 
67
67
  await this.schematronValidator.loadSchematron(schematronPath, true);
68
+ if (!this.schematronValidator.canValidate()) {
69
+ throw new Error(`Schematron file is not a precompiled Saxon-JS SEF resource: ${schematronPath}`);
70
+ }
68
71
  this.schematronLoaded = true;
69
72
  }
70
73
 
@@ -146,7 +149,8 @@ export class IntegratedValidator {
146
149
  });
147
150
  results.push(...schematronResults);
148
151
  } catch (error) {
149
- console.warn(`Schematron validation failed: ${error.message}`);
152
+ const errorMessage = error instanceof Error ? error.message : String(error);
153
+ console.warn(`Schematron validation failed: ${errorMessage}`);
150
154
  }
151
155
  }
152
156
 
@@ -224,7 +228,8 @@ export class IntegratedValidator {
224
228
  try {
225
229
  await this.loadSchematron('EN16931', format);
226
230
  } catch (error) {
227
- console.warn(`Could not load Schematron: ${error.message}`);
231
+ const errorMessage = error instanceof Error ? error.message : String(error);
232
+ console.warn(`Could not load Schematron: ${errorMessage}`);
228
233
  }
229
234
  }
230
235
 
@@ -278,8 +283,9 @@ export async function createStandardValidator(
278
283
  try {
279
284
  await validator.loadSchematron(standard, format);
280
285
  } catch (error) {
281
- console.warn(`Schematron not available for ${standard} ${format}: ${error.message}`);
286
+ const errorMessage = error instanceof Error ? error.message : String(error);
287
+ console.warn(`Schematron not available for ${standard} ${format}: ${errorMessage}`);
282
288
  }
283
289
 
284
290
  return validator;
285
- }
291
+ }
@@ -1,6 +1,6 @@
1
1
  import * as plugins from '../../plugins.js';
2
- import * as SaxonJS from 'saxon-js';
3
2
  import type { ValidationResult } from './validation.types.js';
3
+ import { loadSaxonJS, type ISaxonJSTransformOptions, type ISaxonJSTransformResult } from '../../vendor/saxonjs.js';
4
4
 
5
5
  /**
6
6
  * Schematron validation options
@@ -12,14 +12,17 @@ export interface SchematronOptions {
12
12
  maxErrors?: number; // Maximum errors before stopping
13
13
  }
14
14
 
15
+ type TSchematronSourceKind = 'none' | 'schematron' | 'sef';
16
+
15
17
  /**
16
18
  * Schematron validation engine using Saxon-JS
17
19
  * Provides official standards validation through Schematron rules
18
20
  */
19
21
  export class SchematronValidator {
20
- private compiledStylesheet: any;
21
22
  private schematronRules: string;
22
- private isCompiled: boolean = false;
23
+ private sourceKind: TSchematronSourceKind = 'none';
24
+ private stylesheetFileName?: string;
25
+ private stylesheetInternal?: unknown;
23
26
 
24
27
  constructor(schematronRules?: string) {
25
28
  this.schematronRules = schematronRules || '';
@@ -29,43 +32,25 @@ export class SchematronValidator {
29
32
  * Load Schematron rules from file or string
30
33
  */
31
34
  public async loadSchematron(source: string, isFilePath: boolean = true): Promise<void> {
35
+ let sourceContent: string;
36
+
32
37
  if (isFilePath) {
33
- // Load from file
34
- const smartfile = await import('@push.rocks/smartfile');
35
- this.schematronRules = await smartfile.SmartFile.fromFilePath(source).then(f => f.contentBuffer.toString());
38
+ sourceContent = await plugins.fs.readFile(source, 'utf-8');
36
39
  } else {
37
- // Use provided string
38
- this.schematronRules = source;
40
+ sourceContent = source;
39
41
  }
40
-
41
- // Reset compilation state
42
- this.isCompiled = false;
43
- }
44
-
45
- /**
46
- * Compile Schematron to XSLT using ISO Schematron skeleton
47
- */
48
- private async compileSchematron(): Promise<void> {
49
- if (this.isCompiled) return;
50
-
51
- // The Schematron to XSLT transformation requires the ISO Schematron skeleton
52
- // For now, we'll use a simplified approach with direct XSLT generation
53
- // In production, we would use the official ISO Schematron skeleton XSLTs
54
-
55
- try {
56
- // Convert Schematron to XSLT
57
- // This is a simplified version - in production we'd use the full ISO skeleton
58
- const xslt = this.generateXSLTFromSchematron(this.schematronRules);
59
-
60
- // Compile the XSLT with Saxon-JS
61
- this.compiledStylesheet = await SaxonJS.compile({
62
- stylesheetText: xslt,
63
- warnings: 'silent'
64
- });
65
-
66
- this.isCompiled = true;
67
- } catch (error) {
68
- throw new Error(`Failed to compile Schematron: ${error.message}`);
42
+
43
+ this.schematronRules = sourceContent;
44
+ this.sourceKind = this.detectSourceKind(source, sourceContent, isFilePath);
45
+ this.stylesheetFileName = undefined;
46
+ this.stylesheetInternal = undefined;
47
+
48
+ if (this.sourceKind === 'sef') {
49
+ if (isFilePath) {
50
+ this.stylesheetFileName = source;
51
+ } else {
52
+ this.stylesheetInternal = JSON.parse(sourceContent);
53
+ }
69
54
  }
70
55
  }
71
56
 
@@ -80,19 +65,28 @@ export class SchematronValidator {
80
65
  throw new Error('No Schematron rules loaded');
81
66
  }
82
67
 
83
- // Ensure Schematron is compiled
84
- await this.compileSchematron();
85
-
86
68
  const results: ValidationResult[] = [];
69
+
70
+ if (!this.canValidate()) {
71
+ return [this.createUnavailableResult()];
72
+ }
87
73
 
88
74
  try {
89
- // Transform the XML with the compiled Schematron XSLT
90
- const transformResult = await SaxonJS.transform({
91
- stylesheetInternal: this.compiledStylesheet,
75
+ const transformOptions: ISaxonJSTransformOptions = {
92
76
  sourceText: xmlContent,
93
77
  destination: 'serialized',
94
78
  stylesheetParams: options.parameters || {}
95
- });
79
+ };
80
+
81
+ if (this.stylesheetFileName) {
82
+ transformOptions.stylesheetFileName = this.stylesheetFileName;
83
+ } else {
84
+ transformOptions.stylesheetInternal = this.stylesheetInternal;
85
+ }
86
+
87
+ // Transform the XML with a precompiled Saxon-JS SEF stylesheet.
88
+ const SaxonJS = await loadSaxonJS();
89
+ const transformResult = await SaxonJS.transform(transformOptions, 'async') as ISaxonJSTransformResult;
96
90
 
97
91
  // Parse the SVRL (Schematron Validation Report Language) output
98
92
  results.push(...this.parseSVRL(transformResult.principalResult));
@@ -108,11 +102,12 @@ export class SchematronValidator {
108
102
 
109
103
  return results;
110
104
  } catch (error) {
105
+ const errorMessage = error instanceof Error ? error.message : String(error);
111
106
  results.push({
112
107
  ruleId: 'SCHEMATRON-ERROR',
113
108
  source: 'SCHEMATRON',
114
109
  severity: 'error',
115
- message: `Schematron validation failed: ${error.message}`,
110
+ message: `Schematron validation failed: ${errorMessage}`,
116
111
  btReference: undefined,
117
112
  bgReference: undefined
118
113
  });
@@ -190,43 +185,46 @@ export class SchematronValidator {
190
185
  };
191
186
  }
192
187
 
193
- /**
194
- * Generate simplified XSLT from Schematron
195
- * This is a placeholder - in production, use ISO Schematron skeleton
196
- */
197
- private generateXSLTFromSchematron(schematron: string): string {
198
- // This is a simplified transformation
199
- // In production, we would use the official ISO Schematron skeleton XSLTs
200
- // (iso_schematron_skeleton.xsl, iso_svrl_for_xslt2.xsl, etc.)
201
-
202
- // For now, return a basic XSLT that creates SVRL output
203
- return `<?xml version="1.0" encoding="UTF-8"?>
204
- <xsl:stylesheet version="3.0"
205
- xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
206
- xmlns:svrl="http://purl.oclc.org/dsdl/svrl">
207
-
208
- <xsl:output method="xml" indent="yes"/>
209
-
210
- <xsl:template match="/">
211
- <svrl:schematron-output>
212
- <!-- This is a placeholder transformation -->
213
- <!-- Real implementation would process Schematron patterns and rules -->
214
- <svrl:active-pattern>
215
- <xsl:attribute name="document">
216
- <xsl:value-of select="base-uri(/)"/>
217
- </xsl:attribute>
218
- </svrl:active-pattern>
219
- </svrl:schematron-output>
220
- </xsl:template>
221
- </xsl:stylesheet>`;
222
- }
223
-
224
188
  /**
225
189
  * Check if validator has rules loaded
226
190
  */
227
191
  public hasRules(): boolean {
228
192
  return !!this.schematronRules;
229
193
  }
194
+
195
+ /**
196
+ * Check if loaded rules can be executed by Saxon-JS.
197
+ */
198
+ public canValidate(): boolean {
199
+ return this.sourceKind === 'sef' && Boolean(this.stylesheetFileName || this.stylesheetInternal);
200
+ }
201
+
202
+ private detectSourceKind(source: string, sourceContent: string, isFilePath: boolean): TSchematronSourceKind {
203
+ const trimmed = sourceContent.trim();
204
+ if (!trimmed) {
205
+ return 'none';
206
+ }
207
+
208
+ if (isFilePath && /\.sef\.json$/i.test(source)) {
209
+ return 'sef';
210
+ }
211
+
212
+ if (trimmed.startsWith('{')) {
213
+ return 'sef';
214
+ }
215
+
216
+ return 'schematron';
217
+ }
218
+
219
+ private createUnavailableResult(): ValidationResult {
220
+ return {
221
+ ruleId: 'SCHEMATRON-UNCOMPILED',
222
+ source: 'SCHEMATRON',
223
+ severity: 'error',
224
+ message: 'Schematron validation requires a precompiled Saxon-JS SEF JSON stylesheet. Raw .sch files are loaded for inspection only and are not executed.',
225
+ remediation: 'Compile Schematron rules to .sef.json with Saxon tooling and load the compiled SEF resource.'
226
+ };
227
+ }
230
228
 
231
229
  /**
232
230
  * Get list of available phases from Schematron
@@ -323,7 +321,8 @@ export class HybridValidator {
323
321
  try {
324
322
  results.push(...validator.validate(xmlContent));
325
323
  } catch (error) {
326
- console.warn(`TS validator failed: ${error.message}`);
324
+ const errorMessage = error instanceof Error ? error.message : String(error);
325
+ console.warn(`TS validator failed: ${errorMessage}`);
327
326
  }
328
327
  }
329
328
 
@@ -333,7 +332,8 @@ export class HybridValidator {
333
332
  const schematronResults = await this.schematronValidator.validate(xmlContent, options);
334
333
  results.push(...schematronResults);
335
334
  } catch (error) {
336
- console.warn(`Schematron validation failed: ${error.message}`);
335
+ const errorMessage = error instanceof Error ? error.message : String(error);
336
+ console.warn(`Schematron validation failed: ${errorMessage}`);
337
337
  }
338
338
  }
339
339
 
@@ -345,4 +345,4 @@ export class HybridValidator {
345
345
  return true;
346
346
  });
347
347
  }
348
- }
348
+ }
@@ -1,8 +1,11 @@
1
1
  import { Worker } from 'worker_threads';
2
- import * as path from 'path';
3
2
  import type { ValidationResult } from './validation.types.js';
4
3
  import type { SchematronOptions } from './schematron.validator.js';
5
4
 
5
+ export interface ISchematronWorkerPoolInitializeOptions {
6
+ isFilePath?: boolean;
7
+ }
8
+
6
9
  /**
7
10
  * Worker pool for Schematron validation
8
11
  * Provides non-blocking validation in worker threads
@@ -17,7 +20,8 @@ export class SchematronWorkerPool {
17
20
  reject: (error: Error) => void;
18
21
  }> = [];
19
22
  private maxWorkers: number;
20
- private schematronRules: string = '';
23
+ private stylesheetFileName?: string;
24
+ private stylesheetInternal?: unknown;
21
25
 
22
26
  constructor(maxWorkers: number = 4) {
23
27
  this.maxWorkers = maxWorkers;
@@ -26,48 +30,81 @@ export class SchematronWorkerPool {
26
30
  /**
27
31
  * Initialize worker pool
28
32
  */
29
- public async initialize(schematronRules: string): Promise<void> {
30
- this.schematronRules = schematronRules;
33
+ public async initialize(
34
+ stylesheetSource: string,
35
+ options: ISchematronWorkerPoolInitializeOptions = {}
36
+ ): Promise<void> {
37
+ this.loadStylesheet(stylesheetSource, options);
31
38
 
32
39
  // Create workers
33
40
  for (let i = 0; i < this.maxWorkers; i++) {
34
41
  await this.createWorker();
35
42
  }
36
43
  }
44
+
45
+ private loadStylesheet(
46
+ stylesheetSource: string,
47
+ options: ISchematronWorkerPoolInitializeOptions
48
+ ): void {
49
+ const source = stylesheetSource.trim();
50
+ const isFilePath = options.isFilePath ?? /\.sef\.json$/i.test(stylesheetSource);
51
+
52
+ this.stylesheetFileName = undefined;
53
+ this.stylesheetInternal = undefined;
54
+
55
+ if (isFilePath) {
56
+ if (!/\.sef\.json$/i.test(stylesheetSource)) {
57
+ throw new Error('Schematron worker validation requires a precompiled Saxon-JS SEF JSON stylesheet file.');
58
+ }
59
+ this.stylesheetFileName = stylesheetSource;
60
+ return;
61
+ }
62
+
63
+ if (!source.startsWith('{')) {
64
+ throw new Error('Schematron worker validation requires a precompiled Saxon-JS SEF JSON stylesheet. Raw .sch files are not executed.');
65
+ }
66
+
67
+ this.stylesheetInternal = JSON.parse(source);
68
+ }
37
69
 
38
70
  /**
39
71
  * Create a new worker
40
72
  */
41
73
  private async createWorker(): Promise<void> {
42
- const workerPath = path.join(import.meta.url, 'schematron.worker.impl.js');
43
-
44
74
  const worker = new Worker(`
45
75
  const { parentPort } = require('worker_threads');
46
76
  const SaxonJS = require('saxon-js');
47
77
 
48
- let compiledStylesheet = null;
78
+ let stylesheetFileName = null;
79
+ let stylesheetInternal = null;
49
80
 
50
81
  parentPort.on('message', async (msg) => {
51
82
  try {
52
83
  if (msg.type === 'init') {
53
- // Compile Schematron to XSLT
54
- compiledStylesheet = await SaxonJS.compile({
55
- stylesheetText: msg.xslt,
56
- warnings: 'silent'
57
- });
84
+ stylesheetFileName = msg.stylesheetFileName || null;
85
+ stylesheetInternal = msg.stylesheetInternal || null;
86
+ if (!stylesheetFileName && !stylesheetInternal) {
87
+ throw new Error('Worker not initialized with a SEF stylesheet');
88
+ }
58
89
  parentPort.postMessage({ type: 'ready' });
59
90
  } else if (msg.type === 'validate') {
60
- if (!compiledStylesheet) {
91
+ if (!stylesheetFileName && !stylesheetInternal) {
61
92
  throw new Error('Worker not initialized');
62
93
  }
63
-
64
- // Transform XML with compiled Schematron
65
- const result = await SaxonJS.transform({
66
- stylesheetInternal: compiledStylesheet,
94
+
95
+ const transformOptions = {
67
96
  sourceText: msg.xmlContent,
68
97
  destination: 'serialized',
69
98
  stylesheetParams: msg.options.parameters || {}
70
- });
99
+ };
100
+
101
+ if (stylesheetFileName) {
102
+ transformOptions.stylesheetFileName = stylesheetFileName;
103
+ } else {
104
+ transformOptions.stylesheetInternal = stylesheetInternal;
105
+ }
106
+
107
+ const result = await SaxonJS.transform(transformOptions, 'async');
71
108
 
72
109
  parentPort.postMessage({
73
110
  type: 'result',
@@ -83,7 +120,7 @@ export class SchematronWorkerPool {
83
120
  });
84
121
  `, { eval: true });
85
122
 
86
- // Initialize worker with Schematron rules
123
+ // Initialize worker with a precompiled SEF stylesheet.
87
124
  await new Promise<void>((resolve, reject) => {
88
125
  worker.once('message', (msg) => {
89
126
  if (msg.type === 'ready') {
@@ -96,7 +133,8 @@ export class SchematronWorkerPool {
96
133
  // Send initialization message
97
134
  worker.postMessage({
98
135
  type: 'init',
99
- xslt: this.generateXSLTFromSchematron(this.schematronRules)
136
+ stylesheetFileName: this.stylesheetFileName,
137
+ stylesheetInternal: this.stylesheetInternal
100
138
  });
101
139
  });
102
140
 
@@ -174,26 +212,6 @@ export class SchematronWorkerPool {
174
212
  return results;
175
213
  }
176
214
 
177
- /**
178
- * Generate XSLT from Schematron (simplified)
179
- */
180
- private generateXSLTFromSchematron(schematron: string): string {
181
- // Simplified - would use ISO Schematron skeleton in production
182
- return `<?xml version="1.0" encoding="UTF-8"?>
183
- <xsl:stylesheet version="3.0"
184
- xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
185
- xmlns:svrl="http://purl.oclc.org/dsdl/svrl">
186
-
187
- <xsl:output method="xml" indent="yes"/>
188
-
189
- <xsl:template match="/">
190
- <svrl:schematron-output>
191
- <svrl:active-pattern document="{base-uri(/)}"/>
192
- </svrl:schematron-output>
193
- </xsl:template>
194
- </xsl:stylesheet>`;
195
- }
196
-
197
215
  /**
198
216
  * Terminate all workers
199
217
  */
@@ -218,4 +236,4 @@ export class SchematronWorkerPool {
218
236
  queuedTasks: this.taskQueue.length
219
237
  };
220
238
  }
221
- }
239
+ }
package/ts/index.ts CHANGED
@@ -144,11 +144,12 @@ export function validateXml(
144
144
  const validator = ValidatorFactory.createValidator(xml);
145
145
  return validator.validate(level);
146
146
  } catch (error) {
147
+ const errorMessage = error instanceof Error ? error.message : String(error);
147
148
  return {
148
149
  valid: false,
149
150
  errors: [{
150
151
  code: 'VAL-ERROR',
151
- message: `Validation error: ${error.message}`
152
+ message: `Validation error: ${errorMessage}`
152
153
  }],
153
154
  level
154
155
  };
@@ -1,5 +1,3 @@
1
- import { business, finance } from '../plugins.js';
2
-
3
1
  /**
4
2
  * Supported electronic invoice formats
5
3
  */
@@ -88,4 +86,4 @@ export type { TCreditNote } from '@tsclass/tsclass/dist_ts/finance/index.js';
88
86
  export type { TDebitNote } from '@tsclass/tsclass/dist_ts/finance/index.js';
89
87
  export type { TContact } from '@tsclass/tsclass/dist_ts/business/index.js';
90
88
  export type { TLetterEnvelope } from '@tsclass/tsclass/dist_ts/business/index.js';
91
- export type { TDocumentEnvelope } from '@tsclass/tsclass/dist_ts/business/index.js';
89
+ export type { TDocumentEnvelope } from '@tsclass/tsclass/dist_ts/business/index.js';