@antelopejs/dms-mailing 0.9.2 → 0.10.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 (65) hide show
  1. package/README.md +11 -11
  2. package/dist/data/read-only-routes.d.ts +14 -1
  3. package/dist/data/sends-table.d.ts +14 -1
  4. package/dist/data/suppressions-table.d.ts +14 -1
  5. package/dist/data/templates-table.d.ts +15 -2
  6. package/dist/db/models/send-events.model.d.ts +3 -3
  7. package/dist/db/models/sends.model.d.ts +3 -3
  8. package/dist/db/models/settings.model.d.ts +3 -3
  9. package/dist/db/models/suppressions.model.d.ts +3 -3
  10. package/dist/db/models/template-versions.model.d.ts +3 -3
  11. package/dist/db/models/template-versions.model.js +2 -3
  12. package/dist/db/models/template-versions.model.js.map +1 -1
  13. package/dist/db/models/templates.model.d.ts +4 -3
  14. package/dist/db/models/templates.model.js +14 -6
  15. package/dist/db/models/templates.model.js.map +1 -1
  16. package/dist/db/models/webhook-status.model.d.ts +3 -3
  17. package/dist/db/tables/templates.table.d.ts +4 -1
  18. package/dist/hooks/tenant-export.js +5 -1
  19. package/dist/hooks/tenant-export.js.map +1 -1
  20. package/dist/pages/editor.js +1 -1
  21. package/dist/pages/editor.js.map +1 -1
  22. package/dist/routes/sends.js +2 -1
  23. package/dist/routes/sends.js.map +1 -1
  24. package/dist/routes/templates.d.ts +2 -2
  25. package/dist/routes/templates.js +2 -2
  26. package/dist/routes/templates.js.map +1 -1
  27. package/dist/services/settings.js +2 -1
  28. package/dist/services/settings.js.map +1 -1
  29. package/dist/services/templates.js +10 -8
  30. package/dist/services/templates.js.map +1 -1
  31. package/dist/validation/declarations.schema.d.ts +1 -1
  32. package/dist/validation/stored.schema.d.ts +12 -0
  33. package/dist/validation/stored.schema.js +198 -0
  34. package/dist/validation/stored.schema.js.map +1 -0
  35. package/frontend-vue/app/composables/useEditorContext.ts +1 -1
  36. package/frontend-vue/build/backend-boundary.ts +7 -2
  37. package/frontend-vue/dms.frontend.ts +1 -4
  38. package/frontend-vue/eslint.config.mjs +9 -1
  39. package/frontend-vue/package.json +1 -1
  40. package/frontend-vue/pnpm-lock.yaml +24 -7
  41. package/frontend-vue/tests/backend-boundary.test.ts +4 -4
  42. package/frontend-vue/tests/column-displays.test.ts +13 -12
  43. package/frontend-vue/tests/footer-block.test.ts +1 -1
  44. package/frontend-vue/tests/template-gallery-display.test.ts +6 -5
  45. package/frontend-vue/tests/template-overlays.test.ts +4 -12
  46. package/package.json +15 -15
  47. package/src/db/models/template-versions.model.ts +8 -6
  48. package/src/db/models/templates.model.ts +25 -9
  49. package/src/hooks/tenant-export.ts +5 -2
  50. package/src/pages/editor.ts +1 -1
  51. package/src/routes/sends.ts +2 -3
  52. package/src/routes/templates.ts +3 -2
  53. package/src/services/settings.ts +9 -3
  54. package/src/services/templates.ts +35 -16
  55. package/src/validation/stored.schema.ts +257 -0
  56. /package/frontend-vue/app/components/{Editor.vue → TemplateEditor.vue} +0 -0
  57. /package/frontend-vue/app/components/blocks/{Button.vue → BlockButton.vue} +0 -0
  58. /package/frontend-vue/app/components/blocks/{Code.vue → BlockCode.vue} +0 -0
  59. /package/frontend-vue/app/components/blocks/{Divider.vue → BlockDivider.vue} +0 -0
  60. /package/frontend-vue/app/components/blocks/{Footer.vue → BlockFooter.vue} +0 -0
  61. /package/frontend-vue/app/components/blocks/{Heading.vue → BlockHeading.vue} +0 -0
  62. /package/frontend-vue/app/components/blocks/{Hero.vue → BlockHero.vue} +0 -0
  63. /package/frontend-vue/app/components/blocks/{List.vue → BlockList.vue} +0 -0
  64. /package/frontend-vue/app/components/blocks/{Paragraph.vue → BlockParagraph.vue} +0 -0
  65. /package/frontend-vue/app/components/blocks/{Total.vue → BlockTotal.vue} +0 -0
@@ -0,0 +1,257 @@
1
+ import { z } from "zod";
2
+ import type { MailingTemplateVersion } from "../db/tables";
3
+ import type { TemplateChange, TemplateChangeKind } from "../engine/changes";
4
+ import type {
5
+ Block,
6
+ BlockType,
7
+ Condition,
8
+ ConditionOperator,
9
+ TemplateCategory,
10
+ TemplateContent,
11
+ TextAlign,
12
+ VariableDefinition,
13
+ VariableType,
14
+ } from "../types";
15
+
16
+ /** What a stored JSON column holds, checked when it is read back. */
17
+ type StoredSchema<T> = z.ZodType<T, z.ZodTypeDef, unknown>;
18
+
19
+ /**
20
+ * `text`, a stored JSON column, parsed and read with `schema`: a
21
+ * `SyntaxError` when it is not JSON, a `TypeError` naming `what` when it does
22
+ * not hold one, as `parseJsonObject` refuses.
23
+ */
24
+ export function parseStored<T>(
25
+ text: string,
26
+ schema: StoredSchema<T>,
27
+ what: string,
28
+ ): T {
29
+ const value: unknown = JSON.parse(text);
30
+ const read = schema.safeParse(value);
31
+ if (!read.success) throw new TypeError(`expected ${what}`);
32
+ return read.data;
33
+ }
34
+
35
+ /** A string that is one of the keys of `members`, which lists every member. */
36
+ function oneOf<T extends string>(members: Record<T, true>): StoredSchema<T> {
37
+ return z
38
+ .string()
39
+ .refine((value): value is T => Object.hasOwn(members, value));
40
+ }
41
+
42
+ const conditionOperators = {
43
+ truthy: true,
44
+ falsy: true,
45
+ eq: true,
46
+ ne: true,
47
+ gt: true,
48
+ lt: true,
49
+ } satisfies Record<ConditionOperator, true>;
50
+
51
+ const textAligns = {
52
+ left: true,
53
+ center: true,
54
+ right: true,
55
+ } satisfies Record<TextAlign, true>;
56
+
57
+ const variableTypes = {
58
+ string: true,
59
+ text: true,
60
+ number: true,
61
+ money: true,
62
+ date: true,
63
+ url: true,
64
+ boolean: true,
65
+ array: true,
66
+ } satisfies Record<VariableType, true>;
67
+
68
+ const blockTypes = {
69
+ hero: true,
70
+ heading: true,
71
+ paragraph: true,
72
+ code: true,
73
+ list: true,
74
+ total: true,
75
+ button: true,
76
+ if: true,
77
+ divider: true,
78
+ footer: true,
79
+ } satisfies Record<BlockType, true>;
80
+
81
+ const changeKinds = {
82
+ locale_added: true,
83
+ locale_removed: true,
84
+ subject: true,
85
+ preheader: true,
86
+ block_added: true,
87
+ block_removed: true,
88
+ block_changed: true,
89
+ blocks_reordered: true,
90
+ variable_added: true,
91
+ variable_removed: true,
92
+ variable_changed: true,
93
+ } satisfies Record<TemplateChangeKind, true>;
94
+
95
+ const storedConditionSchema: StoredSchema<Condition> = z
96
+ .object({
97
+ path: z.string(),
98
+ operator: oneOf(conditionOperators),
99
+ value: z.string(),
100
+ })
101
+ .passthrough();
102
+
103
+ const align = oneOf(textAligns);
104
+ const base = { id: z.string(), visibleIf: storedConditionSchema.nullable() };
105
+
106
+ /**
107
+ * A block as the module stores it: the shape of {@link Block} and nothing
108
+ * more, without the bounds an edit is held to, so every block the module
109
+ * wrote reads back unchanged.
110
+ */
111
+ const storedBlockSchema: StoredSchema<Block> = z.lazy(() => storedBlockUnion);
112
+
113
+ /** Built once: the lazy schema above returns it for every nested block. */
114
+ const storedBlockUnion = z.discriminatedUnion("type", [
115
+ z
116
+ .object({
117
+ ...base,
118
+ type: z.literal("hero"),
119
+ imageUrl: z.string(),
120
+ alt: z.string(),
121
+ })
122
+ .passthrough(),
123
+ z
124
+ .object({
125
+ ...base,
126
+ type: z.literal("heading"),
127
+ text: z.string(),
128
+ align,
129
+ size: z.number(),
130
+ })
131
+ .passthrough(),
132
+ z
133
+ .object({
134
+ ...base,
135
+ type: z.literal("paragraph"),
136
+ text: z.string(),
137
+ align,
138
+ })
139
+ .passthrough(),
140
+ z
141
+ .object({ ...base, type: z.literal("code"), text: z.string() })
142
+ .passthrough(),
143
+ z
144
+ .object({
145
+ ...base,
146
+ type: z.literal("list"),
147
+ source: z.string(),
148
+ labelPath: z.string(),
149
+ valuePath: z.string(),
150
+ })
151
+ .passthrough(),
152
+ z
153
+ .object({
154
+ ...base,
155
+ type: z.literal("total"),
156
+ label: z.string(),
157
+ value: z.string(),
158
+ })
159
+ .passthrough(),
160
+ z
161
+ .object({
162
+ ...base,
163
+ type: z.literal("button"),
164
+ text: z.string(),
165
+ href: z.string(),
166
+ align,
167
+ })
168
+ .passthrough(),
169
+ z
170
+ .object({
171
+ ...base,
172
+ type: z.literal("if"),
173
+ condition: storedConditionSchema,
174
+ children: z.array(storedBlockSchema),
175
+ elseChildren: z.array(storedBlockSchema).nullable(),
176
+ })
177
+ .passthrough(),
178
+ z.object({ ...base, type: z.literal("divider") }).passthrough(),
179
+ z
180
+ .object({
181
+ ...base,
182
+ type: z.literal("footer"),
183
+ text: z.string(),
184
+ unsubscribeLabel: z.string(),
185
+ preferencesLabel: z.string(),
186
+ })
187
+ .passthrough(),
188
+ ]);
189
+
190
+ /** Stored {@link TemplateContent}: `json_content`, `json_draft`, a version's. */
191
+ export const storedContentSchema: StoredSchema<TemplateContent> = z
192
+ .object({
193
+ locales: z.record(
194
+ z
195
+ .object({
196
+ subject: z.string(),
197
+ preheader: z.string(),
198
+ blocks: z.array(storedBlockSchema),
199
+ })
200
+ .passthrough(),
201
+ ),
202
+ })
203
+ .passthrough();
204
+
205
+ /** Stored declarations: `json_variables`, `json_variables_draft`, a version's. */
206
+ export const storedVariablesSchema: StoredSchema<VariableDefinition[]> =
207
+ z.array(
208
+ z
209
+ .object({
210
+ path: z.string(),
211
+ type: oneOf(variableTypes),
212
+ required: z.boolean(),
213
+ })
214
+ .passthrough(),
215
+ );
216
+
217
+ /** A version's `json_changes`, as {@link TemplateChange} lists them. */
218
+ export const storedChangesSchema: StoredSchema<TemplateChange[]> = z.array(
219
+ z
220
+ .object({
221
+ locale: z.string(),
222
+ kind: oneOf(changeKinds),
223
+ blockId: z.string().optional(),
224
+ blockType: oneOf(blockTypes).optional(),
225
+ variable: z.string().optional(),
226
+ })
227
+ .passthrough(),
228
+ );
229
+
230
+ /**
231
+ * A template's `json_published_version`: the version row it inserts, whose
232
+ * `publishedAt` JSON wrote as a string.
233
+ */
234
+ export const storedVersionRowSchema: StoredSchema<MailingTemplateVersion> = z
235
+ .object({
236
+ _id: z.string(),
237
+ templateId: z.string(),
238
+ version: z.number(),
239
+ json_content: z.string(),
240
+ json_variables: z.string(),
241
+ locales: z.string(),
242
+ publishedAt: z.string().transform((at) => new Date(at)),
243
+ publishedBy: z.string(),
244
+ json_changes: z.string(),
245
+ })
246
+ .passthrough();
247
+
248
+ /** The settings' `json_categories`, each label one text per language. */
249
+ export const storedCategoriesSchema: StoredSchema<TemplateCategory[]> = z.array(
250
+ z
251
+ .object({
252
+ id: z.string(),
253
+ label: z.record(z.string()),
254
+ icon: z.string(),
255
+ })
256
+ .passthrough(),
257
+ );