@endora-commerce/mod-transactional-emails 0.100.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +63 -0
  3. package/dist/admin/api/transactional-emails-client.d.ts +43 -0
  4. package/dist/admin/api/transactional-emails-client.d.ts.map +1 -0
  5. package/dist/admin/api/transactional-emails-client.js +47 -0
  6. package/dist/admin/api/transactional-emails-client.js.map +1 -0
  7. package/dist/admin/components/BrandingPanel.d.ts +10 -0
  8. package/dist/admin/components/BrandingPanel.d.ts.map +1 -0
  9. package/dist/admin/components/BrandingPanel.js +58 -0
  10. package/dist/admin/components/BrandingPanel.js.map +1 -0
  11. package/dist/admin/components/EmailEditorPane.d.ts +7 -0
  12. package/dist/admin/components/EmailEditorPane.d.ts.map +1 -0
  13. package/dist/admin/components/EmailEditorPane.js +48 -0
  14. package/dist/admin/components/EmailEditorPane.js.map +1 -0
  15. package/dist/admin/index.d.ts +46 -0
  16. package/dist/admin/index.d.ts.map +1 -0
  17. package/dist/admin/index.js +92 -0
  18. package/dist/admin/index.js.map +1 -0
  19. package/dist/admin/pages/EmailBlockEditorPage.d.ts +16 -0
  20. package/dist/admin/pages/EmailBlockEditorPage.d.ts.map +1 -0
  21. package/dist/admin/pages/EmailBlockEditorPage.js +19 -0
  22. package/dist/admin/pages/EmailBlockEditorPage.js.map +1 -0
  23. package/dist/admin/pages/EmailBlocksPage.d.ts +8 -0
  24. package/dist/admin/pages/EmailBlocksPage.d.ts.map +1 -0
  25. package/dist/admin/pages/EmailBlocksPage.js +55 -0
  26. package/dist/admin/pages/EmailBlocksPage.js.map +1 -0
  27. package/dist/admin/pages/EmailEditor.d.ts +8 -0
  28. package/dist/admin/pages/EmailEditor.d.ts.map +1 -0
  29. package/dist/admin/pages/EmailEditor.js +126 -0
  30. package/dist/admin/pages/EmailEditor.js.map +1 -0
  31. package/dist/admin/pages/EmailFragmentEditor.d.ts +9 -0
  32. package/dist/admin/pages/EmailFragmentEditor.d.ts.map +1 -0
  33. package/dist/admin/pages/EmailFragmentEditor.js +93 -0
  34. package/dist/admin/pages/EmailFragmentEditor.js.map +1 -0
  35. package/dist/admin/pages/EmailTemplateEditorPage.d.ts +12 -0
  36. package/dist/admin/pages/EmailTemplateEditorPage.d.ts.map +1 -0
  37. package/dist/admin/pages/EmailTemplateEditorPage.js +15 -0
  38. package/dist/admin/pages/EmailTemplateEditorPage.js.map +1 -0
  39. package/dist/admin/pages/EmailTemplatesPage.d.ts +8 -0
  40. package/dist/admin/pages/EmailTemplatesPage.d.ts.map +1 -0
  41. package/dist/admin/pages/EmailTemplatesPage.js +55 -0
  42. package/dist/admin/pages/EmailTemplatesPage.js.map +1 -0
  43. package/dist/admin/pages/EmailsList.d.ts +8 -0
  44. package/dist/admin/pages/EmailsList.d.ts.map +1 -0
  45. package/dist/admin/pages/EmailsList.js +85 -0
  46. package/dist/admin/pages/EmailsList.js.map +1 -0
  47. package/dist/backend/commands/email-activation.commands.d.ts +42 -0
  48. package/dist/backend/commands/email-activation.commands.d.ts.map +1 -0
  49. package/dist/backend/commands/email-activation.commands.js +51 -0
  50. package/dist/backend/commands/email-activation.commands.js.map +1 -0
  51. package/dist/backend/entities/email-block-sales-channel.entity.d.ts +11 -0
  52. package/dist/backend/entities/email-block-sales-channel.entity.d.ts.map +1 -0
  53. package/dist/backend/entities/email-block-sales-channel.entity.js +39 -0
  54. package/dist/backend/entities/email-block-sales-channel.entity.js.map +1 -0
  55. package/dist/backend/entities/email-block.entity.d.ts +24 -0
  56. package/dist/backend/entities/email-block.entity.d.ts.map +1 -0
  57. package/dist/backend/entities/email-block.entity.js +85 -0
  58. package/dist/backend/entities/email-block.entity.js.map +1 -0
  59. package/dist/backend/entities/email-template-sales-channel.entity.d.ts +10 -0
  60. package/dist/backend/entities/email-template-sales-channel.entity.d.ts.map +1 -0
  61. package/dist/backend/entities/email-template-sales-channel.entity.js +38 -0
  62. package/dist/backend/entities/email-template-sales-channel.entity.js.map +1 -0
  63. package/dist/backend/entities/email-template.entity.d.ts +19 -0
  64. package/dist/backend/entities/email-template.entity.d.ts.map +1 -0
  65. package/dist/backend/entities/email-template.entity.js +76 -0
  66. package/dist/backend/entities/email-template.entity.js.map +1 -0
  67. package/dist/backend/entities/transactional-email-content.entity.d.ts +25 -0
  68. package/dist/backend/entities/transactional-email-content.entity.d.ts.map +1 -0
  69. package/dist/backend/entities/transactional-email-content.entity.js +77 -0
  70. package/dist/backend/entities/transactional-email-content.entity.js.map +1 -0
  71. package/dist/backend/entities/transactional-email.entity.d.ts +32 -0
  72. package/dist/backend/entities/transactional-email.entity.d.ts.map +1 -0
  73. package/dist/backend/entities/transactional-email.entity.js +100 -0
  74. package/dist/backend/entities/transactional-email.entity.js.map +1 -0
  75. package/dist/backend/index.d.ts +105 -0
  76. package/dist/backend/index.d.ts.map +1 -0
  77. package/dist/backend/index.js +126 -0
  78. package/dist/backend/index.js.map +1 -0
  79. package/dist/backend/plugin.d.ts +49 -0
  80. package/dist/backend/plugin.d.ts.map +1 -0
  81. package/dist/backend/plugin.js +54 -0
  82. package/dist/backend/plugin.js.map +1 -0
  83. package/dist/backend/routes.admin.d.ts +23 -0
  84. package/dist/backend/routes.admin.d.ts.map +1 -0
  85. package/dist/backend/routes.admin.js +133 -0
  86. package/dist/backend/routes.admin.js.map +1 -0
  87. package/dist/backend/services/branding.service.d.ts +63 -0
  88. package/dist/backend/services/branding.service.d.ts.map +1 -0
  89. package/dist/backend/services/branding.service.js +119 -0
  90. package/dist/backend/services/branding.service.js.map +1 -0
  91. package/dist/backend/services/content-resolver.d.ts +28 -0
  92. package/dist/backend/services/content-resolver.d.ts.map +1 -0
  93. package/dist/backend/services/content-resolver.js +64 -0
  94. package/dist/backend/services/content-resolver.js.map +1 -0
  95. package/dist/backend/services/email-block.service.d.ts +25 -0
  96. package/dist/backend/services/email-block.service.d.ts.map +1 -0
  97. package/dist/backend/services/email-block.service.js +162 -0
  98. package/dist/backend/services/email-block.service.js.map +1 -0
  99. package/dist/backend/services/email-builder-registry.d.ts +9 -0
  100. package/dist/backend/services/email-builder-registry.d.ts.map +1 -0
  101. package/dist/backend/services/email-builder-registry.js +14 -0
  102. package/dist/backend/services/email-builder-registry.js.map +1 -0
  103. package/dist/backend/services/email-defaults-registry.d.ts +63 -0
  104. package/dist/backend/services/email-defaults-registry.d.ts.map +1 -0
  105. package/dist/backend/services/email-defaults-registry.js +60 -0
  106. package/dist/backend/services/email-defaults-registry.js.map +1 -0
  107. package/dist/backend/services/email-template.service.d.ts +23 -0
  108. package/dist/backend/services/email-template.service.d.ts.map +1 -0
  109. package/dist/backend/services/email-template.service.js +155 -0
  110. package/dist/backend/services/email-template.service.js.map +1 -0
  111. package/dist/backend/services/embed-resolver.d.ts +21 -0
  112. package/dist/backend/services/embed-resolver.d.ts.map +1 -0
  113. package/dist/backend/services/embed-resolver.js +68 -0
  114. package/dist/backend/services/embed-resolver.js.map +1 -0
  115. package/dist/backend/services/manifest-reconciler.d.ts +28 -0
  116. package/dist/backend/services/manifest-reconciler.d.ts.map +1 -0
  117. package/dist/backend/services/manifest-reconciler.js +98 -0
  118. package/dist/backend/services/manifest-reconciler.js.map +1 -0
  119. package/dist/backend/services/template-email.d.ts +26 -0
  120. package/dist/backend/services/template-email.d.ts.map +1 -0
  121. package/dist/backend/services/template-email.js +69 -0
  122. package/dist/backend/services/template-email.js.map +1 -0
  123. package/dist/backend/services/transactional-email.service.d.ts +138 -0
  124. package/dist/backend/services/transactional-email.service.d.ts.map +1 -0
  125. package/dist/backend/services/transactional-email.service.js +410 -0
  126. package/dist/backend/services/transactional-email.service.js.map +1 -0
  127. package/dist/manifest.d.ts +195 -0
  128. package/dist/manifest.d.ts.map +1 -0
  129. package/dist/manifest.js +367 -0
  130. package/dist/manifest.js.map +1 -0
  131. package/dist/migrations/20260629T113442_transactional_emails_init.d.ts +6 -0
  132. package/dist/migrations/20260629T113442_transactional_emails_init.d.ts.map +1 -0
  133. package/dist/migrations/20260629T113442_transactional_emails_init.js +133 -0
  134. package/dist/migrations/20260629T113442_transactional_emails_init.js.map +1 -0
  135. package/dist/migrations/20260801T111001_transactional_emails_email_defaults_reseed.d.ts +6 -0
  136. package/dist/migrations/20260801T111001_transactional_emails_email_defaults_reseed.d.ts.map +1 -0
  137. package/dist/migrations/20260801T111001_transactional_emails_email_defaults_reseed.js +49 -0
  138. package/dist/migrations/20260801T111001_transactional_emails_email_defaults_reseed.js.map +1 -0
  139. package/dist/migrations/20260903T101748_transactional_emails_namespace_block_names.d.ts +6 -0
  140. package/dist/migrations/20260903T101748_transactional_emails_namespace_block_names.d.ts.map +1 -0
  141. package/dist/migrations/20260903T101748_transactional_emails_namespace_block_names.js +60 -0
  142. package/dist/migrations/20260903T101748_transactional_emails_namespace_block_names.js.map +1 -0
  143. package/dist/migrations/index.d.ts +29 -0
  144. package/dist/migrations/index.d.ts.map +1 -0
  145. package/dist/migrations/index.js +33 -0
  146. package/dist/migrations/index.js.map +1 -0
  147. package/docs/transactional-emails.md +176 -0
  148. package/i18n/en.json +92 -0
  149. package/i18n/pl.json +92 -0
  150. package/package.json +113 -0
  151. package/tailwind.css +14 -0
@@ -0,0 +1,119 @@
1
+ /**
2
+ * BrandingService — feature 047 (R5/US2).
3
+ *
4
+ * Resolves email branding (header logo, accent, default header/footer block
5
+ * codes) globally and per sales channel via the Settings module, and exposes the
6
+ * resolved logo URL as the `branding.logoUrl` variable for rendering.
7
+ */
8
+ import { z } from 'zod';
9
+ import { SettingNotRegistered, SettingOutOfScopeForChannel } from '@endora-commerce/platform/kernel';
10
+ import { SalesChannel } from '@endora-commerce/platform/kernel';
11
+ import { TRANSACTIONAL_EMAILS_SETTING_CODES } from '../../manifest.js';
12
+ /**
13
+ * Conditions this service has already reported (D-43's warn-once). Module scope
14
+ * and never reset: branding is read on every rendered email.
15
+ */
16
+ const warnedConditions = new Set();
17
+ function warnOnce(condition, message) {
18
+ if (warnedConditions.has(condition))
19
+ return;
20
+ warnedConditions.add(condition);
21
+ console.warn(message);
22
+ }
23
+ export class BrandingService {
24
+ settings;
25
+ resolveAssetUrl;
26
+ writeDeps;
27
+ constructor(settings, resolveAssetUrl, writeDeps) {
28
+ this.settings = settings;
29
+ this.resolveAssetUrl = resolveAssetUrl;
30
+ this.writeDeps = writeDeps;
31
+ }
32
+ /**
33
+ * Two conditions degrade, and both to the caller's compiled-in default:
34
+ * the setting is not registered (the module has not reconciled its manifest
35
+ * yet), and the setting is scoped to a subset of channels that this read is
36
+ * not inside. Everything else propagates — the bare `catch` this replaces
37
+ * would have swallowed a `MODULE_DISABLED` envelope with equal enthusiasm,
38
+ * which is fail-open dressed as robustness (composition rule 7, D-43).
39
+ */
40
+ async readString(code, salesChannelId, fallback) {
41
+ try {
42
+ return await this.settings.get(code, salesChannelId, z.string());
43
+ }
44
+ catch (error) {
45
+ if (error instanceof SettingNotRegistered)
46
+ return fallback;
47
+ if (error instanceof SettingOutOfScopeForChannel) {
48
+ warnOnce(`out-of-scope:${code}`, `[transactional_emails] branding setting "${code}" is scoped to specific sales ` +
49
+ `channels, so it has no value for this read — falling back to the built-in ` +
50
+ `default (logged once per process).`);
51
+ return fallback;
52
+ }
53
+ throw error;
54
+ }
55
+ }
56
+ /**
57
+ * `salesChannelId === null` is the platform-wide read, and it is passed
58
+ * through as `null` (D-41). It used to become the nil UUID: well-formed
59
+ * enough to pass the seam guard, matching no row, so the right tier by
60
+ * accident — and cached under a fake channel key that no invalidation ever
61
+ * targeted, instead of the reserved `__global__` segment.
62
+ */
63
+ async resolve(salesChannelId) {
64
+ const C = TRANSACTIONAL_EMAILS_SETTING_CODES;
65
+ const logoAssetId = await this.readString(C.LOGO_ASSET_ID, salesChannelId, '');
66
+ const accentColor = await this.readString(C.ACCENT_COLOR, salesChannelId, '#1f2937');
67
+ const headerBlockCode = await this.readString(C.HEADER_BLOCK_CODE, salesChannelId, 'default_email_header');
68
+ const footerBlockCode = await this.readString(C.FOOTER_BLOCK_CODE, salesChannelId, 'default_email_footer');
69
+ let logoUrl = '';
70
+ if (logoAssetId && this.resolveAssetUrl) {
71
+ try {
72
+ logoUrl = (await this.resolveAssetUrl(logoAssetId)) ?? '';
73
+ }
74
+ catch {
75
+ logoUrl = '';
76
+ }
77
+ }
78
+ return {
79
+ salesChannelId,
80
+ logoAssetId,
81
+ logoUrl,
82
+ accentColor,
83
+ headerBlockCode,
84
+ footerBlockCode,
85
+ source: salesChannelId ? 'channel' : 'global',
86
+ };
87
+ }
88
+ /** Persist branding values at the given scope (global when salesChannelId is null). */
89
+ async update(salesChannelId, patch, actor) {
90
+ if (!this.writeDeps) {
91
+ throw new Error('Branding write is not configured.');
92
+ }
93
+ const C = TRANSACTIONAL_EMAILS_SETTING_CODES;
94
+ const entries = [];
95
+ if (patch.logoAssetId !== undefined)
96
+ entries.push([C.LOGO_ASSET_ID, patch.logoAssetId]);
97
+ if (patch.accentColor !== undefined)
98
+ entries.push([C.ACCENT_COLOR, patch.accentColor]);
99
+ if (patch.headerBlockCode !== undefined)
100
+ entries.push([C.HEADER_BLOCK_CODE, patch.headerBlockCode]);
101
+ if (patch.footerBlockCode !== undefined)
102
+ entries.push([C.FOOTER_BLOCK_CODE, patch.footerBlockCode]);
103
+ let channelCodes = null;
104
+ if (salesChannelId) {
105
+ const channel = await this.writeDeps.emFactory().findOne(SalesChannel, { id: salesChannelId });
106
+ channelCodes = channel ? [channel.code] : [];
107
+ }
108
+ for (const [code, value] of entries) {
109
+ if (channelCodes) {
110
+ await this.writeDeps.admin.setValueForSubset(code, channelCodes, value, null, actor);
111
+ }
112
+ else {
113
+ await this.writeDeps.admin.setValueForAllChannels(code, value, null, actor);
114
+ }
115
+ }
116
+ return this.resolve(salesChannelId);
117
+ }
118
+ }
119
+ //# sourceMappingURL=branding.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"branding.service.js","sourceRoot":"","sources":["../../../src/backend/services/branding.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Content resolver — feature 047 (R5/US1/US2).
3
+ *
4
+ * Resolves the effective subject + content for an email definition at a given
5
+ * scope and language: per-channel override -> global override -> module default,
6
+ * each with a language fallback to the channel default language.
7
+ */
8
+ import type { EntityManager } from '@mikro-orm/postgresql';
9
+ import type { PuckDataTree } from '@endora-commerce/email-components/schema/envelope';
10
+ import type { TransactionalEmail } from '../entities/transactional-email.entity.js';
11
+ export type ContentSource = 'channel' | 'global' | 'default';
12
+ export interface ResolvedContent {
13
+ subject: string;
14
+ content: PuckDataTree;
15
+ source: ContentSource;
16
+ version: number | null;
17
+ language: string;
18
+ }
19
+ export interface ResolveOptions {
20
+ salesChannelId: string | null;
21
+ language: string;
22
+ /** Fallback language when the requested language has no content. */
23
+ fallbackLanguage?: string;
24
+ }
25
+ export declare class ContentResolver {
26
+ resolve(em: EntityManager, email: TransactionalEmail, opts: ResolveOptions): Promise<ResolvedContent>;
27
+ }
28
+ //# sourceMappingURL=content-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-resolver.d.ts","sourceRoot":"","sources":["../../../src/backend/services/content-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mDAAmD,CAAC;AACtF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,2CAA2C,CAAC;AAGpF,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE7D,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,YAAY,CAAC;IACtB,MAAM,EAAE,aAAa,CAAC;IACtB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,oEAAoE;IACpE,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAuBD,qBAAa,eAAe;IACpB,OAAO,CACX,EAAE,EAAE,aAAa,EACjB,KAAK,EAAE,kBAAkB,EACzB,IAAI,EAAE,cAAc,GACnB,OAAO,CAAC,eAAe,CAAC;CAsC5B"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Content resolver — feature 047 (R5/US1/US2).
3
+ *
4
+ * Resolves the effective subject + content for an email definition at a given
5
+ * scope and language: per-channel override -> global override -> module default,
6
+ * each with a language fallback to the channel default language.
7
+ */
8
+ import { TransactionalEmailContent } from '../entities/transactional-email-content.entity.js';
9
+ function envelopeTree(envelope, language) {
10
+ if (!envelope || typeof envelope !== 'object')
11
+ return null;
12
+ const langs = envelope.languages;
13
+ if (!langs || typeof langs !== 'object')
14
+ return null;
15
+ const tree = langs[language];
16
+ return tree && typeof tree === 'object' ? tree : null;
17
+ }
18
+ function defaultSubject(email, language, fallback) {
19
+ const map = email.defaultSubject ?? {};
20
+ return map[language] ?? (fallback ? map[fallback] : undefined) ?? Object.values(map)[0] ?? '';
21
+ }
22
+ function defaultContent(email, language, fallback) {
23
+ return (envelopeTree(email.defaultContent, language) ??
24
+ (fallback ? envelopeTree(email.defaultContent, fallback) : null) ??
25
+ { root: { props: {} }, content: [] });
26
+ }
27
+ export class ContentResolver {
28
+ async resolve(em, email, opts) {
29
+ const langs = [opts.language, ...(opts.fallbackLanguage ? [opts.fallbackLanguage] : [])];
30
+ // 1. Per-channel override (requested language, then fallback language).
31
+ if (opts.salesChannelId) {
32
+ for (const lang of langs) {
33
+ const row = await em.findOne(TransactionalEmailContent, {
34
+ emailId: email.id,
35
+ salesChannelId: opts.salesChannelId,
36
+ language: lang,
37
+ });
38
+ if (row) {
39
+ return { subject: row.subject, content: row.content, source: 'channel', version: row.version, language: lang };
40
+ }
41
+ }
42
+ }
43
+ // 2. Global override (requested language, then fallback language).
44
+ for (const lang of langs) {
45
+ const row = await em.findOne(TransactionalEmailContent, {
46
+ emailId: email.id,
47
+ salesChannelId: null,
48
+ language: lang,
49
+ });
50
+ if (row) {
51
+ return { subject: row.subject, content: row.content, source: 'global', version: row.version, language: lang };
52
+ }
53
+ }
54
+ // 3. Module default.
55
+ return {
56
+ subject: defaultSubject(email, opts.language, opts.fallbackLanguage),
57
+ content: defaultContent(email, opts.language, opts.fallbackLanguage),
58
+ source: 'default',
59
+ version: null,
60
+ language: opts.language,
61
+ };
62
+ }
63
+ }
64
+ //# sourceMappingURL=content-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-resolver.js","sourceRoot":"","sources":["../../../src/backend/services/content-resolver.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * EmailBlockService — feature 047 (US3). CRUD for reusable email-safe blocks,
3
+ * including per-channel scoping via the bridge table, optimistic concurrency,
4
+ * email-safe validation, system-block protection, and a reference check on
5
+ * delete (a block embedded by any email/block/template cannot be removed).
6
+ */
7
+ import type { EntityManager } from '@mikro-orm/postgresql';
8
+ import { type CreateEmailBlockRequest, type EmailBlockDetail, type EmailBlockSummary, type PatchEmailBlockRequest, type PutEmailBlockContentRequest } from '@endora-commerce/contracts';
9
+ import type { AuditPort } from '@endora-commerce/platform/kernel';
10
+ export declare class EmailBlockService {
11
+ #private;
12
+ private readonly emFactory;
13
+ private readonly auditLog?;
14
+ constructor(emFactory: () => EntityManager, auditLog?: AuditPort | undefined);
15
+ private channelIds;
16
+ private summary;
17
+ list(salesChannelId?: string): Promise<EmailBlockSummary[]>;
18
+ private loadOrThrow;
19
+ get(id: string): Promise<EmailBlockDetail>;
20
+ create(req: CreateEmailBlockRequest, actor?: unknown): Promise<EmailBlockDetail>;
21
+ patch(id: string, req: PatchEmailBlockRequest): Promise<EmailBlockDetail>;
22
+ setContent(id: string, language: string, req: PutEmailBlockContentRequest): Promise<EmailBlockDetail>;
23
+ delete(id: string): Promise<void>;
24
+ }
25
+ //# sourceMappingURL=email-block.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email-block.service.d.ts","sourceRoot":"","sources":["../../../src/backend/services/email-block.service.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAEL,KAAK,uBAAuB,EAC5B,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,EAC3B,KAAK,2BAA2B,EACjC,MAAM,4BAA4B,CAAC;AAMpC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAalE,qBAAa,iBAAiB;;IAE1B,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;gBADT,SAAS,EAAE,MAAM,aAAa,EAC9B,QAAQ,CAAC,EAAE,SAAS,YAAA;YAqBzB,UAAU;YAKV,OAAO;IAcf,IAAI,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC;YAenD,WAAW;IAMnB,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAY1C,MAAM,CAAC,GAAG,EAAE,uBAAuB,EAAE,KAAK,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAwBhF,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,sBAAsB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAqBzE,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,2BAA2B,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAiBrG,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAQxC"}
@@ -0,0 +1,162 @@
1
+ /**
2
+ * EmailBlockService — feature 047 (US3). CRUD for reusable email-safe blocks,
3
+ * including per-channel scoping via the bridge table, optimistic concurrency,
4
+ * email-safe validation, system-block protection, and a reference check on
5
+ * delete (a block embedded by any email/block/template cannot be removed).
6
+ */
7
+ import { ERROR_CODES, } from '@endora-commerce/contracts';
8
+ import { EMAIL_SAFE_COMPONENT_NAMES } from '@endora-commerce/email-components/schema/component-types';
9
+ import { walkUnknownComponents } from '@endora-commerce/email-components/tree/walk-embeds';
10
+ import { HttpError } from '@endora-commerce/platform/http';
11
+ import { recordAuditFromContext } from '@endora-commerce/platform/commands';
12
+ import { EmailBlock } from '../entities/email-block.entity.js';
13
+ import { EmailBlockSalesChannel } from '../entities/email-block-sales-channel.entity.js';
14
+ const KNOWN = new Set(EMAIL_SAFE_COMPONENT_NAMES);
15
+ function validateTree(content) {
16
+ const unknown = walkUnknownComponents(content, KNOWN);
17
+ if (unknown.size > 0) {
18
+ throw new HttpError(400, ERROR_CODES.VALIDATION_FAILED, `Non-email-safe components: ${[...unknown].join(', ')}`);
19
+ }
20
+ }
21
+ export class EmailBlockService {
22
+ emFactory;
23
+ auditLog;
24
+ constructor(emFactory, auditLog) {
25
+ this.emFactory = emFactory;
26
+ this.auditLog = auditLog;
27
+ }
28
+ #audit(em, action, objectId, stateBefore, stateAfter) {
29
+ if (this.auditLog) {
30
+ recordAuditFromContext(this.auditLog, em, {
31
+ action,
32
+ objectType: 'email_block',
33
+ objectId,
34
+ stateBefore,
35
+ stateAfter,
36
+ });
37
+ }
38
+ }
39
+ async channelIds(em, blockId) {
40
+ const rows = await em.find(EmailBlockSalesChannel, { blockId });
41
+ return rows.map((r) => r.salesChannelId);
42
+ }
43
+ async summary(em, b) {
44
+ const ids = await this.channelIds(em, b.id);
45
+ return {
46
+ id: b.id,
47
+ code: b.code,
48
+ name: b.name,
49
+ active: b.active,
50
+ isSystem: b.isSystem,
51
+ scope: ids.length > 0 ? 'channel' : 'global',
52
+ languages: b.languages,
53
+ version: b.version,
54
+ };
55
+ }
56
+ async list(salesChannelId) {
57
+ const em = this.emFactory();
58
+ const blocks = await em.find(EmailBlock, {}, { orderBy: { name: 'asc' } });
59
+ const out = [];
60
+ for (const b of blocks) {
61
+ const summary = await this.summary(em, b);
62
+ if (salesChannelId && summary.scope === 'channel') {
63
+ const ids = await this.channelIds(em, b.id);
64
+ if (!ids.includes(salesChannelId))
65
+ continue;
66
+ }
67
+ out.push(summary);
68
+ }
69
+ return out;
70
+ }
71
+ async loadOrThrow(em, id) {
72
+ const b = await em.findOne(EmailBlock, { id });
73
+ if (!b)
74
+ throw new HttpError(404, ERROR_CODES.NOT_FOUND, `Email block ${id} not found.`);
75
+ return b;
76
+ }
77
+ async get(id) {
78
+ const em = this.emFactory();
79
+ const b = await this.loadOrThrow(em, id);
80
+ const ids = await this.channelIds(em, b.id);
81
+ return {
82
+ ...(await this.summary(em, b)),
83
+ description: b.description,
84
+ content: b.content.languages ?? {},
85
+ salesChannelIds: ids,
86
+ };
87
+ }
88
+ async create(req, actor) {
89
+ void actor;
90
+ const em = this.emFactory();
91
+ const existing = await em.findOne(EmailBlock, { code: req.code });
92
+ if (existing)
93
+ throw new HttpError(409, ERROR_CODES.VERSION_CONFLICT, `Block code "${req.code}" already exists.`);
94
+ const block = em.create(EmailBlock, {
95
+ code: req.code,
96
+ name: req.name,
97
+ description: req.description ?? null,
98
+ active: req.active ?? true,
99
+ content: { schema_version: 1, languages: {} },
100
+ languages: req.languages,
101
+ isSystem: false,
102
+ version: 1,
103
+ });
104
+ await em.persistAndFlush(block);
105
+ for (const scId of req.salesChannelIds ?? []) {
106
+ em.persist(em.create(EmailBlockSalesChannel, { blockId: block.id, salesChannelId: scId, code: block.code }));
107
+ }
108
+ this.#audit(em, 'email_block.create', block.id, null, { code: block.code, name: block.name });
109
+ await em.flush();
110
+ return this.get(block.id);
111
+ }
112
+ async patch(id, req) {
113
+ const em = this.emFactory();
114
+ const b = await this.loadOrThrow(em, id);
115
+ if (req.expectedVersion !== undefined && req.expectedVersion !== b.version) {
116
+ throw new HttpError(409, ERROR_CODES.VERSION_CONFLICT, 'Block was modified by someone else.');
117
+ }
118
+ if (req.name !== undefined)
119
+ b.name = req.name;
120
+ if (req.description !== undefined)
121
+ b.description = req.description;
122
+ if (req.active !== undefined && !b.isSystem)
123
+ b.active = req.active;
124
+ if (req.salesChannelIds !== undefined && !b.isSystem) {
125
+ const current = await em.find(EmailBlockSalesChannel, { blockId: b.id });
126
+ await em.removeAndFlush(current);
127
+ for (const scId of req.salesChannelIds) {
128
+ em.persist(em.create(EmailBlockSalesChannel, { blockId: b.id, salesChannelId: scId, code: b.code }));
129
+ }
130
+ }
131
+ this.#audit(em, 'email_block.update', b.id, null, { code: b.code, name: b.name });
132
+ await em.flush();
133
+ return this.get(id);
134
+ }
135
+ async setContent(id, language, req) {
136
+ validateTree(req.content);
137
+ const em = this.emFactory();
138
+ const b = await this.loadOrThrow(em, id);
139
+ if (req.expectedVersion !== b.version) {
140
+ throw new HttpError(409, ERROR_CODES.VERSION_CONFLICT, 'Block was modified by someone else.');
141
+ }
142
+ const env = b.content ?? {};
143
+ const languages = { ...(env.languages ?? {}), [language]: req.content };
144
+ b.content = { schema_version: env.schema_version ?? 1, languages };
145
+ if (!b.languages.includes(language))
146
+ b.languages = [...b.languages, language];
147
+ b.version += 1;
148
+ this.#audit(em, 'email_block.set_content', b.id, null, { language, version: b.version });
149
+ await em.flush();
150
+ return this.get(id);
151
+ }
152
+ async delete(id) {
153
+ const em = this.emFactory();
154
+ const b = await this.loadOrThrow(em, id);
155
+ if (b.isSystem)
156
+ throw new HttpError(409, ERROR_CODES.VALIDATION_FAILED, 'System blocks cannot be deleted.');
157
+ const bridges = await em.find(EmailBlockSalesChannel, { blockId: b.id });
158
+ this.#audit(em, 'email_block.delete', b.id, { code: b.code }, null);
159
+ await em.removeAndFlush([...bridges, b]);
160
+ }
161
+ }
162
+ //# sourceMappingURL=email-block.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email-block.service.js","sourceRoot":"","sources":["../../../src/backend/services/email-block.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Email page-builder descriptor (feature 047). The admin merges this with the
3
+ * bundled `defaultEmailBuilderConfig` from @endora-commerce/email-components to present an
4
+ * email-safe palette. Names are the single source of truth shared with the
5
+ * renderer and save-time validation.
6
+ */
7
+ import type { EmailPageBuilderDescriptor } from '@endora-commerce/contracts';
8
+ export declare function describeEmailBuilder(): EmailPageBuilderDescriptor;
9
+ //# sourceMappingURL=email-builder-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email-builder-registry.d.ts","sourceRoot":"","sources":["../../../src/backend/services/email-builder-registry.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,4BAA4B,CAAC;AAE7E,wBAAgB,oBAAoB,IAAI,0BAA0B,CAKjE"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Email page-builder descriptor (feature 047). The admin merges this with the
3
+ * bundled `defaultEmailBuilderConfig` from @endora-commerce/email-components to present an
4
+ * email-safe palette. Names are the single source of truth shared with the
5
+ * renderer and save-time validation.
6
+ */
7
+ import { EMAIL_SAFE_COMPONENT_NAMES } from '@endora-commerce/email-components/schema/component-types';
8
+ export function describeEmailBuilder() {
9
+ return {
10
+ schemaVersion: 1,
11
+ components: EMAIL_SAFE_COMPONENT_NAMES.map((name) => ({ name })),
12
+ };
13
+ }
14
+ //# sourceMappingURL=email-builder-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email-builder-registry.js","sourceRoot":"","sources":["../../../src/backend/services/email-builder-registry.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,0BAA0B,EAAE,MAAM,0DAA0D,CAAC;AAGtG,MAAM,UAAU,oBAAoB;IAClC,OAAO;QACL,aAAa,EAAE,CAAC;QAChB,UAAU,EAAE,0BAA0B,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;KACjE,CAAC;AACJ,CAAC"}
@@ -0,0 +1,63 @@
1
+ import type { EmailDefaults, EmailDefaultsRegistryPort } from '@endora-commerce/contracts';
2
+ /**
3
+ * EmailDefaultsRegistry — feature 047 (R6).
4
+ *
5
+ * In-process registry where each owning module registers the default subject +
6
+ * content for the transactional emails it declares in its manifest. Kept out of
7
+ * the static manifest because content envelopes are large. The reconciler reads
8
+ * this at boot to seed `transactional_emails.default_*`.
9
+ */
10
+ /**
11
+ * `EmailDefaults` moved to `@endora-commerce/contracts` in feature 075's Phase P — seven
12
+ * modules push one from their own boot hook, which makes it a boundary shape
13
+ * rather than an internal. Re-exported here for the length of Phase P, which
14
+ * cuts no consumer.
15
+ */
16
+ export type { EmailDefaults };
17
+ /**
18
+ * `implements` the shape Phase P published, which is what stops the registry
19
+ * and its contract drifting: seven modules read the published type, and `tsc`
20
+ * refuses the day a method here stops matching.
21
+ */
22
+ export declare class EmailDefaultsRegistry implements EmailDefaultsRegistryPort {
23
+ private readonly byCode;
24
+ private readonly ownerByCode;
25
+ /**
26
+ * `ownerModuleId` is required since D-39: a contribution seam records who
27
+ * contributed, so the honour/skip question above has something to key on and
28
+ * so an entry can be attributed without guessing. It became required rather
29
+ * than optional because an optional owner makes "nobody stated a policy for
30
+ * this entry" indistinguishable from "this entry has no owner".
31
+ */
32
+ register(code: string, defaults: EmailDefaults, ownerModuleId: string): void;
33
+ ownerOf(code: string): string | undefined;
34
+ /** Every registered code with its contributing module — the owner ledger. */
35
+ owners(): ReadonlyMap<string, string>;
36
+ /**
37
+ * The owning module's sentence for why this email may not be switched off,
38
+ * or `null` when it may (issue #89).
39
+ *
40
+ * An **unregistered** code answers `null`, and that is not a fail-open: a
41
+ * definition row exists only because a module declared the code in its
42
+ * manifest, and a module that declares a protected email declares its
43
+ * defaults in the same boot hook. A code with no entry here is one nobody
44
+ * seeded content for — already the degenerate case, and switching it off
45
+ * changes nothing that was going to be delivered.
46
+ */
47
+ nonDeactivatableReasonOf(code: string): string | null;
48
+ get(code: string): EmailDefaults | undefined;
49
+ has(code: string): boolean;
50
+ }
51
+ /**
52
+ * Process-wide singleton, kept only for callers that have not moved to the
53
+ * container-scoped instance.
54
+ *
55
+ * **Prefer the `emailDefaultsPort` registration**, which `transactional_emails`
56
+ * provides per composition. This one is shared by every composition in a
57
+ * process — harmless for static content that every composition registers
58
+ * identically, but it means a module absent from one composition can still have
59
+ * its defaults present there, courtesy of another. With ~950 compositions in a
60
+ * test run that is a real, if benign, cross-composition leak.
61
+ */
62
+ export declare const emailDefaultsRegistry: EmailDefaultsRegistry;
63
+ //# sourceMappingURL=email-defaults-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email-defaults-registry.d.ts","sourceRoot":"","sources":["../../../src/backend/services/email-defaults-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAC;AAC3F;;;;;;;GAOG;AAEH;;;;;GAKG;AACH,YAAY,EAAE,aAAa,EAAE,CAAC;AAE9B;;;;GAIG;AACH,qBAAa,qBAAsB,YAAW,yBAAyB;IACrE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAoC;IAC3D,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA6B;IAEzD;;;;;;OAMG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,GAAG,IAAI;IAK5E,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAIzC,6EAA6E;IAC7E,MAAM,IAAI,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC;IAIrC;;;;;;;;;;OAUG;IACH,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAIrD,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS;IAI5C,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;CAG3B;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,qBAAqB,uBAA8B,CAAC"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * `implements` the shape Phase P published, which is what stops the registry
3
+ * and its contract drifting: seven modules read the published type, and `tsc`
4
+ * refuses the day a method here stops matching.
5
+ */
6
+ export class EmailDefaultsRegistry {
7
+ byCode = new Map();
8
+ ownerByCode = new Map();
9
+ /**
10
+ * `ownerModuleId` is required since D-39: a contribution seam records who
11
+ * contributed, so the honour/skip question above has something to key on and
12
+ * so an entry can be attributed without guessing. It became required rather
13
+ * than optional because an optional owner makes "nobody stated a policy for
14
+ * this entry" indistinguishable from "this entry has no owner".
15
+ */
16
+ register(code, defaults, ownerModuleId) {
17
+ this.byCode.set(code, defaults);
18
+ this.ownerByCode.set(code, ownerModuleId);
19
+ }
20
+ ownerOf(code) {
21
+ return this.ownerByCode.get(code);
22
+ }
23
+ /** Every registered code with its contributing module — the owner ledger. */
24
+ owners() {
25
+ return new Map(this.ownerByCode);
26
+ }
27
+ /**
28
+ * The owning module's sentence for why this email may not be switched off,
29
+ * or `null` when it may (issue #89).
30
+ *
31
+ * An **unregistered** code answers `null`, and that is not a fail-open: a
32
+ * definition row exists only because a module declared the code in its
33
+ * manifest, and a module that declares a protected email declares its
34
+ * defaults in the same boot hook. A code with no entry here is one nobody
35
+ * seeded content for — already the degenerate case, and switching it off
36
+ * changes nothing that was going to be delivered.
37
+ */
38
+ nonDeactivatableReasonOf(code) {
39
+ return this.byCode.get(code)?.nonDeactivatable?.reason ?? null;
40
+ }
41
+ get(code) {
42
+ return this.byCode.get(code);
43
+ }
44
+ has(code) {
45
+ return this.byCode.has(code);
46
+ }
47
+ }
48
+ /**
49
+ * Process-wide singleton, kept only for callers that have not moved to the
50
+ * container-scoped instance.
51
+ *
52
+ * **Prefer the `emailDefaultsPort` registration**, which `transactional_emails`
53
+ * provides per composition. This one is shared by every composition in a
54
+ * process — harmless for static content that every composition registers
55
+ * identically, but it means a module absent from one composition can still have
56
+ * its defaults present there, courtesy of another. With ~950 compositions in a
57
+ * test run that is a real, if benign, cross-composition leak.
58
+ */
59
+ export const emailDefaultsRegistry = new EmailDefaultsRegistry();
60
+ //# sourceMappingURL=email-defaults-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email-defaults-registry.js","sourceRoot":"","sources":["../../../src/backend/services/email-defaults-registry.ts"],"names":[],"mappings":"AAkBA;;;;GAIG;AACH,MAAM,OAAO,qBAAqB;IACf,MAAM,GAAG,IAAI,GAAG,EAAyB,CAAC;IAC1C,WAAW,GAAG,IAAI,GAAG,EAAkB,CAAC;IAEzD;;;;;;OAMG;IACH,QAAQ,CAAC,IAAY,EAAE,QAAuB,EAAE,aAAqB;QACnE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QAChC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC;IAC5C,CAAC;IAED,OAAO,CAAC,IAAY;QAClB,OAAO,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACpC,CAAC;IAED,6EAA6E;IAC7E,MAAM;QACJ,OAAO,IAAI,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IACnC,CAAC;IAED;;;;;;;;;;OAUG;IACH,wBAAwB,CAAC,IAAY;QACnC,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,gBAAgB,EAAE,MAAM,IAAI,IAAI,CAAC;IACjE,CAAC;IAED,GAAG,CAAC,IAAY;QACd,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC/B,CAAC;IAED,GAAG,CAAC,IAAY;QACd,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC/B,CAAC;CACF;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,IAAI,qBAAqB,EAAE,CAAC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * EmailTemplateService — feature 047 (US3). CRUD for reusable email-safe
3
+ * templates. Same surface as EmailBlockService without an `active` flag.
4
+ */
5
+ import type { EntityManager } from '@mikro-orm/postgresql';
6
+ import { type CreateEmailTemplateRequest, type EmailTemplateDetail, type EmailTemplateSummary, type PatchEmailTemplateRequest, type PutEmailBlockContentRequest } from '@endora-commerce/contracts';
7
+ import type { AuditPort } from '@endora-commerce/platform/kernel';
8
+ export declare class EmailTemplateService {
9
+ #private;
10
+ private readonly emFactory;
11
+ private readonly auditLog?;
12
+ constructor(emFactory: () => EntityManager, auditLog?: AuditPort | undefined);
13
+ private channelIds;
14
+ private summary;
15
+ list(salesChannelId?: string): Promise<EmailTemplateSummary[]>;
16
+ private loadOrThrow;
17
+ get(id: string): Promise<EmailTemplateDetail>;
18
+ create(req: CreateEmailTemplateRequest): Promise<EmailTemplateDetail>;
19
+ patch(id: string, req: PatchEmailTemplateRequest): Promise<EmailTemplateDetail>;
20
+ setContent(id: string, language: string, req: PutEmailBlockContentRequest): Promise<EmailTemplateDetail>;
21
+ delete(id: string): Promise<void>;
22
+ }
23
+ //# sourceMappingURL=email-template.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email-template.service.d.ts","sourceRoot":"","sources":["../../../src/backend/services/email-template.service.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAEL,KAAK,0BAA0B,EAC/B,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,yBAAyB,EAC9B,KAAK,2BAA2B,EACjC,MAAM,4BAA4B,CAAC;AAMpC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAalE,qBAAa,oBAAoB;;IAE7B,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;gBADT,SAAS,EAAE,MAAM,aAAa,EAC9B,QAAQ,CAAC,EAAE,SAAS,YAAA;YAqBzB,UAAU;YAKV,OAAO;IAaf,IAAI,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,EAAE,CAAC;YAetD,WAAW;IAMnB,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAY7C,MAAM,CAAC,GAAG,EAAE,0BAA0B,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAsBrE,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,yBAAyB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAoB/E,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,2BAA2B,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAiBxG,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAQxC"}