@dev.smartpricing/message-composer-utils 3.1.2

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 (119) hide show
  1. package/dist/types/api/aiIntegration.js +7 -0
  2. package/dist/types/api/aiIntegration.js.map +1 -0
  3. package/dist/types/api/apiMessages.js +2 -0
  4. package/dist/types/api/apiMessages.js.map +1 -0
  5. package/dist/types/api/index.js +3 -0
  6. package/dist/types/api/index.js.map +1 -0
  7. package/dist/types/app.js +23 -0
  8. package/dist/types/app.js.map +1 -0
  9. package/dist/types/brand.js +31 -0
  10. package/dist/types/brand.js.map +1 -0
  11. package/dist/types/dynamic-values.js +5 -0
  12. package/dist/types/dynamic-values.js.map +1 -0
  13. package/dist/types/emailblocks.v1.defaults.js +83 -0
  14. package/dist/types/emailblocks.v1.defaults.js.map +1 -0
  15. package/dist/types/emailblocks.v1.js +339 -0
  16. package/dist/types/emailblocks.v1.js.map +1 -0
  17. package/dist/types/index.js +16 -0
  18. package/dist/types/index.js.map +1 -0
  19. package/dist/types/kafka-events.js +2 -0
  20. package/dist/types/kafka-events.js.map +1 -0
  21. package/dist/types/language.js +2 -0
  22. package/dist/types/language.js.map +1 -0
  23. package/dist/types/media.js +6 -0
  24. package/dist/types/media.js.map +1 -0
  25. package/dist/types/message.js +41 -0
  26. package/dist/types/message.js.map +1 -0
  27. package/dist/types/meta-template.js +2 -0
  28. package/dist/types/meta-template.js.map +1 -0
  29. package/dist/types/meta-webhook.js +2 -0
  30. package/dist/types/meta-webhook.js.map +1 -0
  31. package/dist/types/submission-job.js +2 -0
  32. package/dist/types/submission-job.js.map +1 -0
  33. package/dist/types/tag.js +2 -0
  34. package/dist/types/tag.js.map +1 -0
  35. package/dist/types/utils.js +2 -0
  36. package/dist/types/utils.js.map +1 -0
  37. package/dist/types/whatsappblocks.v1.js +24 -0
  38. package/dist/types/whatsappblocks.v1.js.map +1 -0
  39. package/dist/utils/array.js +26 -0
  40. package/dist/utils/array.js.map +1 -0
  41. package/dist/utils/async.js +22 -0
  42. package/dist/utils/async.js.map +1 -0
  43. package/dist/utils/date.js +37 -0
  44. package/dist/utils/date.js.map +1 -0
  45. package/dist/utils/index.js +34 -0
  46. package/dist/utils/index.js.map +1 -0
  47. package/dist/utils/intl.js +37 -0
  48. package/dist/utils/intl.js.map +1 -0
  49. package/dist/utils/meta-status.js +21 -0
  50. package/dist/utils/meta-status.js.map +1 -0
  51. package/dist/utils/number.js +24 -0
  52. package/dist/utils/number.js.map +1 -0
  53. package/dist/utils/object.js +87 -0
  54. package/dist/utils/object.js.map +1 -0
  55. package/dist/utils/string.js +22 -0
  56. package/dist/utils/string.js.map +1 -0
  57. package/dist/utils/templates/brand-cascade.js +116 -0
  58. package/dist/utils/templates/brand-cascade.js.map +1 -0
  59. package/dist/utils/templates/email-compiler.js +126 -0
  60. package/dist/utils/templates/email-compiler.js.map +1 -0
  61. package/dist/utils/templates/email-mjml.js +444 -0
  62. package/dist/utils/templates/email-mjml.js.map +1 -0
  63. package/dist/utils/templates/email.js +580 -0
  64. package/dist/utils/templates/email.js.map +1 -0
  65. package/dist/utils/templates/html-sanitizer.js +95 -0
  66. package/dist/utils/templates/html-sanitizer.js.map +1 -0
  67. package/dist/utils/templates/messages.js +33 -0
  68. package/dist/utils/templates/messages.js.map +1 -0
  69. package/dist/utils/templates/meta.js +246 -0
  70. package/dist/utils/templates/meta.js.map +1 -0
  71. package/dist/utils/templates/tiptap.js +167 -0
  72. package/dist/utils/templates/tiptap.js.map +1 -0
  73. package/dist/utils/templates/whatsapp-compiler.js +168 -0
  74. package/dist/utils/templates/whatsapp-compiler.js.map +1 -0
  75. package/dist/utils/templates/whatsapp.js +329 -0
  76. package/dist/utils/templates/whatsapp.js.map +1 -0
  77. package/dist/utils/validations.js +39 -0
  78. package/dist/utils/validations.js.map +1 -0
  79. package/package.json +43 -0
  80. package/src/types/api/aiIntegration.ts +59 -0
  81. package/src/types/api/apiMessages.ts +22 -0
  82. package/src/types/api/index.ts +2 -0
  83. package/src/types/app.ts +76 -0
  84. package/src/types/brand.ts +80 -0
  85. package/src/types/dynamic-values.ts +47 -0
  86. package/src/types/emailblocks.v1.defaults.ts +123 -0
  87. package/src/types/emailblocks.v1.ts +655 -0
  88. package/src/types/index.ts +15 -0
  89. package/src/types/kafka-events.ts +46 -0
  90. package/src/types/language.ts +9 -0
  91. package/src/types/media.ts +26 -0
  92. package/src/types/message.ts +635 -0
  93. package/src/types/meta-template.ts +50 -0
  94. package/src/types/meta-webhook.ts +32 -0
  95. package/src/types/submission-job.ts +30 -0
  96. package/src/types/tag.ts +8 -0
  97. package/src/types/utils.ts +14 -0
  98. package/src/types/whatsappblocks.v1.ts +102 -0
  99. package/src/utils/array.ts +33 -0
  100. package/src/utils/async.ts +30 -0
  101. package/src/utils/date.ts +39 -0
  102. package/src/utils/index.ts +44 -0
  103. package/src/utils/intl.ts +51 -0
  104. package/src/utils/meta-status.ts +24 -0
  105. package/src/utils/number.ts +36 -0
  106. package/src/utils/object.ts +101 -0
  107. package/src/utils/string.ts +24 -0
  108. package/src/utils/templates/brand-cascade.ts +156 -0
  109. package/src/utils/templates/email-compiler.ts +155 -0
  110. package/src/utils/templates/email-mjml.ts +602 -0
  111. package/src/utils/templates/email.ts +713 -0
  112. package/src/utils/templates/html-sanitizer.ts +121 -0
  113. package/src/utils/templates/messages.ts +44 -0
  114. package/src/utils/templates/meta.ts +336 -0
  115. package/src/utils/templates/tiptap.ts +247 -0
  116. package/src/utils/templates/whatsapp-compiler.ts +212 -0
  117. package/src/utils/templates/whatsapp.ts +408 -0
  118. package/src/utils/validations.ts +49 -0
  119. package/tsconfig.json +10 -0
@@ -0,0 +1,156 @@
1
+ import type {
2
+ EmailMessage,
3
+ EmailGlobalSettings,
4
+ EmailBlock,
5
+ BrandDefaultEmailSettings,
6
+ BrandDefaultButtonSettings,
7
+ BrandSocials,
8
+ SocialPlatform,
9
+ } from "../../types/index.js";
10
+ import { DEFAULT_EMAIL_GLOBAL_SETTINGS } from "../../types/index.js";
11
+
12
+ /**
13
+ * Brand data needed for cascade resolution.
14
+ * This is the subset of BrandFromDb used at compile time.
15
+ */
16
+ export interface BrandCascadeData {
17
+ default_email_settings: BrandDefaultEmailSettings;
18
+ default_button_settings: BrandDefaultButtonSettings;
19
+ default_socials: BrandSocials | null;
20
+ logo_url: string | null;
21
+ }
22
+
23
+ /**
24
+ * Resolves EmailGlobalSettings by cascading:
25
+ * generalSettings → brand → hardcoded defaults
26
+ */
27
+ export function resolveEmailSettings(
28
+ settings: EmailGlobalSettings,
29
+ brand: BrandCascadeData | null,
30
+ ): Required<EmailGlobalSettings> {
31
+ const brandSettings = brand?.default_email_settings ?? {};
32
+ const brandButtons = brand?.default_button_settings ?? {};
33
+
34
+ return {
35
+ backgroundColor:
36
+ settings.backgroundColor ??
37
+ brandSettings.backgroundColor ??
38
+ DEFAULT_EMAIL_GLOBAL_SETTINGS.backgroundColor,
39
+ contentBackgroundColor:
40
+ settings.contentBackgroundColor ??
41
+ brandSettings.contentBackgroundColor ??
42
+ DEFAULT_EMAIL_GLOBAL_SETTINGS.contentBackgroundColor,
43
+ contentWidth:
44
+ settings.contentWidth ?? DEFAULT_EMAIL_GLOBAL_SETTINGS.contentWidth,
45
+ fontFamily:
46
+ settings.fontFamily ??
47
+ (brandSettings.fontFamily as typeof DEFAULT_EMAIL_GLOBAL_SETTINGS.fontFamily) ??
48
+ DEFAULT_EMAIL_GLOBAL_SETTINGS.fontFamily,
49
+ textColor:
50
+ settings.textColor ??
51
+ brandSettings.textColor ??
52
+ DEFAULT_EMAIL_GLOBAL_SETTINGS.textColor,
53
+ verticalSpacing:
54
+ settings.verticalSpacing ?? DEFAULT_EMAIL_GLOBAL_SETTINGS.verticalSpacing,
55
+ horizontalSpacing:
56
+ settings.horizontalSpacing ??
57
+ DEFAULT_EMAIL_GLOBAL_SETTINGS.horizontalSpacing,
58
+ button: {
59
+ backgroundColor: brandButtons.backgroundColor ?? "#000000",
60
+ textColor: brandButtons.textColor ?? "#FFFFFF",
61
+ },
62
+ };
63
+ }
64
+
65
+ /**
66
+ * Resolves blocks with brand cascade data.
67
+ * - Header: fills empty src with brand logo
68
+ * - Socials: fills missing URLs from brand socials
69
+ * - Buttons: fills undefined colors from brand button defaults
70
+ */
71
+ function resolveBlocks(
72
+ blocks: EmailBlock[],
73
+ brand: BrandCascadeData | null,
74
+ ): EmailBlock[] {
75
+ if (!brand) return blocks;
76
+
77
+ return blocks.map((block) => {
78
+ switch (block.type) {
79
+ case "header": {
80
+ if (!block.settings.src && brand.logo_url) {
81
+ return {
82
+ ...block,
83
+ settings: { ...block.settings, src: brand.logo_url },
84
+ };
85
+ }
86
+ return block;
87
+ }
88
+ case "socials": {
89
+ if (!brand.default_socials) return block;
90
+ const resolvedSocials = { ...block.settings.socials };
91
+ for (const platform of Object.keys(
92
+ resolvedSocials,
93
+ ) as SocialPlatform[]) {
94
+ const blockSocial = resolvedSocials[platform];
95
+ if (!blockSocial?.enabled) continue;
96
+ // Enabled + no block URL → cascade from brand
97
+ if (!blockSocial.url) {
98
+ const brandUrl = brand.default_socials[platform];
99
+ if (brandUrl) {
100
+ resolvedSocials[platform] = { ...blockSocial, url: brandUrl };
101
+ }
102
+ }
103
+ }
104
+ return {
105
+ ...block,
106
+ settings: { ...block.settings, socials: resolvedSocials },
107
+ };
108
+ }
109
+ case "button": {
110
+ const brandButton = brand.default_button_settings;
111
+ return {
112
+ ...block,
113
+ settings: {
114
+ ...block.settings,
115
+ textColor: block.settings.textColor ?? brandButton.textColor,
116
+ backgroundColor:
117
+ block.settings.backgroundColor ?? brandButton.backgroundColor,
118
+ },
119
+ };
120
+ }
121
+ case "grid": {
122
+ // Recurse into grid column blocks
123
+ return {
124
+ ...block,
125
+ settings: {
126
+ ...block.settings,
127
+ columnData: block.settings.columnData.map((col) => ({
128
+ ...col,
129
+ blocks: resolveBlocks(col.blocks, brand) as typeof col.blocks,
130
+ })),
131
+ },
132
+ };
133
+ }
134
+ default:
135
+ return block;
136
+ }
137
+ });
138
+ }
139
+
140
+ /**
141
+ * Resolves the full email message body with brand cascade.
142
+ * Returns a new body where all undefined settings and block values
143
+ * are filled from the brand, then from hardcoded defaults.
144
+ *
145
+ * The returned body can be passed directly to translateEmailToMjml.
146
+ */
147
+ export function resolveEmailBodyWithBrand(
148
+ body: EmailMessage["body"],
149
+ brand: BrandCascadeData | null,
150
+ ): EmailMessage["body"] {
151
+ return {
152
+ subject: body.subject,
153
+ blocks: resolveBlocks(body.blocks, brand),
154
+ settings: resolveEmailSettings(body.settings, brand),
155
+ };
156
+ }
@@ -0,0 +1,155 @@
1
+ import type {
2
+ EmailMessage,
3
+ EmailBlock,
4
+ GridAllowedBlock,
5
+ PostEntity,
6
+ } from "../../types/index.js";
7
+ import type { JSONContent } from "@tiptap/core";
8
+
9
+ /**
10
+ * Extract parameters from TipTap JSONContent
11
+ */
12
+ function extractParamsFromContent(content: JSONContent | undefined): string[] {
13
+ if (!content) return [];
14
+
15
+ const results: string[] = [];
16
+
17
+ // Extract from text nodes
18
+ if (content.type === "text" && content.text) {
19
+ const matches = content.text.matchAll(/\{\{([^}]+)\}\}/g);
20
+ for (const match of matches) {
21
+ results.push(match[1]);
22
+ }
23
+ }
24
+
25
+ // Extract from mentions
26
+ if (content.type === "mention" && content.attrs?.label) {
27
+ results.push(content.attrs.label);
28
+ }
29
+
30
+ // Recurse through content
31
+ if (content.content && Array.isArray(content.content)) {
32
+ for (const child of content.content) {
33
+ results.push(...extractParamsFromContent(child));
34
+ }
35
+ }
36
+
37
+ return results;
38
+ }
39
+
40
+ /**
41
+ * Extract parameters from email blocks recursively
42
+ */
43
+ function extractParamsFromBlocks(
44
+ blocks: (EmailBlock | GridAllowedBlock)[],
45
+ ): string[] {
46
+ const results: string[] = [];
47
+
48
+ for (const block of blocks) {
49
+ switch (block.type) {
50
+ case "heading":
51
+ case "paragraph":
52
+ case "discount":
53
+ case "footer":
54
+ case "unsubscribe_link":
55
+ results.push(...extractParamsFromContent(block.settings.content));
56
+ break;
57
+ case "socials":
58
+ for (const social of Object.values(block.settings.socials)) {
59
+ if (social.enabled && social.url) {
60
+ const matches = social.url.matchAll(/\{\{([^}]+)\}\}/g);
61
+ for (const match of matches) {
62
+ results.push(match[1]);
63
+ }
64
+ }
65
+ }
66
+ break;
67
+ case "button":
68
+ results.push(...extractParamsFromContent(block.settings.content));
69
+ // Extract from URL
70
+ if (block.settings.url) {
71
+ const matches = block.settings.url.matchAll(/\{\{([^}]+)\}\}/g);
72
+ for (const match of matches) {
73
+ results.push(match[1]);
74
+ }
75
+ }
76
+ break;
77
+
78
+ case "grid":
79
+ // Recurse into grid columns
80
+ for (const col of block.settings.columnData) {
81
+ results.push(...extractParamsFromBlocks(col.blocks));
82
+ }
83
+ break;
84
+ case "image":
85
+ // Extract from image href
86
+ if (block.settings.href) {
87
+ const matches = block.settings.href.matchAll(/\{\{([^}]+)\}\}/g);
88
+ for (const match of matches) {
89
+ results.push(match[1]);
90
+ }
91
+ }
92
+ break;
93
+ case "top_products":
94
+ if (block.settings.storeId) {
95
+ const matches = block.settings.storeId.matchAll(/\{\{([^}]+)\}\}/g);
96
+ for (const match of matches) {
97
+ results.push(match[1]);
98
+ }
99
+ }
100
+ break;
101
+ // Other blocks don't have parameters
102
+ default:
103
+ break;
104
+ }
105
+ }
106
+
107
+ return results;
108
+ }
109
+
110
+ /**
111
+ * Extract required parameters from email template
112
+ */
113
+ export function extractEmailParameters(
114
+ message: EmailMessage | PostEntity<EmailMessage>,
115
+ ): string[] {
116
+ const paramRegex = /\{\{([^}]+)\}\}/g;
117
+ const results: string[] = [];
118
+
119
+ // Extract from subject
120
+ if (message.body.subject) {
121
+ for (const match of message.body.subject.matchAll(paramRegex)) {
122
+ results.push(match[1]);
123
+ }
124
+ }
125
+
126
+ // Extract from preheader
127
+ if (message.metadata.preheader) {
128
+ for (const match of message.metadata.preheader.matchAll(paramRegex)) {
129
+ results.push(match[1]);
130
+ }
131
+ }
132
+
133
+ // Extract from blocks recursively
134
+ results.push(...extractParamsFromBlocks(message.body.blocks));
135
+
136
+ // Dedupe using Set
137
+ return [...new Set(results)];
138
+ }
139
+
140
+ /**
141
+ * Validate email parameters
142
+ */
143
+ export function validateEmailParameters(
144
+ message: EmailMessage,
145
+ parameters: Record<string, unknown>,
146
+ ): { valid: boolean; missing: string[] } {
147
+ const required = extractEmailParameters(message);
148
+ const provided = Object.keys(parameters);
149
+ const missing = required.filter((p) => !provided.includes(p));
150
+
151
+ return {
152
+ valid: missing.length === 0,
153
+ missing,
154
+ };
155
+ }