@marlinjai/mail-contract 0.4.0 → 0.5.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.
- package/dist/index.d.mts +9337 -2521
- package/dist/index.d.ts +9337 -2521
- package/dist/index.js +417 -16
- package/dist/index.mjs +383 -16
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -22,6 +22,29 @@ function page(item) {
|
|
|
22
22
|
}
|
|
23
23
|
var IdParams = z.object({ id: Id });
|
|
24
24
|
var Ok = z.object({ ok: z.literal(true) });
|
|
25
|
+
var LOCALE_TAG_PATTERN = /^[A-Za-z]{2,3}(?:-[A-Za-z]{4})?(?:-(?:[A-Za-z]{2}|[0-9]{3}))?$/;
|
|
26
|
+
var LocaleTag = z.string().min(2).max(35).regex(LOCALE_TAG_PATTERN, "a language tag such as en, fr or pt-BR");
|
|
27
|
+
function canonicalLocale(tag) {
|
|
28
|
+
if (typeof tag !== "string") return null;
|
|
29
|
+
const trimmed = tag.trim().replace(/_/g, "-");
|
|
30
|
+
if (!LOCALE_TAG_PATTERN.test(trimmed)) return null;
|
|
31
|
+
return trimmed.split("-").map((part, i) => {
|
|
32
|
+
if (i === 0) return part.toLowerCase();
|
|
33
|
+
if (part.length === 4) return part[0].toUpperCase() + part.slice(1).toLowerCase();
|
|
34
|
+
return part.toUpperCase();
|
|
35
|
+
}).join("-");
|
|
36
|
+
}
|
|
37
|
+
function primaryLanguage(tag) {
|
|
38
|
+
return canonicalLocale(tag)?.split("-")[0] ?? null;
|
|
39
|
+
}
|
|
40
|
+
function matchLocale(wanted, available) {
|
|
41
|
+
const canonical = canonicalLocale(wanted);
|
|
42
|
+
if (!canonical) return null;
|
|
43
|
+
const exact = available.find((a) => canonicalLocale(a) === canonical);
|
|
44
|
+
if (exact !== void 0) return exact;
|
|
45
|
+
const primary = canonical.split("-")[0];
|
|
46
|
+
return available.find((a) => primaryLanguage(a) === primary) ?? null;
|
|
47
|
+
}
|
|
25
48
|
|
|
26
49
|
// src/errors.ts
|
|
27
50
|
import { z as z2 } from "zod";
|
|
@@ -137,32 +160,71 @@ var TemplateDocument = z3.object({
|
|
|
137
160
|
sections: z3.array(z3.record(z3.unknown()))
|
|
138
161
|
}).passthrough();
|
|
139
162
|
var MAX_DOCUMENT_BYTES = 1e6;
|
|
163
|
+
var MAX_SUBJECT_LENGTH = 998;
|
|
164
|
+
var MAX_PREHEADER_LENGTH = 500;
|
|
165
|
+
var TemplateSubject = z3.string().max(MAX_SUBJECT_LENGTH).regex(/^[^\x00-\x1f\x7f]*$/, "one line, without control characters");
|
|
166
|
+
var TemplatePreheader = z3.string().max(MAX_PREHEADER_LENGTH).regex(/^[^\x00-\x1f\x7f]*$/, "one line, without control characters");
|
|
167
|
+
var TEMPLATE_LANGUAGE_STATUSES = ["draft", "ready"];
|
|
168
|
+
var TemplateLanguageStatus = z3.enum(TEMPLATE_LANGUAGE_STATUSES);
|
|
169
|
+
var TemplateTranslation = z3.object({
|
|
170
|
+
subject: TemplateSubject.nullable(),
|
|
171
|
+
preheader: TemplatePreheader.nullable(),
|
|
172
|
+
document: TemplateDocument,
|
|
173
|
+
status: TemplateLanguageStatus
|
|
174
|
+
});
|
|
175
|
+
var MAX_TEMPLATE_TRANSLATIONS = 19;
|
|
176
|
+
var TemplateTranslations = z3.record(LocaleTag, TemplateTranslation).refine((t) => Object.keys(t).length <= MAX_TEMPLATE_TRANSLATIONS, `at most ${MAX_TEMPLATE_TRANSLATIONS} language versions`);
|
|
140
177
|
var Template = z3.object({
|
|
141
178
|
id: Id,
|
|
142
179
|
name: z3.string().min(1).max(200),
|
|
143
180
|
description: z3.string().max(2e3).nullable(),
|
|
181
|
+
/**
|
|
182
|
+
* The main language: what `subject`, `preheader` and `document` are written
|
|
183
|
+
* in, and what every recipient without a ready version of their own gets.
|
|
184
|
+
* Set from the workspace's `default_locale` when the template is created
|
|
185
|
+
* unless given, and never changed by a later change of that setting.
|
|
186
|
+
*/
|
|
187
|
+
locale: LocaleTag,
|
|
188
|
+
/** The main language's subject line; null when the mailing that uses the template gives one. */
|
|
189
|
+
subject: TemplateSubject.nullable(),
|
|
190
|
+
preheader: TemplatePreheader.nullable(),
|
|
144
191
|
document: TemplateDocument,
|
|
145
|
-
/**
|
|
192
|
+
/**
|
|
193
|
+
* The other languages. A version whose language the workspace no longer
|
|
194
|
+
* lists is kept, but no send uses it until the language is listed again.
|
|
195
|
+
*/
|
|
196
|
+
translations: TemplateTranslations,
|
|
197
|
+
/** Increments on every saved change, of any language. */
|
|
146
198
|
version: z3.number().int().min(1),
|
|
147
199
|
thumbnail_url: z3.string().url().nullable(),
|
|
148
200
|
archived_at: Timestamp.nullable(),
|
|
149
201
|
created_at: Timestamp,
|
|
150
202
|
updated_at: Timestamp
|
|
151
203
|
});
|
|
152
|
-
var TemplateSummary = Template.omit({ document: true })
|
|
204
|
+
var TemplateSummary = Template.omit({ document: true, translations: true }).extend({
|
|
205
|
+
languages: z3.record(LocaleTag, TemplateLanguageStatus)
|
|
206
|
+
});
|
|
153
207
|
var TemplateCreate = z3.object({
|
|
154
208
|
name: z3.string().min(1).max(200),
|
|
155
209
|
description: z3.string().max(2e3).optional(),
|
|
156
|
-
|
|
210
|
+
locale: LocaleTag.optional(),
|
|
211
|
+
subject: TemplateSubject.nullable().optional(),
|
|
212
|
+
preheader: TemplatePreheader.nullable().optional(),
|
|
213
|
+
document: TemplateDocument,
|
|
214
|
+
translations: TemplateTranslations.optional()
|
|
157
215
|
});
|
|
158
216
|
var TemplateUpdate = z3.object({
|
|
159
217
|
base_version: z3.number().int().min(1),
|
|
160
218
|
name: z3.string().min(1).max(200).optional(),
|
|
161
219
|
description: z3.string().max(2e3).nullable().optional(),
|
|
220
|
+
locale: LocaleTag.optional(),
|
|
221
|
+
subject: TemplateSubject.nullable().optional(),
|
|
222
|
+
preheader: TemplatePreheader.nullable().optional(),
|
|
162
223
|
document: TemplateDocument.optional(),
|
|
224
|
+
translations: z3.record(LocaleTag, TemplateTranslation.nullable()).optional(),
|
|
163
225
|
archived: z3.boolean().optional()
|
|
164
226
|
}).refine(
|
|
165
|
-
(v) => v.name !== void 0 || v.description !== void 0 || v.document !== void 0 || v.archived !== void 0,
|
|
227
|
+
(v) => v.name !== void 0 || v.description !== void 0 || v.locale !== void 0 || v.subject !== void 0 || v.preheader !== void 0 || v.document !== void 0 || v.translations !== void 0 || v.archived !== void 0,
|
|
166
228
|
"at least one field besides base_version"
|
|
167
229
|
);
|
|
168
230
|
var SavedSectionBody = z3.record(z3.unknown());
|
|
@@ -192,7 +254,11 @@ var TemplateListQuery = PageQuery.extend({
|
|
|
192
254
|
var TemplateVersion = z3.object({
|
|
193
255
|
template_id: Id,
|
|
194
256
|
version: z3.number().int().min(1),
|
|
257
|
+
locale: LocaleTag,
|
|
258
|
+
subject: TemplateSubject.nullable(),
|
|
259
|
+
preheader: TemplatePreheader.nullable(),
|
|
195
260
|
document: TemplateDocument,
|
|
261
|
+
translations: TemplateTranslations,
|
|
196
262
|
created_by: z3.string().nullable(),
|
|
197
263
|
created_at: Timestamp
|
|
198
264
|
});
|
|
@@ -213,7 +279,8 @@ var CompileResult = z3.object({
|
|
|
213
279
|
errors: z3.array(CompileMessage)
|
|
214
280
|
});
|
|
215
281
|
var TemplateCompileRequest = z3.object({
|
|
216
|
-
version: z3.number().int().min(1).optional()
|
|
282
|
+
version: z3.number().int().min(1).optional(),
|
|
283
|
+
locale: LocaleTag.optional()
|
|
217
284
|
});
|
|
218
285
|
var CompileRequest = z3.object({
|
|
219
286
|
document: TemplateDocument
|
|
@@ -305,7 +372,8 @@ var EXPORT_FORMATS = ["mjml", "html"];
|
|
|
305
372
|
var ExportFormat = z3.enum(EXPORT_FORMATS);
|
|
306
373
|
var TemplateExportQuery = z3.object({
|
|
307
374
|
format: ExportFormat,
|
|
308
|
-
version: z3.coerce.number().int().min(1).optional()
|
|
375
|
+
version: z3.coerce.number().int().min(1).optional(),
|
|
376
|
+
locale: LocaleTag.optional()
|
|
309
377
|
});
|
|
310
378
|
var MailingExportQuery = z3.object({ format: ExportFormat });
|
|
311
379
|
var EXPORT_CONTENT_TYPES = {
|
|
@@ -436,9 +504,29 @@ var Mailing = z5.object({
|
|
|
436
504
|
name: z5.string().max(200).nullable(),
|
|
437
505
|
subject: z5.string().min(1).max(998),
|
|
438
506
|
preheader: z5.string().max(500).nullable(),
|
|
439
|
-
/**
|
|
507
|
+
/**
|
|
508
|
+
* The template the document was taken from, if any. The snapshot is what is
|
|
509
|
+
* sent in the main language; the template's other language versions are
|
|
510
|
+
* taken, the ready ones, when the mailing starts (see "Languages" in the
|
|
511
|
+
* contract's documentation).
|
|
512
|
+
*/
|
|
440
513
|
template_id: Id.nullable(),
|
|
441
514
|
document: TemplateDocument,
|
|
515
|
+
/**
|
|
516
|
+
* The main language: what `subject`, `preheader` and `document` are written
|
|
517
|
+
* in, and what every recipient without a ready version in their own language
|
|
518
|
+
* gets. The template's main language, or the workspace's default for a
|
|
519
|
+
* mailing made from a document.
|
|
520
|
+
*/
|
|
521
|
+
locale: LocaleTag,
|
|
522
|
+
/**
|
|
523
|
+
* The language every recipient is treated as reading, or null to read each
|
|
524
|
+
* recipient's own (their `merge.locale`, else their contact's language). A
|
|
525
|
+
* letter sets it to the language its one reader wrote in; a broadcast can use
|
|
526
|
+
* it to send one language version to everyone. It is matched like anyone's
|
|
527
|
+
* language: without a ready version in it, the main language goes out.
|
|
528
|
+
*/
|
|
529
|
+
send_locale: LocaleTag.nullable(),
|
|
442
530
|
/**
|
|
443
531
|
* The topic the mailing is sent under. Null for a letter (a one-to-one
|
|
444
532
|
* mailing with at most one recipient, which belongs to no topic and whose
|
|
@@ -469,6 +557,8 @@ var MailingContent = {
|
|
|
469
557
|
name: z5.string().max(200).optional(),
|
|
470
558
|
subject: z5.string().min(1).max(998),
|
|
471
559
|
preheader: z5.string().max(500).optional(),
|
|
560
|
+
/** See `Mailing.send_locale`. */
|
|
561
|
+
send_locale: LocaleTag.nullable().optional(),
|
|
472
562
|
/**
|
|
473
563
|
* Omitted or null makes the mailing a letter: one recipient at most, sent to
|
|
474
564
|
* anybody not blocked on every topic, with an unsubscribe link that offers
|
|
@@ -481,11 +571,15 @@ var MailingContent = {
|
|
|
481
571
|
};
|
|
482
572
|
var MailingCreate = z5.object({
|
|
483
573
|
...MailingContent,
|
|
574
|
+
subject: MailingContent.subject.optional(),
|
|
484
575
|
document: TemplateDocument.optional(),
|
|
485
576
|
template_id: Id.optional()
|
|
486
577
|
}).refine((v) => v.document === void 0 !== (v.template_id === void 0), {
|
|
487
578
|
message: "exactly one of document or template_id",
|
|
488
579
|
path: ["document"]
|
|
580
|
+
}).refine((v) => v.template_id !== void 0 || v.subject !== void 0, {
|
|
581
|
+
message: "a mailing made from a document needs a subject",
|
|
582
|
+
path: ["subject"]
|
|
489
583
|
});
|
|
490
584
|
var MailingUpdate = z5.object({
|
|
491
585
|
name: z5.string().max(200).nullable().optional(),
|
|
@@ -493,6 +587,7 @@ var MailingUpdate = z5.object({
|
|
|
493
587
|
preheader: z5.string().max(500).nullable().optional(),
|
|
494
588
|
/** Null turns a draft into a letter, which it can only be with at most one recipient. */
|
|
495
589
|
topic: Slug.nullable().optional(),
|
|
590
|
+
send_locale: LocaleTag.nullable().optional(),
|
|
496
591
|
provider_id: Id.optional(),
|
|
497
592
|
metadata: MailingMetadata.optional(),
|
|
498
593
|
document: TemplateDocument.optional()
|
|
@@ -515,7 +610,8 @@ var MailingRetryFailedRequest = z5.object({
|
|
|
515
610
|
var MailingActionRequest = z5.object({}).strict();
|
|
516
611
|
var MailingTestRequest = z5.object({
|
|
517
612
|
to: Email,
|
|
518
|
-
merge: Properties.optional()
|
|
613
|
+
merge: Properties.optional(),
|
|
614
|
+
locale: LocaleTag.optional()
|
|
519
615
|
});
|
|
520
616
|
var MailingTestResult = z5.object({
|
|
521
617
|
message_id: Id,
|
|
@@ -582,9 +678,27 @@ var MessageSummary = z5.object({
|
|
|
582
678
|
error: z5.string().nullable(),
|
|
583
679
|
is_test: z5.boolean(),
|
|
584
680
|
recipient_count: z5.number().int().min(1),
|
|
681
|
+
/** The language version the message was sent in; null for messages archived before languages existed. */
|
|
682
|
+
locale: LocaleTag.nullable(),
|
|
585
683
|
created_at: Timestamp
|
|
586
684
|
});
|
|
587
685
|
var Message = MessageSummary.extend({ html: z5.string() });
|
|
686
|
+
var MailingLanguageStatus = z5.enum(["main", "ready", "draft", "not_a_workspace_language"]);
|
|
687
|
+
var MailingLanguage = z5.object({
|
|
688
|
+
locale: LocaleTag,
|
|
689
|
+
status: MailingLanguageStatus,
|
|
690
|
+
/** Queued recipients who will get this version. */
|
|
691
|
+
planned: z5.number().int().min(0),
|
|
692
|
+
/** Messages already sent in this language (tests excluded). */
|
|
693
|
+
sent: z5.number().int().min(0)
|
|
694
|
+
});
|
|
695
|
+
var MailingLanguageReport = z5.object({
|
|
696
|
+
main_locale: LocaleTag,
|
|
697
|
+
send_locale: LocaleTag.nullable(),
|
|
698
|
+
languages: z5.array(MailingLanguage),
|
|
699
|
+
fallback: z5.array(z5.object({ wanted: z5.string(), planned: z5.number().int().min(1) })),
|
|
700
|
+
no_language: z5.number().int().min(0)
|
|
701
|
+
});
|
|
588
702
|
var MessageListQuery = PageQuery.extend({
|
|
589
703
|
mailing_id: Id.optional(),
|
|
590
704
|
outcome: MessageOutcome.optional()
|
|
@@ -1094,15 +1208,22 @@ var Unchosen = z7.literal("");
|
|
|
1094
1208
|
var DraftSlug = z7.union([Slug, Unchosen]);
|
|
1095
1209
|
var DraftPropertyKey = z7.union([ContactPropertyKey, Unchosen]);
|
|
1096
1210
|
var label = { label: z7.string().max(120).nullish() };
|
|
1211
|
+
var EmailStepLanguage = z7.object({
|
|
1212
|
+
subject: z7.string().min(1).max(998),
|
|
1213
|
+
preheader: z7.string().max(500).nullable(),
|
|
1214
|
+
document: TemplateDocument
|
|
1215
|
+
}).strict();
|
|
1097
1216
|
var EmailStep = z7.object({
|
|
1098
1217
|
kind: z7.literal("email"),
|
|
1099
1218
|
...label,
|
|
1100
1219
|
...next,
|
|
1101
1220
|
template_id: Id.nullish(),
|
|
1102
1221
|
document: TemplateDocument.nullish(),
|
|
1103
|
-
/** Empty while the step is being written; `not_chosen` refuses to publish it. */
|
|
1222
|
+
/** Empty while the step is being written, or when its template has a subject; else `not_chosen` refuses to publish it. */
|
|
1104
1223
|
subject: z7.string().max(998),
|
|
1105
|
-
preheader: z7.string().max(500).nullish()
|
|
1224
|
+
preheader: z7.string().max(500).nullish(),
|
|
1225
|
+
locale: LocaleTag.nullish(),
|
|
1226
|
+
languages: z7.record(LocaleTag, EmailStepLanguage).nullish()
|
|
1106
1227
|
}).strict();
|
|
1107
1228
|
var DelayStep = z7.object({
|
|
1108
1229
|
kind: z7.literal("delay"),
|
|
@@ -1408,7 +1529,7 @@ function validateAutomation(flow, settings, ctx) {
|
|
|
1408
1529
|
if (!step.template_id && !step.document) {
|
|
1409
1530
|
add({ code: "empty_email", step_id: id, path: [...path, "template_id"], message: "This email has neither a template nor content." });
|
|
1410
1531
|
}
|
|
1411
|
-
if (step.subject.trim() === "") {
|
|
1532
|
+
if (step.subject.trim() === "" && !(step.template_id && known.templates_with_subject?.has(step.template_id))) {
|
|
1412
1533
|
add({ code: "not_chosen", step_id: id, path: [...path, "subject"], message: "This email has no subject line." });
|
|
1413
1534
|
}
|
|
1414
1535
|
if (step.template_id) checkRef(known.templates, step.template_id, "template", [...path, "template_id"], add, id);
|
|
@@ -1893,7 +2014,7 @@ var AutomationReport = z7.object({
|
|
|
1893
2014
|
var AutomationExitRunRequest = z7.object({ reason: z7.string().max(200).optional() }).strict();
|
|
1894
2015
|
var AutomationRunParams = z7.object({ id: Id, run_id: Id });
|
|
1895
2016
|
var AutomationStepParams = z7.object({ id: Id, step_id: StepId });
|
|
1896
|
-
var AutomationTestEmailRequest = z7.object({ to: Email, merge: Properties.optional() }).strict();
|
|
2017
|
+
var AutomationTestEmailRequest = z7.object({ to: Email, merge: Properties.optional(), locale: LocaleTag.optional() }).strict();
|
|
1897
2018
|
var AutomationValidateRequest = z7.object({ flow: AutomationFlow.optional(), settings: AutomationSettings.partial().optional() }).strict();
|
|
1898
2019
|
var EventCreate = z7.object({
|
|
1899
2020
|
name: EventName,
|
|
@@ -1962,10 +2083,19 @@ var MergeDefaults = z8.record(MergeFieldName, MergeDefaultValue).refine((m) => O
|
|
|
1962
2083
|
message: `a reserved merge field (${Object.keys(RESERVED_MERGE_FIELDS).join(", ")}) cannot have a workspace default`
|
|
1963
2084
|
});
|
|
1964
2085
|
var WorkspaceSettings = z8.object({
|
|
1965
|
-
/**
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
2086
|
+
/**
|
|
2087
|
+
* The workspace's main language (a {@link LocaleTag}, e.g. "de"): the main
|
|
2088
|
+
* language of a new template, and of the hosted pages when nothing else
|
|
2089
|
+
* decides.
|
|
2090
|
+
*/
|
|
2091
|
+
default_locale: LocaleTag,
|
|
2092
|
+
/**
|
|
2093
|
+
* The languages the workspace writes in, including `default_locale`, none
|
|
2094
|
+
* twice (compared without regard to casing). A template can have a version in
|
|
2095
|
+
* each; the hosted pages are offered in those of them they support (English,
|
|
2096
|
+
* German, French, Italian, Spanish). The service stores canonical casing.
|
|
2097
|
+
*/
|
|
2098
|
+
locales: z8.array(LocaleTag).min(1).max(20).refine((l) => new Set(l.map((t) => t.toLowerCase())).size === l.length, "a language appears twice"),
|
|
1969
2099
|
/** Open and click tracking. Off by default, opt-in per workspace. */
|
|
1970
2100
|
tracking_enabled: z8.boolean(),
|
|
1971
2101
|
/** Where images, stylesheets and fonts in a mail may load from. `any` by default. */
|
|
@@ -2217,6 +2347,200 @@ var InviteAccepted = z9.object({
|
|
|
2217
2347
|
});
|
|
2218
2348
|
var INVITE_REFUSAL_REASONS = ["email_mismatch", "inviter_lacks_role", "expired", "revoked", "accepted"];
|
|
2219
2349
|
|
|
2350
|
+
// src/template-languages.ts
|
|
2351
|
+
function languageTabs(mainLocale, versions, workspaceLocales) {
|
|
2352
|
+
const main = canonicalLocale(mainLocale) ?? mainLocale;
|
|
2353
|
+
const statuses = /* @__PURE__ */ new Map();
|
|
2354
|
+
for (const [tag, status] of Object.entries(versions)) {
|
|
2355
|
+
const canonical = canonicalLocale(tag) ?? tag;
|
|
2356
|
+
if (canonical !== main) statuses.set(canonical, status);
|
|
2357
|
+
}
|
|
2358
|
+
const tabs = [{ locale: main, kind: "main", status: "ready" }];
|
|
2359
|
+
const seen = /* @__PURE__ */ new Set([main]);
|
|
2360
|
+
for (const tag of workspaceLocales) {
|
|
2361
|
+
const canonical = canonicalLocale(tag) ?? tag;
|
|
2362
|
+
if (seen.has(canonical)) continue;
|
|
2363
|
+
seen.add(canonical);
|
|
2364
|
+
tabs.push({ locale: canonical, kind: "translation", status: statuses.get(canonical) ?? "missing" });
|
|
2365
|
+
}
|
|
2366
|
+
const kept = [...statuses.keys()].filter((tag) => !seen.has(tag)).sort();
|
|
2367
|
+
for (const tag of kept) tabs.push({ locale: tag, kind: "kept", status: statuses.get(tag) });
|
|
2368
|
+
return tabs;
|
|
2369
|
+
}
|
|
2370
|
+
function languageName(tag) {
|
|
2371
|
+
const canonical = canonicalLocale(tag) ?? tag;
|
|
2372
|
+
try {
|
|
2373
|
+
const names = new Intl.DisplayNames(["en"], { type: "language", fallback: "none" });
|
|
2374
|
+
const name = names.of(canonical);
|
|
2375
|
+
return name && name !== canonical ? name : canonical;
|
|
2376
|
+
} catch {
|
|
2377
|
+
return canonical;
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2380
|
+
function storedTemplateLine(value) {
|
|
2381
|
+
if (value === null || value === void 0) return null;
|
|
2382
|
+
const trimmed = value.trim();
|
|
2383
|
+
return trimmed === "" ? null : trimmed;
|
|
2384
|
+
}
|
|
2385
|
+
function buildTemplatePatch(args) {
|
|
2386
|
+
const { loaded, main, documentChanged, translations, changed, removed } = args;
|
|
2387
|
+
const out = {};
|
|
2388
|
+
const subject = storedTemplateLine(main.subject);
|
|
2389
|
+
if (subject !== storedTemplateLine(loaded.subject)) out.subject = subject;
|
|
2390
|
+
const preheader = storedTemplateLine(main.preheader);
|
|
2391
|
+
if (preheader !== storedTemplateLine(loaded.preheader)) out.preheader = preheader;
|
|
2392
|
+
if (documentChanged) out.document = main.document;
|
|
2393
|
+
const patch = {};
|
|
2394
|
+
for (const tag of changed) {
|
|
2395
|
+
const version = translations[tag];
|
|
2396
|
+
if (version && !removed.has(tag)) {
|
|
2397
|
+
patch[tag] = { subject: storedTemplateLine(version.subject), preheader: storedTemplateLine(version.preheader), document: version.document, status: version.status };
|
|
2398
|
+
}
|
|
2399
|
+
}
|
|
2400
|
+
for (const tag of removed) {
|
|
2401
|
+
if (Object.prototype.hasOwnProperty.call(loaded.translations, tag)) patch[tag] = null;
|
|
2402
|
+
}
|
|
2403
|
+
if (Object.keys(patch).length > 0) out.translations = patch;
|
|
2404
|
+
return out;
|
|
2405
|
+
}
|
|
2406
|
+
function restorePatch(old, current, workspaceLocales) {
|
|
2407
|
+
const oldMain = canonicalLocale(old.locale) ?? old.locale;
|
|
2408
|
+
const listed = workspaceLocales ? new Set(workspaceLocales.map((t) => canonicalLocale(t) ?? t)) : null;
|
|
2409
|
+
const patch = {};
|
|
2410
|
+
const skipped = [];
|
|
2411
|
+
const untouched = /* @__PURE__ */ new Set();
|
|
2412
|
+
for (const [tag, version] of Object.entries(old.translations)) {
|
|
2413
|
+
const canonical = canonicalLocale(tag) ?? tag;
|
|
2414
|
+
if (canonical === oldMain) continue;
|
|
2415
|
+
if (listed && !listed.has(canonical)) {
|
|
2416
|
+
skipped.push(canonical);
|
|
2417
|
+
untouched.add(canonical);
|
|
2418
|
+
continue;
|
|
2419
|
+
}
|
|
2420
|
+
patch[canonical] = version;
|
|
2421
|
+
}
|
|
2422
|
+
for (const tag of Object.keys(current.translations)) {
|
|
2423
|
+
const canonical = canonicalLocale(tag) ?? tag;
|
|
2424
|
+
if (!(canonical in patch) && !untouched.has(canonical)) patch[canonical] = null;
|
|
2425
|
+
if (canonical === oldMain) patch[canonical] = null;
|
|
2426
|
+
}
|
|
2427
|
+
const update = {
|
|
2428
|
+
...oldMain !== (canonicalLocale(current.locale) ?? current.locale) ? { locale: oldMain } : {},
|
|
2429
|
+
subject: old.subject,
|
|
2430
|
+
preheader: old.preheader,
|
|
2431
|
+
document: old.document,
|
|
2432
|
+
...Object.keys(patch).length > 0 ? { translations: patch } : {}
|
|
2433
|
+
};
|
|
2434
|
+
return { update, skipped: skipped.sort() };
|
|
2435
|
+
}
|
|
2436
|
+
function draftsFromTemplate(template) {
|
|
2437
|
+
const translations = {};
|
|
2438
|
+
for (const [tag, version] of Object.entries(template.translations)) translations[canonicalLocale(tag) ?? tag] = version;
|
|
2439
|
+
return {
|
|
2440
|
+
main: { subject: template.subject ?? "", preheader: template.preheader ?? "", document: template.document },
|
|
2441
|
+
translations,
|
|
2442
|
+
mainDocumentChanged: false,
|
|
2443
|
+
changed: [],
|
|
2444
|
+
removed: []
|
|
2445
|
+
};
|
|
2446
|
+
}
|
|
2447
|
+
var withChanged = (list2, tag) => list2.includes(tag) ? [...list2] : [...list2, tag];
|
|
2448
|
+
var without = (list2, tag) => list2.filter((t) => t !== tag);
|
|
2449
|
+
function draftDocument(drafts, locale) {
|
|
2450
|
+
if (locale === null) return drafts.main.document;
|
|
2451
|
+
return drafts.translations[locale]?.document;
|
|
2452
|
+
}
|
|
2453
|
+
function addLanguageDraft(drafts, locale) {
|
|
2454
|
+
const tag = canonicalLocale(locale) ?? locale;
|
|
2455
|
+
if (drafts.translations[tag]) return drafts;
|
|
2456
|
+
return {
|
|
2457
|
+
...drafts,
|
|
2458
|
+
translations: {
|
|
2459
|
+
...drafts.translations,
|
|
2460
|
+
[tag]: {
|
|
2461
|
+
subject: drafts.main.subject,
|
|
2462
|
+
preheader: drafts.main.preheader,
|
|
2463
|
+
document: structuredClone(drafts.main.document),
|
|
2464
|
+
status: "draft"
|
|
2465
|
+
}
|
|
2466
|
+
},
|
|
2467
|
+
changed: withChanged(drafts.changed, tag),
|
|
2468
|
+
removed: without(drafts.removed, tag)
|
|
2469
|
+
};
|
|
2470
|
+
}
|
|
2471
|
+
function removeLanguageDraft(drafts, locale) {
|
|
2472
|
+
const tag = canonicalLocale(locale) ?? locale;
|
|
2473
|
+
const { [tag]: _gone, ...rest } = drafts.translations;
|
|
2474
|
+
return { ...drafts, translations: rest, changed: without(drafts.changed, tag), removed: withChanged(drafts.removed, tag) };
|
|
2475
|
+
}
|
|
2476
|
+
function editLanguageDraft(drafts, locale, edit) {
|
|
2477
|
+
if (locale === null) {
|
|
2478
|
+
return {
|
|
2479
|
+
...drafts,
|
|
2480
|
+
main: { ...drafts.main, ...edit },
|
|
2481
|
+
mainDocumentChanged: drafts.mainDocumentChanged || edit.document !== void 0
|
|
2482
|
+
};
|
|
2483
|
+
}
|
|
2484
|
+
const version = drafts.translations[locale];
|
|
2485
|
+
if (!version) return drafts;
|
|
2486
|
+
const next2 = { ...version, ...edit };
|
|
2487
|
+
return { ...drafts, translations: { ...drafts.translations, [locale]: next2 }, changed: withChanged(drafts.changed, locale) };
|
|
2488
|
+
}
|
|
2489
|
+
function keepLanguageDocument(drafts, locale, document) {
|
|
2490
|
+
if (locale === null) return { ...drafts, main: { ...drafts.main, document } };
|
|
2491
|
+
const version = drafts.translations[locale];
|
|
2492
|
+
if (!version) return drafts;
|
|
2493
|
+
return { ...drafts, translations: { ...drafts.translations, [locale]: { ...version, document } } };
|
|
2494
|
+
}
|
|
2495
|
+
function setLanguageStatus(drafts, locale, status) {
|
|
2496
|
+
const version = drafts.translations[locale];
|
|
2497
|
+
if (!version || version.status === status) return drafts;
|
|
2498
|
+
return { ...drafts, translations: { ...drafts.translations, [locale]: { ...version, status } }, changed: withChanged(drafts.changed, locale) };
|
|
2499
|
+
}
|
|
2500
|
+
function draftsPatch(drafts, loaded) {
|
|
2501
|
+
return buildTemplatePatch({
|
|
2502
|
+
loaded,
|
|
2503
|
+
main: drafts.main,
|
|
2504
|
+
documentChanged: drafts.mainDocumentChanged,
|
|
2505
|
+
translations: drafts.translations,
|
|
2506
|
+
changed: new Set(drafts.changed),
|
|
2507
|
+
removed: new Set(drafts.removed)
|
|
2508
|
+
});
|
|
2509
|
+
}
|
|
2510
|
+
function draftsDirty(drafts, loaded) {
|
|
2511
|
+
return Object.keys(draftsPatch(drafts, loaded)).length > 0;
|
|
2512
|
+
}
|
|
2513
|
+
function readyWithoutSubject(drafts) {
|
|
2514
|
+
return Object.entries(drafts.translations).filter(([tag, v]) => drafts.changed.includes(tag) && v.status === "ready" && storedTemplateLine(v.subject) === null).map(([tag]) => tag);
|
|
2515
|
+
}
|
|
2516
|
+
function rebaseDrafts(sent, now, saved) {
|
|
2517
|
+
const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
2518
|
+
let out = draftsFromTemplate(saved);
|
|
2519
|
+
if (now.main.subject !== sent.main.subject) out = editLanguageDraft(out, null, { subject: now.main.subject });
|
|
2520
|
+
if (now.main.preheader !== sent.main.preheader) out = editLanguageDraft(out, null, { preheader: now.main.preheader });
|
|
2521
|
+
out = same(now.main.document, sent.main.document) ? keepLanguageDocument(out, null, now.main.document) : editLanguageDraft(out, null, { document: now.main.document });
|
|
2522
|
+
const tags = /* @__PURE__ */ new Set([...Object.keys(sent.translations), ...Object.keys(now.translations)]);
|
|
2523
|
+
for (const tag of tags) {
|
|
2524
|
+
const before = sent.translations[tag];
|
|
2525
|
+
const after = now.translations[tag];
|
|
2526
|
+
if (same(before, after)) {
|
|
2527
|
+
if (after) out = keepLanguageDocument(out, tag, after.document);
|
|
2528
|
+
continue;
|
|
2529
|
+
}
|
|
2530
|
+
if (!after) {
|
|
2531
|
+
out = removeLanguageDraft(out, tag);
|
|
2532
|
+
continue;
|
|
2533
|
+
}
|
|
2534
|
+
out = {
|
|
2535
|
+
...out,
|
|
2536
|
+
translations: { ...out.translations, [tag]: after },
|
|
2537
|
+
changed: withChanged(out.changed, tag),
|
|
2538
|
+
removed: without(out.removed, tag)
|
|
2539
|
+
};
|
|
2540
|
+
}
|
|
2541
|
+
return out;
|
|
2542
|
+
}
|
|
2543
|
+
|
|
2220
2544
|
// src/providers.ts
|
|
2221
2545
|
import { z as z10 } from "zod";
|
|
2222
2546
|
var PROVIDER_KINDS = ["smtp", "resend"];
|
|
@@ -3811,6 +4135,15 @@ var platformRoutes = {
|
|
|
3811
4135
|
access: "read",
|
|
3812
4136
|
phase: "S4"
|
|
3813
4137
|
},
|
|
4138
|
+
"mailings.languages": {
|
|
4139
|
+
method: "GET",
|
|
4140
|
+
path: "/v1/mailings/:id/languages",
|
|
4141
|
+
params: IdParams,
|
|
4142
|
+
response: MailingLanguageReport,
|
|
4143
|
+
status: 200,
|
|
4144
|
+
access: "read",
|
|
4145
|
+
phase: "S4"
|
|
4146
|
+
},
|
|
3814
4147
|
"signupForms.list": {
|
|
3815
4148
|
method: "GET",
|
|
3816
4149
|
path: "/v1/signup-forms",
|
|
@@ -4408,6 +4741,7 @@ export {
|
|
|
4408
4741
|
EXPORT_WARNING_COUNT_HEADER,
|
|
4409
4742
|
Email,
|
|
4410
4743
|
EmailStep,
|
|
4744
|
+
EmailStepLanguage,
|
|
4411
4745
|
ErrorBody,
|
|
4412
4746
|
ErrorCodeSchema,
|
|
4413
4747
|
Event,
|
|
@@ -4459,6 +4793,8 @@ export {
|
|
|
4459
4793
|
JsonValue,
|
|
4460
4794
|
LETTER_RECIPIENT_REFUSAL,
|
|
4461
4795
|
LIST_UNSUBSCRIBE_POST_VALUE,
|
|
4796
|
+
LOCALE_TAG_PATTERN,
|
|
4797
|
+
LocaleTag,
|
|
4462
4798
|
MAILING_ACTIONS,
|
|
4463
4799
|
MAILING_KINDS,
|
|
4464
4800
|
MAILING_STATUSES,
|
|
@@ -4483,12 +4819,15 @@ export {
|
|
|
4483
4819
|
MAX_MJML_ELEMENTS,
|
|
4484
4820
|
MAX_MJML_IMPORT_BYTES,
|
|
4485
4821
|
MAX_PAGE_LIMIT,
|
|
4822
|
+
MAX_PREHEADER_LENGTH,
|
|
4486
4823
|
MAX_RECIPIENTS_PER_BATCH,
|
|
4487
4824
|
MAX_RE_ENTRY_COOLDOWN_SECONDS,
|
|
4488
4825
|
MAX_SAVED_SECTIONS,
|
|
4489
4826
|
MAX_SPLIT_BRANCHES,
|
|
4490
4827
|
MAX_STEPS,
|
|
4491
4828
|
MAX_STEP_VISITS,
|
|
4829
|
+
MAX_SUBJECT_LENGTH,
|
|
4830
|
+
MAX_TEMPLATE_TRANSLATIONS,
|
|
4492
4831
|
MAX_TRIGGERS,
|
|
4493
4832
|
MEMBER_ROLES,
|
|
4494
4833
|
MERGE_FIELD_PATTERN,
|
|
@@ -4506,6 +4845,9 @@ export {
|
|
|
4506
4845
|
MailingExportQuery,
|
|
4507
4846
|
MailingFinishedData,
|
|
4508
4847
|
MailingKind,
|
|
4848
|
+
MailingLanguage,
|
|
4849
|
+
MailingLanguageReport,
|
|
4850
|
+
MailingLanguageStatus,
|
|
4509
4851
|
MailingListQuery,
|
|
4510
4852
|
MailingMetadata,
|
|
4511
4853
|
MailingRetryFailedRequest,
|
|
@@ -4606,6 +4948,7 @@ export {
|
|
|
4606
4948
|
SuppressionCreate,
|
|
4607
4949
|
SuppressionListQuery,
|
|
4608
4950
|
SuppressionReason,
|
|
4951
|
+
TEMPLATE_LANGUAGE_STATUSES,
|
|
4609
4952
|
TERMINAL_MAILING_STATUSES,
|
|
4610
4953
|
Tag,
|
|
4611
4954
|
TagAssignment,
|
|
@@ -4621,8 +4964,13 @@ export {
|
|
|
4621
4964
|
TemplateImportPreview,
|
|
4622
4965
|
TemplateImportPreviewRequest,
|
|
4623
4966
|
TemplateImportResult,
|
|
4967
|
+
TemplateLanguageStatus,
|
|
4624
4968
|
TemplateListQuery,
|
|
4969
|
+
TemplatePreheader,
|
|
4970
|
+
TemplateSubject,
|
|
4625
4971
|
TemplateSummary,
|
|
4972
|
+
TemplateTranslation,
|
|
4973
|
+
TemplateTranslations,
|
|
4626
4974
|
TemplateUpdate,
|
|
4627
4975
|
TemplateUpdatedData,
|
|
4628
4976
|
TemplateVersion,
|
|
@@ -4675,14 +5023,22 @@ export {
|
|
|
4675
5023
|
WorkspaceSettings,
|
|
4676
5024
|
WorkspaceUpdate,
|
|
4677
5025
|
acceptsIdempotencyKey,
|
|
5026
|
+
addLanguageDraft,
|
|
4678
5027
|
alwaysWaits,
|
|
4679
5028
|
automationFilterFamily,
|
|
4680
5029
|
automationRoutes,
|
|
4681
5030
|
billingRoutes,
|
|
4682
5031
|
buildPath,
|
|
5032
|
+
buildTemplatePatch,
|
|
4683
5033
|
canTransition,
|
|
5034
|
+
canonicalLocale,
|
|
4684
5035
|
cycles,
|
|
4685
5036
|
defaultSettings,
|
|
5037
|
+
draftDocument,
|
|
5038
|
+
draftsDirty,
|
|
5039
|
+
draftsFromTemplate,
|
|
5040
|
+
draftsPatch,
|
|
5041
|
+
editLanguageDraft,
|
|
4686
5042
|
emptyFlow,
|
|
4687
5043
|
errorBody,
|
|
4688
5044
|
exportFilename,
|
|
@@ -4698,6 +5054,10 @@ export {
|
|
|
4698
5054
|
isDayEntirelyPast,
|
|
4699
5055
|
isPublishable,
|
|
4700
5056
|
isRetryableError,
|
|
5057
|
+
keepLanguageDocument,
|
|
5058
|
+
languageName,
|
|
5059
|
+
languageTabs,
|
|
5060
|
+
matchLocale,
|
|
4701
5061
|
matchRoute,
|
|
4702
5062
|
missingRequiredMergeFields,
|
|
4703
5063
|
nowSeconds,
|
|
@@ -4706,12 +5066,19 @@ export {
|
|
|
4706
5066
|
parseExportWarningsHeader,
|
|
4707
5067
|
parseUsageWarningHeader,
|
|
4708
5068
|
platformRoutes,
|
|
5069
|
+
primaryLanguage,
|
|
4709
5070
|
reachable,
|
|
5071
|
+
readyWithoutSubject,
|
|
5072
|
+
rebaseDrafts,
|
|
5073
|
+
removeLanguageDraft,
|
|
5074
|
+
restorePatch,
|
|
4710
5075
|
routes,
|
|
4711
5076
|
sendingRoutes,
|
|
5077
|
+
setLanguageStatus,
|
|
4712
5078
|
signWebhook,
|
|
4713
5079
|
statusForError,
|
|
4714
5080
|
stepTargets,
|
|
5081
|
+
storedTemplateLine,
|
|
4715
5082
|
stronglyConnected,
|
|
4716
5083
|
templateRoutes,
|
|
4717
5084
|
testGroupSize,
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.5.0",
|
|
7
7
|
"description": "The v1 API contract of the mail service: zod schemas and types for every request, response, error and webhook, the route table, and webhook signing helpers",
|
|
8
8
|
"license": "MIT",
|
|
9
9
|
"repository": {
|