email-editor-core 0.0.4

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 (47) hide show
  1. package/README.md +438 -0
  2. package/dist/index.d.ts +127 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +260 -0
  5. package/dist/renderer/blocks/button.d.ts +18 -0
  6. package/dist/renderer/blocks/button.d.ts.map +1 -0
  7. package/dist/renderer/blocks/button.js +57 -0
  8. package/dist/renderer/blocks/divider.d.ts +18 -0
  9. package/dist/renderer/blocks/divider.d.ts.map +1 -0
  10. package/dist/renderer/blocks/divider.js +42 -0
  11. package/dist/renderer/blocks/highlight.d.ts +18 -0
  12. package/dist/renderer/blocks/highlight.d.ts.map +1 -0
  13. package/dist/renderer/blocks/highlight.js +49 -0
  14. package/dist/renderer/blocks/image.d.ts +18 -0
  15. package/dist/renderer/blocks/image.d.ts.map +1 -0
  16. package/dist/renderer/blocks/image.js +59 -0
  17. package/dist/renderer/blocks/paragraph.d.ts +18 -0
  18. package/dist/renderer/blocks/paragraph.d.ts.map +1 -0
  19. package/dist/renderer/blocks/paragraph.js +41 -0
  20. package/dist/renderer/blocks/title.d.ts +18 -0
  21. package/dist/renderer/blocks/title.d.ts.map +1 -0
  22. package/dist/renderer/blocks/title.js +49 -0
  23. package/dist/renderer/parseInlineFormatting.d.ts +14 -0
  24. package/dist/renderer/parseInlineFormatting.d.ts.map +1 -0
  25. package/dist/renderer/parseInlineFormatting.js +178 -0
  26. package/dist/renderer/renderBlock.d.ts +21 -0
  27. package/dist/renderer/renderBlock.d.ts.map +1 -0
  28. package/dist/renderer/renderBlock.js +44 -0
  29. package/dist/renderer/renderEmail.d.ts +26 -0
  30. package/dist/renderer/renderEmail.d.ts.map +1 -0
  31. package/dist/renderer/renderEmail.js +275 -0
  32. package/dist/sanitizer.d.ts +147 -0
  33. package/dist/sanitizer.d.ts.map +1 -0
  34. package/dist/sanitizer.js +533 -0
  35. package/dist/template-config.d.ts +38 -0
  36. package/dist/template-config.d.ts.map +1 -0
  37. package/dist/template-config.js +196 -0
  38. package/dist/test-formatting.d.ts +6 -0
  39. package/dist/test-formatting.d.ts.map +1 -0
  40. package/dist/test-formatting.js +132 -0
  41. package/dist/types.d.ts +243 -0
  42. package/dist/types.d.ts.map +1 -0
  43. package/dist/types.js +5 -0
  44. package/dist/validator.d.ts +86 -0
  45. package/dist/validator.d.ts.map +1 -0
  46. package/dist/validator.js +435 -0
  47. package/package.json +17 -0
@@ -0,0 +1,86 @@
1
+ /**
2
+ * VALIDATION STRATEGY AND IMPLEMENTATION
3
+ * Enforces data integrity and template compliance
4
+ */
5
+ import type { EmailDocument, ValidationError, ValidationContext } from './types.js';
6
+ /**
7
+ * Validation rule interface
8
+ * Each rule checks a specific constraint
9
+ */
10
+ interface ValidationRule {
11
+ name: string;
12
+ description: string;
13
+ validate: (context: ValidationContext) => ValidationError[];
14
+ }
15
+ /**
16
+ * RULE 1: Block type availability
17
+ * Ensures only allowed block types for this template are used
18
+ */
19
+ export declare const blockTypeAllowedRule: ValidationRule;
20
+ /**
21
+ * RULE 2: Block count constraints
22
+ * Enforces min/max count per block type
23
+ */
24
+ export declare const blockCountConstraintRule: ValidationRule;
25
+ /**
26
+ * RULE 3: Total block count
27
+ * Enforces maximum total blocks in body
28
+ */
29
+ export declare const totalBlockCountRule: ValidationRule;
30
+ /**
31
+ * RULE 4: Mandatory blocks
32
+ * Ensures all required block types are present
33
+ */
34
+ export declare const mandatoryBlocksRule: ValidationRule;
35
+ /**
36
+ * RULE 5: Block order consistency
37
+ * If reordering is disabled, checks that blocks follow required order
38
+ */
39
+ export declare const blockOrderRule: ValidationRule;
40
+ /**
41
+ * RULE 6: Fixed sections presence
42
+ * Ensures help and compliance sections are present if required
43
+ */
44
+ export declare const fixedSectionsRule: ValidationRule;
45
+ /**
46
+ * RULE 7: Block IDs uniqueness
47
+ * Ensures all block IDs are unique
48
+ */
49
+ export declare const blockIdUniquenessRule: ValidationRule;
50
+ /**
51
+ * RULE 8: Block content validation
52
+ * Type-specific content rules
53
+ */
54
+ export declare const blockContentValidationRule: ValidationRule;
55
+ /**
56
+ * RULE 9: Color format validation
57
+ * Ensures all color values are valid hex colors
58
+ */
59
+ export declare const colorFormatRule: ValidationRule;
60
+ /**
61
+ * MAIN VALIDATION FUNCTION
62
+ *
63
+ * @param email - The email document to validate
64
+ * @param strict - If true, warnings become errors
65
+ * @returns ValidationError array
66
+ */
67
+ export declare function validateEmailDocument(email: EmailDocument, strict?: boolean): ValidationError[];
68
+ /**
69
+ * Check if email document is valid
70
+ * @returns true if no errors, false otherwise
71
+ */
72
+ export declare function isEmailDocumentValid(email: EmailDocument, strict?: boolean): boolean;
73
+ /**
74
+ * Get validation summary
75
+ * Useful for reporting
76
+ */
77
+ export interface ValidationSummary {
78
+ isValid: boolean;
79
+ errorCount: number;
80
+ warningCount: number;
81
+ errors: ValidationError[];
82
+ warnings: ValidationError[];
83
+ }
84
+ export declare function getValidationSummary(email: EmailDocument, strict?: boolean): ValidationSummary;
85
+ export {};
86
+ //# sourceMappingURL=validator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../src/validator.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAGV,aAAa,EACb,eAAe,EACf,iBAAiB,EAElB,MAAM,YAAY,CAAC;AAOpB;;;GAGG;AACH,UAAU,cAAc;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,eAAe,EAAE,CAAC;CAC7D;AAMD;;;GAGG;AACH,eAAO,MAAM,oBAAoB,EAAE,cAqBlC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,wBAAwB,EAAE,cA0CtC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,mBAAmB,EAAE,cAiBjC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,mBAAmB,EAAE,cAsBjC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,cAAc,EAAE,cA0C5B,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,iBAAiB,EAAE,cAyB/B,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,qBAAqB,EAAE,cAsBnC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,0BAA0B,EAAE,cA0HxC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,eAAe,EAAE,cA+C7B,CAAC;AAqBF;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,aAAa,EACpB,MAAM,GAAE,OAAe,GACtB,eAAe,EAAE,CAiBnB;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,aAAa,EACpB,MAAM,GAAE,OAAe,GACtB,OAAO,CAGT;AAED;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,QAAQ,EAAE,eAAe,EAAE,CAAC;CAC7B;AAED,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,aAAa,EACpB,MAAM,GAAE,OAAe,GACtB,iBAAiB,CAanB"}
@@ -0,0 +1,435 @@
1
+ /**
2
+ * VALIDATION STRATEGY AND IMPLEMENTATION
3
+ * Enforces data integrity and template compliance
4
+ */
5
+ import { getTemplateConfig } from './template-config.js';
6
+ // ============================================================================
7
+ // CORE VALIDATORS
8
+ // ============================================================================
9
+ /**
10
+ * RULE 1: Block type availability
11
+ * Ensures only allowed block types for this template are used
12
+ */
13
+ export const blockTypeAllowedRule = {
14
+ name: 'BLOCK_TYPE_ALLOWED',
15
+ description: 'Block type is allowed for this template',
16
+ validate: (context) => {
17
+ const { email, templateConfig } = context;
18
+ const errors = [];
19
+ email.body.blocks.forEach((block) => {
20
+ if (!templateConfig.allowedBlockTypes.includes(block.type)) {
21
+ errors.push({
22
+ code: 'BLOCK_TYPE_NOT_ALLOWED',
23
+ message: `Block type "${block.type}" is not allowed in ${templateConfig.templateType} template`,
24
+ blockId: block.id,
25
+ blockType: block.type,
26
+ severity: 'error',
27
+ });
28
+ }
29
+ });
30
+ return errors;
31
+ },
32
+ };
33
+ /**
34
+ * RULE 2: Block count constraints
35
+ * Enforces min/max count per block type
36
+ */
37
+ export const blockCountConstraintRule = {
38
+ name: 'BLOCK_COUNT_CONSTRAINT',
39
+ description: 'Block count respects per-type min/max constraints',
40
+ validate: (context) => {
41
+ const { email, templateConfig } = context;
42
+ const errors = [];
43
+ // Count blocks by type
44
+ const blockCounts = new Map();
45
+ email.body.blocks.forEach((block) => {
46
+ blockCounts.set(block.type, (blockCounts.get(block.type) ?? 0) + 1);
47
+ });
48
+ // Check each constraint
49
+ Object.entries(templateConfig.blockConstraints).forEach(([blockType, constraint]) => {
50
+ if (!constraint)
51
+ return;
52
+ const count = blockCounts.get(blockType) ?? 0;
53
+ // Check minimum
54
+ if (count < constraint.min) {
55
+ errors.push({
56
+ code: 'MIN_BLOCKS_NOT_MET',
57
+ message: `Minimum ${constraint.min} ${blockType} block(s) required. Found: ${count}`,
58
+ blockType: blockType,
59
+ severity: constraint.required ? 'error' : 'warning',
60
+ });
61
+ }
62
+ // Check maximum
63
+ if (count > constraint.max) {
64
+ errors.push({
65
+ code: 'MAX_BLOCKS_EXCEEDED',
66
+ message: `Maximum ${constraint.max} ${blockType} block(s) allowed. Found: ${count}`,
67
+ blockType: blockType,
68
+ severity: 'error',
69
+ });
70
+ }
71
+ });
72
+ return errors;
73
+ },
74
+ };
75
+ /**
76
+ * RULE 3: Total block count
77
+ * Enforces maximum total blocks in body
78
+ */
79
+ export const totalBlockCountRule = {
80
+ name: 'TOTAL_BLOCK_COUNT',
81
+ description: 'Total block count does not exceed template maximum',
82
+ validate: (context) => {
83
+ const { email, templateConfig } = context;
84
+ const errors = [];
85
+ if (email.body.blocks.length > templateConfig.maxTotalBlocks) {
86
+ errors.push({
87
+ code: 'MAX_TOTAL_BLOCKS_EXCEEDED',
88
+ message: `Maximum ${templateConfig.maxTotalBlocks} total blocks allowed. Found: ${email.body.blocks.length}`,
89
+ severity: 'error',
90
+ });
91
+ }
92
+ return errors;
93
+ },
94
+ };
95
+ /**
96
+ * RULE 4: Mandatory blocks
97
+ * Ensures all required block types are present
98
+ */
99
+ export const mandatoryBlocksRule = {
100
+ name: 'MANDATORY_BLOCKS',
101
+ description: 'All mandatory block types are present',
102
+ validate: (context) => {
103
+ const { email, templateConfig } = context;
104
+ const errors = [];
105
+ const presentBlockTypes = new Set(email.body.blocks.map((b) => b.type));
106
+ templateConfig.mandatoryBlocks.forEach((blockType) => {
107
+ if (!presentBlockTypes.has(blockType)) {
108
+ errors.push({
109
+ code: 'MANDATORY_BLOCK_MISSING',
110
+ message: `Mandatory block type "${blockType}" is missing`,
111
+ blockType,
112
+ severity: 'error',
113
+ });
114
+ }
115
+ });
116
+ return errors;
117
+ },
118
+ };
119
+ /**
120
+ * RULE 5: Block order consistency
121
+ * If reordering is disabled, checks that blocks follow required order
122
+ */
123
+ export const blockOrderRule = {
124
+ name: 'BLOCK_ORDER',
125
+ description: 'Blocks are in the recommended order',
126
+ validate: (context) => {
127
+ const { email, templateConfig, strict } = context;
128
+ const errors = [];
129
+ // Skip if reordering is allowed or no order requirement
130
+ if (templateConfig.allowReordering || !templateConfig.requireBlockOrder) {
131
+ return errors;
132
+ }
133
+ // Check if blocks follow required order
134
+ let lastOrderIndex = -1;
135
+ for (const block of email.body.blocks) {
136
+ const requireOrder = templateConfig.requireBlockOrder;
137
+ let blockOrderIndex = -1;
138
+ for (let i = 0; i < requireOrder.length; i++) {
139
+ if (requireOrder[i] === 'any' || requireOrder[i] === block.type) {
140
+ blockOrderIndex = i;
141
+ break;
142
+ }
143
+ }
144
+ if (blockOrderIndex < lastOrderIndex) {
145
+ errors.push({
146
+ code: 'BLOCK_ORDER_VIOLATION',
147
+ message: `Block "${block.type}" appears out of order. Expected order: ${requireOrder.join(' → ')}`,
148
+ blockId: block.id,
149
+ blockType: block.type,
150
+ severity: strict ? 'error' : 'warning',
151
+ });
152
+ }
153
+ if (blockOrderIndex >= 0) {
154
+ lastOrderIndex = blockOrderIndex;
155
+ }
156
+ }
157
+ return errors;
158
+ },
159
+ };
160
+ /**
161
+ * RULE 6: Fixed sections presence
162
+ * Ensures help and compliance sections are present if required
163
+ */
164
+ export const fixedSectionsRule = {
165
+ name: 'FIXED_SECTIONS',
166
+ description: 'Required fixed sections are present',
167
+ validate: (context) => {
168
+ const { email, templateConfig } = context;
169
+ const errors = [];
170
+ if (templateConfig.helpSectionRequired && !email.helpSection) {
171
+ errors.push({
172
+ code: 'HELP_SECTION_MISSING',
173
+ message: 'Help section is required for this template',
174
+ severity: 'error',
175
+ });
176
+ }
177
+ if (templateConfig.complianceSectionRequired && !email.complianceSection) {
178
+ errors.push({
179
+ code: 'COMPLIANCE_SECTION_MISSING',
180
+ message: 'Compliance section is required for this template',
181
+ severity: 'error',
182
+ });
183
+ }
184
+ return errors;
185
+ },
186
+ };
187
+ /**
188
+ * RULE 7: Block IDs uniqueness
189
+ * Ensures all block IDs are unique
190
+ */
191
+ export const blockIdUniquenessRule = {
192
+ name: 'BLOCK_ID_UNIQUENESS',
193
+ description: 'All block IDs are unique',
194
+ validate: (context) => {
195
+ const { email } = context;
196
+ const errors = [];
197
+ const idSet = new Set();
198
+ email.body.blocks.forEach((block) => {
199
+ if (idSet.has(block.id)) {
200
+ errors.push({
201
+ code: 'DUPLICATE_BLOCK_ID',
202
+ message: `Duplicate block ID: ${block.id}`,
203
+ blockId: block.id,
204
+ severity: 'error',
205
+ });
206
+ }
207
+ idSet.add(block.id);
208
+ });
209
+ return errors;
210
+ },
211
+ };
212
+ /**
213
+ * RULE 8: Block content validation
214
+ * Type-specific content rules
215
+ */
216
+ export const blockContentValidationRule = {
217
+ name: 'BLOCK_CONTENT',
218
+ description: 'Block content is valid per block type',
219
+ validate: (context) => {
220
+ const { email } = context;
221
+ const errors = [];
222
+ email.body.blocks.forEach((block) => {
223
+ // Title blocks
224
+ if (block.type === 'title') {
225
+ if (!block.content || block.content.trim().length === 0) {
226
+ errors.push({
227
+ code: 'EMPTY_TITLE',
228
+ message: 'Title block cannot be empty',
229
+ blockId: block.id,
230
+ blockType: 'title',
231
+ severity: 'error',
232
+ });
233
+ }
234
+ }
235
+ // Paragraph blocks
236
+ if (block.type === 'paragraph') {
237
+ if (!block.content || block.content.trim().length === 0) {
238
+ errors.push({
239
+ code: 'EMPTY_PARAGRAPH',
240
+ message: 'Paragraph block cannot be empty',
241
+ blockId: block.id,
242
+ blockType: 'paragraph',
243
+ severity: 'error',
244
+ });
245
+ }
246
+ }
247
+ // Image blocks
248
+ if (block.type === 'image') {
249
+ if (!block.src || !block.src.trim()) {
250
+ errors.push({
251
+ code: 'MISSING_IMAGE_SRC',
252
+ message: 'Image block must have a source URL',
253
+ blockId: block.id,
254
+ blockType: 'image',
255
+ severity: 'error',
256
+ });
257
+ }
258
+ else if (!block.src.startsWith('https://')) {
259
+ errors.push({
260
+ code: 'INVALID_IMAGE_PROTOCOL',
261
+ message: 'Image URL must use HTTPS protocol',
262
+ blockId: block.id,
263
+ blockType: 'image',
264
+ severity: 'error',
265
+ });
266
+ }
267
+ if (!block.alt || block.alt.trim().length === 0) {
268
+ errors.push({
269
+ code: 'MISSING_IMAGE_ALT',
270
+ message: 'Image block must have alt text for accessibility',
271
+ blockId: block.id,
272
+ blockType: 'image',
273
+ severity: 'error',
274
+ });
275
+ }
276
+ }
277
+ // Button blocks
278
+ if (block.type === 'button') {
279
+ if (!block.label || block.label.trim().length === 0) {
280
+ errors.push({
281
+ code: 'EMPTY_BUTTON_LABEL',
282
+ message: 'Button block must have a label',
283
+ blockId: block.id,
284
+ blockType: 'button',
285
+ severity: 'error',
286
+ });
287
+ }
288
+ if (!block.href || !block.href.trim()) {
289
+ errors.push({
290
+ code: 'MISSING_BUTTON_HREF',
291
+ message: 'Button block must have a URL',
292
+ blockId: block.id,
293
+ blockType: 'button',
294
+ severity: 'error',
295
+ });
296
+ }
297
+ else if (!block.href.startsWith('https://') && !block.href.startsWith('http://')) {
298
+ errors.push({
299
+ code: 'INVALID_BUTTON_PROTOCOL',
300
+ message: 'Button URL must use HTTP or HTTPS protocol',
301
+ blockId: block.id,
302
+ blockType: 'button',
303
+ severity: 'error',
304
+ });
305
+ }
306
+ }
307
+ // Highlight box blocks
308
+ if (block.type === 'highlight-box') {
309
+ if (!block.content || block.content.trim().length === 0) {
310
+ errors.push({
311
+ code: 'EMPTY_HIGHLIGHT_BOX',
312
+ message: 'Highlight box cannot be empty',
313
+ blockId: block.id,
314
+ blockType: 'highlight-box',
315
+ severity: 'error',
316
+ });
317
+ }
318
+ if (!block.backgroundColor) {
319
+ errors.push({
320
+ code: 'MISSING_HIGHLIGHT_COLOR',
321
+ message: 'Highlight box must have a background color',
322
+ blockId: block.id,
323
+ blockType: 'highlight-box',
324
+ severity: 'warning',
325
+ });
326
+ }
327
+ }
328
+ });
329
+ return errors;
330
+ },
331
+ };
332
+ /**
333
+ * RULE 9: Color format validation
334
+ * Ensures all color values are valid hex colors
335
+ */
336
+ export const colorFormatRule = {
337
+ name: 'COLOR_FORMAT',
338
+ description: 'All color values are valid hex colors',
339
+ validate: (context) => {
340
+ const { email } = context;
341
+ const errors = [];
342
+ const hexColorRegex = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/;
343
+ email.body.blocks.forEach((block) => {
344
+ if ('color' in block && block.color && !hexColorRegex.test(block.color)) {
345
+ errors.push({
346
+ code: 'INVALID_COLOR_FORMAT',
347
+ message: `Invalid color format: ${block.color}. Use hex format like #FF5733`,
348
+ blockId: block.id,
349
+ blockType: block.type,
350
+ severity: 'error',
351
+ });
352
+ }
353
+ if ('backgroundColor' in block &&
354
+ block.backgroundColor &&
355
+ !hexColorRegex.test(block.backgroundColor)) {
356
+ errors.push({
357
+ code: 'INVALID_COLOR_FORMAT',
358
+ message: `Invalid background color format: ${block.backgroundColor}. Use hex format like #FF5733`,
359
+ blockId: block.id,
360
+ blockType: block.type,
361
+ severity: 'error',
362
+ });
363
+ }
364
+ if ('borderColor' in block && block.borderColor && !hexColorRegex.test(block.borderColor)) {
365
+ errors.push({
366
+ code: 'INVALID_COLOR_FORMAT',
367
+ message: `Invalid border color format: ${block.borderColor}. Use hex format like #FF5733`,
368
+ blockId: block.id,
369
+ blockType: block.type,
370
+ severity: 'error',
371
+ });
372
+ }
373
+ });
374
+ return errors;
375
+ },
376
+ };
377
+ // ============================================================================
378
+ // VALIDATION ENGINE
379
+ // ============================================================================
380
+ /**
381
+ * All validation rules in execution order
382
+ */
383
+ const VALIDATION_RULES = [
384
+ blockTypeAllowedRule,
385
+ blockCountConstraintRule,
386
+ totalBlockCountRule,
387
+ mandatoryBlocksRule,
388
+ blockIdUniquenessRule,
389
+ blockContentValidationRule,
390
+ colorFormatRule,
391
+ blockOrderRule, // Order check is last since other errors might matter more
392
+ fixedSectionsRule,
393
+ ];
394
+ /**
395
+ * MAIN VALIDATION FUNCTION
396
+ *
397
+ * @param email - The email document to validate
398
+ * @param strict - If true, warnings become errors
399
+ * @returns ValidationError array
400
+ */
401
+ export function validateEmailDocument(email, strict = false) {
402
+ const templateConfig = getTemplateConfig(email.templateType);
403
+ const context = {
404
+ templateConfig,
405
+ email,
406
+ strict,
407
+ };
408
+ // Run all validation rules
409
+ const allErrors = VALIDATION_RULES.flatMap((rule) => rule.validate(context));
410
+ // Filter if not strict mode
411
+ if (!strict) {
412
+ return allErrors.filter((err) => err.severity === 'error');
413
+ }
414
+ return allErrors;
415
+ }
416
+ /**
417
+ * Check if email document is valid
418
+ * @returns true if no errors, false otherwise
419
+ */
420
+ export function isEmailDocumentValid(email, strict = false) {
421
+ const errors = validateEmailDocument(email, strict);
422
+ return errors.length === 0;
423
+ }
424
+ export function getValidationSummary(email, strict = false) {
425
+ const allErrors = validateEmailDocument(email, true); // Always get both errors and warnings
426
+ const errors = allErrors.filter((e) => e.severity === 'error');
427
+ const warnings = allErrors.filter((e) => e.severity === 'warning');
428
+ return {
429
+ isValid: errors.length === 0,
430
+ errorCount: errors.length,
431
+ warningCount: strict ? warnings.length : 0,
432
+ errors,
433
+ warnings,
434
+ };
435
+ }
package/package.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "email-editor-core",
3
+ "version": "0.0.4",
4
+ "type": "module",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./dist/index.js",
10
+ "types": "./dist/index.d.ts"
11
+ }
12
+ },
13
+ "scripts": {
14
+ "build": "tsc"
15
+ },
16
+ "files": ["dist"]
17
+ }