@aglyn/plugins-email 1.0.0-beta.221 → 1.0.0-beta.223

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 (82) hide show
  1. package/README.md +5 -4
  2. package/package.json +16 -17
  3. package/src/lib/components/email-template-detail.d.ts +5 -13
  4. package/src/lib/components/email-template-detail.js +29 -426
  5. package/src/lib/components/email-template-detail.js.map +1 -1
  6. package/src/lib/components/email-zones.d.ts +15 -0
  7. package/src/lib/components/email-zones.js +1 -0
  8. package/src/lib/components/email-zones.js.map +1 -1
  9. package/src/lib/components/list-detail-card.d.ts +0 -36
  10. package/src/lib/components/list-detail-card.js +125 -84
  11. package/src/lib/components/list-detail-card.js.map +1 -1
  12. package/src/lib/components/list-members-panel.d.ts +17 -0
  13. package/src/lib/components/list-members-panel.js +14 -0
  14. package/src/lib/components/list-members-panel.js.map +1 -1
  15. package/src/lib/components/site-package-email-preview.d.ts +13 -0
  16. package/src/lib/components/site-package-email-preview.js +47 -0
  17. package/src/lib/components/site-package-email-preview.js.map +1 -0
  18. package/src/lib/components/suppressions-card.js +75 -6
  19. package/src/lib/components/suppressions-card.js.map +1 -1
  20. package/src/lib/components/use-org-email-topics.js +2 -9
  21. package/src/lib/components/use-org-email-topics.js.map +1 -1
  22. package/src/lib/constants/site-package.d.ts +21 -0
  23. package/src/lib/constants/site-package.js +21 -0
  24. package/src/lib/constants/site-package.js.map +1 -0
  25. package/src/lib/declarations.console-server.d.ts +12 -0
  26. package/src/lib/declarations.console-server.js +73 -0
  27. package/src/lib/declarations.console-server.js.map +1 -1
  28. package/src/lib/list-import.d.ts +64 -75
  29. package/src/lib/list-import.js +43 -158
  30. package/src/lib/list-import.js.map +1 -1
  31. package/src/lib/model/email-topic-catalog.d.ts +24 -0
  32. package/src/lib/model/email-topic-catalog.js +19 -0
  33. package/src/lib/model/email-topic-catalog.js.map +1 -1
  34. package/src/lib/model/index.d.ts +0 -25
  35. package/src/lib/model/index.js +0 -27
  36. package/src/lib/model/index.js.map +1 -1
  37. package/src/lib/plugin.d.ts +8 -0
  38. package/src/lib/plugin.js +76 -2
  39. package/src/lib/plugin.js.map +1 -1
  40. package/src/lib/server/person-eraser.d.ts +39 -0
  41. package/src/lib/server/person-eraser.js +44 -0
  42. package/src/lib/server/person-eraser.js.map +1 -0
  43. package/src/lib/server-console.js +9 -9
  44. package/src/lib/server-console.js.map +1 -1
  45. package/src/lib/server-list-gate.d.ts +7 -0
  46. package/src/lib/server-list-gate.js +10 -3
  47. package/src/lib/server-list-gate.js.map +1 -1
  48. package/src/lib/server-list-import.d.ts +20 -177
  49. package/src/lib/server-list-import.js +34 -594
  50. package/src/lib/server-list-import.js.map +1 -1
  51. package/src/lib/server.js +16 -29
  52. package/src/lib/server.js.map +1 -1
  53. package/src/lib/testing/stand-in-send-tally.d.ts +29 -0
  54. package/src/lib/testing/stand-in-send-tally.js +52 -0
  55. package/src/lib/testing/stand-in-send-tally.js.map +1 -0
  56. package/src/lib/transfer/email-transfer-catalog.d.ts +151 -0
  57. package/src/lib/transfer/email-transfer-catalog.js +536 -0
  58. package/src/lib/transfer/email-transfer-catalog.js.map +1 -0
  59. package/src/lib/transfer/list-import-steps.d.ts +44 -0
  60. package/src/lib/transfer/list-import-steps.js +201 -0
  61. package/src/lib/transfer/list-import-steps.js.map +1 -0
  62. package/src/lib/transfer/list-members.server.d.ts +165 -0
  63. package/src/lib/transfer/list-members.server.js +911 -0
  64. package/src/lib/transfer/list-members.server.js.map +1 -0
  65. package/src/lib/transfer/suppressions.server.d.ts +69 -0
  66. package/src/lib/transfer/suppressions.server.js +282 -0
  67. package/src/lib/transfer/suppressions.server.js.map +1 -0
  68. package/src/lib/transfer/topics-package.d.ts +56 -0
  69. package/src/lib/transfer/topics-package.js +87 -0
  70. package/src/lib/transfer/topics-package.js.map +1 -0
  71. package/src/lib/transfer/topics-package.server.d.ts +33 -0
  72. package/src/lib/transfer/topics-package.server.js +171 -0
  73. package/src/lib/transfer/topics-package.server.js.map +1 -0
  74. package/src/lib/transfer/transfer-reads.server.d.ts +25 -0
  75. package/src/lib/transfer/transfer-reads.server.js +34 -0
  76. package/src/lib/transfer/transfer-reads.server.js.map +1 -0
  77. package/src/lib/components/list-import-drawer.d.ts +0 -22
  78. package/src/lib/components/list-import-drawer.js +0 -662
  79. package/src/lib/components/list-import-drawer.js.map +0 -1
  80. package/src/lib/model/template-report.d.ts +0 -158
  81. package/src/lib/model/template-report.js +0 -249
  82. package/src/lib/model/template-report.js.map +0 -1
@@ -0,0 +1,56 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import { type SubscriptionTopic } from '@aglyn/aglyn/app-utils/subscription-topics';
18
+ import type { PackageDependency } from '@aglyn/aglyn/data-transfer/package';
19
+ /** The resource key, and so the kind every topic item carries — the kind campaigns and automations already name. */
20
+ export declare const EMAIL_TOPICS_TRANSFER_KEY = "email.topics";
21
+ /** A topic as a package carries it. */
22
+ export interface EmailTopicPackageContent {
23
+ name: string;
24
+ description: string;
25
+ archived: boolean;
26
+ /** The topic's own confirmation setting, or `null` for "whatever the site says". */
27
+ doubleOptIn: boolean | null;
28
+ }
29
+ /** A topic's package content from the catalog, a stored document or an incoming item. */
30
+ export declare function emailTopicPackageContent(source: unknown): EmailTopicPackageContent;
31
+ /** A topic of the merged catalog as a package item. */
32
+ export declare function emailTopicPackageItem(topic: SubscriptionTopic): {
33
+ kind: string;
34
+ id: string;
35
+ name: string;
36
+ content: EmailTopicPackageContent;
37
+ };
38
+ /** A topic names nothing another item could carry. */
39
+ export declare function emailTopicDependencies(): PackageDependency[];
40
+ /** Nothing in a topic is a reference, so it travels as it is. */
41
+ export declare function remapEmailTopicIds(content: EmailTopicPackageContent): EmailTopicPackageContent;
42
+ /** Why one topic cannot be written under `id`, each a sentence. */
43
+ export declare function emailTopicPackageProblems(content: EmailTopicPackageContent, id: string): string[];
44
+ export declare const EMAIL_TOPIC_PACKAGE_RULES: readonly [{
45
+ readonly id: "no-subscriptions";
46
+ readonly label: "Nobody is signed up";
47
+ readonly reason: "A package carries what a topic is called and what it promises, never who chose it. Importing one writes no subscription, asks nobody to confirm and sends no email.";
48
+ }, {
49
+ readonly id: "choices-stay";
50
+ readonly label: "Everyone’s choices stay as they are";
51
+ readonly reason: "Replacing a topic keeps its id, so every opt-out, confirmation and unsubscribe link already sent keeps pointing at it. Who left a topic, on which site, is never in a package and never changed by one.";
52
+ }, {
53
+ readonly id: "retire";
54
+ readonly label: "Undo retires a topic, never deletes it";
55
+ readonly reason: "An email sent under a topic carries its id in the unsubscribe link, so a topic is never removed. Undoing an import retires a topic it added, which takes it out of the composer and the preference page.";
56
+ }];
@@ -0,0 +1,87 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ /*==========================================
17
+ * EMAIL TOPICS AS PACKAGE ITEMS (AGL-3550).
18
+ *
19
+ * A topic is a handful of words edited in place — its name, what a
20
+ * recipient is told they get, whether it is retired, and whether joining
21
+ * it needs a confirmation click — so it travels as a package item rather
22
+ * than as records. What a person chose about a topic (an opt-out, a
23
+ * pending confirmation) is theirs and per site, and never in a package.
24
+ *
25
+ * The catalog an item is read from is the one every reader sees
26
+ * (`mergeSubscriptionTopics`): the built-in topics are in it whether or
27
+ * not anybody changed one, so a campaign or an automation that names a
28
+ * built-in always finds it, and a built-in exported unchanged imports as
29
+ * the same.
30
+ *
31
+ * A topic names nothing. What names a topic — a campaign, an org
32
+ * automation step — declares it as an `email.topics` dependency, so a
33
+ * kept-both copy is what those items in the same package point at.
34
+ *
35
+ * Pure: the server half (`topics-package.server.ts`) reads and writes.
36
+ *=========================================*/ import { isSubscriptionTopicId } from "@aglyn/aglyn/app-utils/subscription-topics";
37
+ /** The resource key, and so the kind every topic item carries — the kind campaigns and automations already name. */ export const EMAIL_TOPICS_TRANSFER_KEY = 'email.topics';
38
+ /** A topic's package content from the catalog, a stored document or an incoming item. */ export function emailTopicPackageContent(source) {
39
+ const raw = source && typeof source === 'object' ? source : {};
40
+ return {
41
+ name: typeof raw['name'] === 'string' ? raw['name'].trim() : '',
42
+ description: typeof raw['description'] === 'string' ? raw['description'].trim() : '',
43
+ archived: raw['archived'] === true,
44
+ doubleOptIn: typeof raw['doubleOptIn'] === 'boolean' ? raw['doubleOptIn'] : null
45
+ };
46
+ }
47
+ /** A topic of the merged catalog as a package item. */ export function emailTopicPackageItem(topic) {
48
+ const content = emailTopicPackageContent(topic);
49
+ return {
50
+ kind: EMAIL_TOPICS_TRANSFER_KEY,
51
+ id: topic.id,
52
+ name: content.name,
53
+ content
54
+ };
55
+ }
56
+ /** A topic names nothing another item could carry. */ export function emailTopicDependencies() {
57
+ return [];
58
+ }
59
+ /** Nothing in a topic is a reference, so it travels as it is. */ export function remapEmailTopicIds(content) {
60
+ return content;
61
+ }
62
+ /** Why one topic cannot be written under `id`, each a sentence. */ export function emailTopicPackageProblems(content, id) {
63
+ const problems = [];
64
+ if (!content.name) problems.push('Name the topic.');
65
+ // The id is signed into every unsubscribe link the topic mints; one the send path refuses could never be sent under.
66
+ if (!isSubscriptionTopicId(id)) problems.push('This topic’s id cannot be used in an unsubscribe link.');
67
+ return problems;
68
+ }
69
+ export const EMAIL_TOPIC_PACKAGE_RULES = [
70
+ {
71
+ id: 'no-subscriptions',
72
+ label: 'Nobody is signed up',
73
+ reason: 'A package carries what a topic is called and what it promises, never who chose it. Importing one writes no subscription, asks nobody to confirm and sends no email.'
74
+ },
75
+ {
76
+ id: 'choices-stay',
77
+ label: 'Everyone’s choices stay as they are',
78
+ reason: 'Replacing a topic keeps its id, so every opt-out, confirmation and unsubscribe link already sent keeps pointing at it. Who left a topic, on which site, is never in a package and never changed by one.'
79
+ },
80
+ {
81
+ id: 'retire',
82
+ label: 'Undo retires a topic, never deletes it',
83
+ reason: 'An email sent under a topic carries its id in the unsubscribe link, so a topic is never removed. Undoing an import retires a topic it added, which takes it out of the composer and the preference page.'
84
+ }
85
+ ];
86
+
87
+ //# sourceMappingURL=topics-package.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/email/src/lib/transfer/topics-package.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/*==========================================\n * EMAIL TOPICS AS PACKAGE ITEMS (AGL-3550).\n *\n * A topic is a handful of words edited in place — its name, what a\n * recipient is told they get, whether it is retired, and whether joining\n * it needs a confirmation click — so it travels as a package item rather\n * than as records. What a person chose about a topic (an opt-out, a\n * pending confirmation) is theirs and per site, and never in a package.\n *\n * The catalog an item is read from is the one every reader sees\n * (`mergeSubscriptionTopics`): the built-in topics are in it whether or\n * not anybody changed one, so a campaign or an automation that names a\n * built-in always finds it, and a built-in exported unchanged imports as\n * the same.\n *\n * A topic names nothing. What names a topic — a campaign, an org\n * automation step — declares it as an `email.topics` dependency, so a\n * kept-both copy is what those items in the same package point at.\n *\n * Pure: the server half (`topics-package.server.ts`) reads and writes.\n *=========================================*/\n\nimport { isSubscriptionTopicId, type SubscriptionTopic } from '@aglyn/aglyn/app-utils/subscription-topics'\nimport type { PackageDependency } from '@aglyn/aglyn/data-transfer/package'\n\n/** The resource key, and so the kind every topic item carries — the kind campaigns and automations already name. */\nexport const EMAIL_TOPICS_TRANSFER_KEY = 'email.topics'\n\n/** A topic as a package carries it. */\nexport interface EmailTopicPackageContent {\n name: string\n description: string\n archived: boolean\n /** The topic's own confirmation setting, or `null` for \"whatever the site says\". */\n doubleOptIn: boolean | null\n}\n\n/** A topic's package content from the catalog, a stored document or an incoming item. */\nexport function emailTopicPackageContent(source: unknown): EmailTopicPackageContent {\n const raw = (source && typeof source === 'object' ? source : {}) as Record<string, unknown>\n return {\n name: typeof raw['name'] === 'string' ? raw['name'].trim() : '',\n description: typeof raw['description'] === 'string' ? raw['description'].trim() : '',\n archived: raw['archived'] === true,\n doubleOptIn: typeof raw['doubleOptIn'] === 'boolean' ? raw['doubleOptIn'] : null,\n }\n}\n\n/** A topic of the merged catalog as a package item. */\nexport function emailTopicPackageItem(topic: SubscriptionTopic) {\n const content = emailTopicPackageContent(topic)\n return { kind: EMAIL_TOPICS_TRANSFER_KEY, id: topic.id, name: content.name, content }\n}\n\n/** A topic names nothing another item could carry. */\nexport function emailTopicDependencies(): PackageDependency[] {\n return []\n}\n\n/** Nothing in a topic is a reference, so it travels as it is. */\nexport function remapEmailTopicIds(content: EmailTopicPackageContent): EmailTopicPackageContent {\n return content\n}\n\n/** Why one topic cannot be written under `id`, each a sentence. */\nexport function emailTopicPackageProblems(content: EmailTopicPackageContent, id: string): string[] {\n const problems: string[] = []\n if (!content.name) problems.push('Name the topic.')\n // The id is signed into every unsubscribe link the topic mints; one the send path refuses could never be sent under.\n if (!isSubscriptionTopicId(id)) problems.push('This topic’s id cannot be used in an unsubscribe link.')\n return problems\n}\n\nexport const EMAIL_TOPIC_PACKAGE_RULES = [\n {\n id: 'no-subscriptions',\n label: 'Nobody is signed up',\n reason:\n 'A package carries what a topic is called and what it promises, never who chose it. Importing one writes no subscription, asks nobody to confirm and sends no email.',\n },\n {\n id: 'choices-stay',\n label: 'Everyone’s choices stay as they are',\n reason:\n 'Replacing a topic keeps its id, so every opt-out, confirmation and unsubscribe link already sent keeps pointing at it. Who left a topic, on which site, is never in a package and never changed by one.',\n },\n {\n id: 'retire',\n label: 'Undo retires a topic, never deletes it',\n reason:\n 'An email sent under a topic carries its id in the unsubscribe link, so a topic is never removed. Undoing an import retires a topic it added, which takes it out of the composer and the preference page.',\n },\n] as const\n"],"names":["isSubscriptionTopicId","EMAIL_TOPICS_TRANSFER_KEY","emailTopicPackageContent","source","raw","name","trim","description","archived","doubleOptIn","emailTopicPackageItem","topic","content","kind","id","emailTopicDependencies","remapEmailTopicIds","emailTopicPackageProblems","problems","push","EMAIL_TOPIC_PACKAGE_RULES","label","reason"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;;;2CAoB2C,GAE3C,SAASA,qBAAqB,QAAgC,6CAA4C;AAG1G,kHAAkH,GAClH,OAAO,MAAMC,4BAA4B,eAAc;AAWvD,uFAAuF,GACvF,OAAO,SAASC,yBAAyBC,MAAe;IACtD,MAAMC,MAAOD,UAAU,OAAOA,WAAW,WAAWA,SAAS,CAAC;IAC9D,OAAO;QACLE,MAAM,OAAOD,GAAG,CAAC,OAAO,KAAK,WAAWA,GAAG,CAAC,OAAO,CAACE,IAAI,KAAK;QAC7DC,aAAa,OAAOH,GAAG,CAAC,cAAc,KAAK,WAAWA,GAAG,CAAC,cAAc,CAACE,IAAI,KAAK;QAClFE,UAAUJ,GAAG,CAAC,WAAW,KAAK;QAC9BK,aAAa,OAAOL,GAAG,CAAC,cAAc,KAAK,YAAYA,GAAG,CAAC,cAAc,GAAG;IAC9E;AACF;AAEA,qDAAqD,GACrD,OAAO,SAASM,sBAAsBC,KAAwB;IAC5D,MAAMC,UAAUV,yBAAyBS;IACzC,OAAO;QAAEE,MAAMZ;QAA2Ba,IAAIH,MAAMG,EAAE;QAAET,MAAMO,QAAQP,IAAI;QAAEO;IAAQ;AACtF;AAEA,oDAAoD,GACpD,OAAO,SAASG;IACd,OAAO,EAAE;AACX;AAEA,+DAA+D,GAC/D,OAAO,SAASC,mBAAmBJ,OAAiC;IAClE,OAAOA;AACT;AAEA,iEAAiE,GACjE,OAAO,SAASK,0BAA0BL,OAAiC,EAAEE,EAAU;IACrF,MAAMI,WAAqB,EAAE;IAC7B,IAAI,CAACN,QAAQP,IAAI,EAAEa,SAASC,IAAI,CAAC;IACjC,qHAAqH;IACrH,IAAI,CAACnB,sBAAsBc,KAAKI,SAASC,IAAI,CAAC;IAC9C,OAAOD;AACT;AAEA,OAAO,MAAME,4BAA4B;IACvC;QACEN,IAAI;QACJO,OAAO;QACPC,QACE;IACJ;IACA;QACER,IAAI;QACJO,OAAO;QACPC,QACE;IACJ;IACA;QACER,IAAI;QACJO,OAAO;QACPC,QACE;IACJ;CACD,CAAS"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import { type ExistingPackageItem } from '@aglyn/aglyn/data-transfer';
18
+ import type { TransferApplyResult, TransferApplyWriter, TransferPackageItemContent, TransferPackageItemWrite, TransferPackageRevertResult, TransferPackageRevertStep, TransferResourceContext } from '@aglyn/aglyn/plugin-manager/plugin-transfer-resources';
19
+ import { type EmailTopicPackageContent } from './topics-package';
20
+ type Firestore = FirebaseFirestore.Firestore;
21
+ export interface EmailTopicsPackageDeps {
22
+ firestore(): Firestore;
23
+ /** What clears a field in a merged write (`FieldValue.delete()`). */
24
+ deleteField(): unknown;
25
+ }
26
+ export declare function createEmailTopicsPackage(deps?: EmailTopicsPackageDeps): {
27
+ items(ctx: TransferResourceContext): Promise<ExistingPackageItem[]>;
28
+ readItems(ctx: TransferResourceContext, ids: readonly string[]): Promise<TransferPackageItemContent<EmailTopicPackageContent>[]>;
29
+ problems(_ctx: TransferResourceContext, write: TransferPackageItemWrite<EmailTopicPackageContent>): string[];
30
+ writeItems(ctx: TransferResourceContext, items: ReadonlyArray<TransferPackageItemWrite<EmailTopicPackageContent>>, writer: TransferApplyWriter): Promise<TransferApplyResult>;
31
+ revertItems(ctx: TransferResourceContext, steps: ReadonlyArray<TransferPackageRevertStep<EmailTopicPackageContent>>): Promise<TransferPackageRevertResult>;
32
+ };
33
+ export {};
@@ -0,0 +1,171 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ /**
3
+ * @license
4
+ * Copyright 2026 Aglyn LLC
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this file except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ */ /*==========================================
18
+ * EMAIL TOPICS IN A WORKSPACE PACKAGE — the server half (AGL-3550).
19
+ *
20
+ * Written the way the topic's own page saves one: `emailTopicDocument`,
21
+ * merged into `orgs/{orgId}/emailTopics/{topicId}` — the document
22
+ * `writeEmailTopic` stores — so a built-in replaced by an import is an
23
+ * override at the built-in's id, exactly as one renamed in the console is.
24
+ * Only that document is ever written: no opt-out, no confirmation, nothing
25
+ * under a site.
26
+ *
27
+ * Undo puts a replaced topic back. A topic the import added is retired
28
+ * rather than deleted, for the reason the console has no delete: an email
29
+ * sent under it in the meantime carries its id in an unsubscribe link, and
30
+ * that link has to go on naming something.
31
+ *=========================================*/ import { DECLARED_SUBSCRIPTION_TOPICS, mergeSubscriptionTopics } from "@aglyn/aglyn/app-utils/subscription-topics";
32
+ import { existingPackageItemsOf } from "@aglyn/aglyn/data-transfer";
33
+ import firebaseAdmin from "@aglyn/tenant-data-admin/server/firebase-admin";
34
+ import { FieldValue } from "firebase-admin/firestore";
35
+ import { EMAIL_TOPICS_COLLECTION, emailTopicDocument, emailTopicsFromDocs } from "../model/email-topic-catalog.js";
36
+ import { EMAIL_TOPICS_TRANSFER_KEY, emailTopicPackageContent, emailTopicPackageItem, emailTopicPackageProblems } from "./topics-package.js";
37
+ function defaultDeps() {
38
+ return {
39
+ firestore: ()=>firebaseAdmin.app().firestore(),
40
+ deleteField: ()=>FieldValue.delete()
41
+ };
42
+ }
43
+ const topicsOf = (firestore, orgId)=>firestore.collection('orgs').doc(orgId).collection(EMAIL_TOPICS_COLLECTION);
44
+ const isBuiltIn = (id)=>DECLARED_SUBSCRIPTION_TOPICS.some((topic)=>topic.id === id);
45
+ /** The catalog as every reader sees it — the built-ins overlaid by what is stored, then the org's own. */ async function readAll(deps, orgId, ids) {
46
+ const stored = await topicsOf(deps.firestore(), orgId).get();
47
+ const catalog = mergeSubscriptionTopics(emailTopicsFromDocs(stored.docs));
48
+ const wanted = ids ? new Set(ids) : null;
49
+ return catalog.filter((topic)=>!wanted || wanted.has(topic.id)).map(emailTopicPackageItem);
50
+ }
51
+ export function createEmailTopicsPackage(deps = defaultDeps()) {
52
+ /** The complete statement of a topic, `null` confirmation cleared back to the site's setting. */ const documentOf = (content)=>emailTopicDocument(content, deps.deleteField());
53
+ return {
54
+ async items (ctx) {
55
+ return existingPackageItemsOf(EMAIL_TOPICS_TRANSFER_KEY, await readAll(deps, ctx.orgId));
56
+ },
57
+ async readItems (ctx, ids) {
58
+ return readAll(deps, ctx.orgId, ids);
59
+ },
60
+ problems (_ctx, write) {
61
+ var _ref;
62
+ var _write_rename;
63
+ const content = emailTopicPackageContent(write.content);
64
+ return emailTopicPackageProblems(_extends({}, content, {
65
+ name: (_ref = (_write_rename = write.rename) == null ? void 0 : _write_rename.name) != null ? _ref : content.name
66
+ }), write.targetId);
67
+ },
68
+ async writeItems (ctx, items, writer) {
69
+ const firestore = deps.firestore();
70
+ const topics = topicsOf(firestore, ctx.orgId);
71
+ const results = [];
72
+ for (const write of items){
73
+ var _ref;
74
+ var _write_rename;
75
+ const before = await writer.alreadyApplied(write.row);
76
+ if (before) {
77
+ results.push(before);
78
+ continue;
79
+ }
80
+ const incoming = emailTopicPackageContent(write.content);
81
+ const content = _extends({}, incoming, {
82
+ name: (_ref = (_write_rename = write.rename) == null ? void 0 : _write_rename.name) != null ? _ref : incoming.name
83
+ });
84
+ const problems = emailTopicPackageProblems(content, write.targetId);
85
+ let result;
86
+ if (problems.length) {
87
+ result = {
88
+ row: write.row,
89
+ outcome: 'failed',
90
+ message: problems.join(' ')
91
+ };
92
+ } else {
93
+ const ref = topics.doc(write.targetId);
94
+ result = await firestore.runTransaction(async (transaction)=>{
95
+ const current = await transaction.get(ref);
96
+ // A built-in is in the catalog with no document at all, so it exists either way.
97
+ const held = current.exists || isBuiltIn(write.targetId);
98
+ if (write.decision === 'replace') {
99
+ if (!held) return {
100
+ row: write.row,
101
+ outcome: 'failed',
102
+ recordId: write.targetId,
103
+ message: 'That topic no longer exists.'
104
+ };
105
+ transaction.set(ref, documentOf(content), {
106
+ merge: true
107
+ });
108
+ return {
109
+ row: write.row,
110
+ outcome: 'updated',
111
+ recordId: write.targetId
112
+ };
113
+ }
114
+ if (held) {
115
+ return {
116
+ row: write.row,
117
+ outcome: 'failed',
118
+ recordId: write.targetId,
119
+ message: 'A topic with this id already exists.'
120
+ };
121
+ }
122
+ transaction.create(ref, documentOf(content));
123
+ return {
124
+ row: write.row,
125
+ outcome: 'created',
126
+ recordId: write.targetId
127
+ };
128
+ });
129
+ }
130
+ await writer.markApplied(result);
131
+ results.push(result);
132
+ }
133
+ return {
134
+ results,
135
+ undo: []
136
+ };
137
+ },
138
+ async revertItems (ctx, steps) {
139
+ const topics = topicsOf(deps.firestore(), ctx.orgId);
140
+ const outcome = {
141
+ done: [],
142
+ refused: []
143
+ };
144
+ for (const step of steps){
145
+ const ref = topics.doc(step.id);
146
+ if (step.action === 'restore') {
147
+ const previous = emailTopicPackageContent(step.content);
148
+ const builtIn = DECLARED_SUBSCRIPTION_TOPICS.find((topic)=>topic.id === step.id);
149
+ const asDeclared = builtIn && previous.name === builtIn.name && previous.description === builtIn.description && !previous.archived && previous.doubleOptIn === null;
150
+ // A built-in nobody had changed held no document, and goes back to holding none.
151
+ if (asDeclared) await ref.delete();
152
+ else await ref.set(documentOf(previous), {
153
+ merge: true
154
+ });
155
+ outcome.done.push(step.id);
156
+ continue;
157
+ }
158
+ const current = await ref.get();
159
+ if (current.exists) await ref.set({
160
+ archived: true
161
+ }, {
162
+ merge: true
163
+ });
164
+ outcome.done.push(step.id);
165
+ }
166
+ return outcome;
167
+ }
168
+ };
169
+ }
170
+
171
+ //# sourceMappingURL=topics-package.server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/email/src/lib/transfer/topics-package.server.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/*==========================================\n * EMAIL TOPICS IN A WORKSPACE PACKAGE — the server half (AGL-3550).\n *\n * Written the way the topic's own page saves one: `emailTopicDocument`,\n * merged into `orgs/{orgId}/emailTopics/{topicId}` — the document\n * `writeEmailTopic` stores — so a built-in replaced by an import is an\n * override at the built-in's id, exactly as one renamed in the console is.\n * Only that document is ever written: no opt-out, no confirmation, nothing\n * under a site.\n *\n * Undo puts a replaced topic back. A topic the import added is retired\n * rather than deleted, for the reason the console has no delete: an email\n * sent under it in the meantime carries its id in an unsubscribe link, and\n * that link has to go on naming something.\n *=========================================*/\n\nimport {\n DECLARED_SUBSCRIPTION_TOPICS,\n mergeSubscriptionTopics,\n} from '@aglyn/aglyn/app-utils/subscription-topics'\nimport {\n existingPackageItemsOf,\n type ExistingPackageItem,\n type TransferRowResult,\n} from '@aglyn/aglyn/data-transfer'\nimport type {\n TransferApplyResult,\n TransferApplyWriter,\n TransferPackageItemContent,\n TransferPackageItemWrite,\n TransferPackageRevertResult,\n TransferPackageRevertStep,\n TransferResourceContext,\n} from '@aglyn/aglyn/plugin-manager/plugin-transfer-resources'\nimport firebaseAdmin from '@aglyn/tenant-data-admin/server/firebase-admin'\nimport { FieldValue } from 'firebase-admin/firestore'\nimport { EMAIL_TOPICS_COLLECTION, emailTopicDocument, emailTopicsFromDocs } from '../model/email-topic-catalog'\nimport {\n EMAIL_TOPICS_TRANSFER_KEY,\n emailTopicPackageContent,\n emailTopicPackageItem,\n emailTopicPackageProblems,\n type EmailTopicPackageContent,\n} from './topics-package'\n\ntype Firestore = FirebaseFirestore.Firestore\n\nexport interface EmailTopicsPackageDeps {\n firestore(): Firestore\n /** What clears a field in a merged write (`FieldValue.delete()`). */\n deleteField(): unknown\n}\n\nfunction defaultDeps(): EmailTopicsPackageDeps {\n return {\n firestore: () => firebaseAdmin.app().firestore() as unknown as Firestore,\n deleteField: () => FieldValue.delete(),\n }\n}\n\nconst topicsOf = (firestore: Firestore, orgId: string) =>\n firestore.collection('orgs').doc(orgId).collection(EMAIL_TOPICS_COLLECTION)\n\nconst isBuiltIn = (id: string) => DECLARED_SUBSCRIPTION_TOPICS.some((topic) => topic.id === id)\n\n/** The catalog as every reader sees it — the built-ins overlaid by what is stored, then the org's own. */\nasync function readAll(\n deps: EmailTopicsPackageDeps,\n orgId: string,\n ids?: readonly string[],\n): Promise<Array<TransferPackageItemContent<EmailTopicPackageContent>>> {\n const stored = await topicsOf(deps.firestore(), orgId).get()\n const catalog = mergeSubscriptionTopics(emailTopicsFromDocs(stored.docs))\n const wanted = ids ? new Set(ids) : null\n return catalog.filter((topic) => !wanted || wanted.has(topic.id)).map(emailTopicPackageItem)\n}\n\nexport function createEmailTopicsPackage(deps: EmailTopicsPackageDeps = defaultDeps()) {\n /** The complete statement of a topic, `null` confirmation cleared back to the site's setting. */\n const documentOf = (content: EmailTopicPackageContent) => emailTopicDocument(content, deps.deleteField())\n\n return {\n async items(ctx: TransferResourceContext): Promise<ExistingPackageItem[]> {\n return existingPackageItemsOf(EMAIL_TOPICS_TRANSFER_KEY, await readAll(deps, ctx.orgId))\n },\n\n async readItems(ctx: TransferResourceContext, ids: readonly string[]) {\n return readAll(deps, ctx.orgId, ids)\n },\n\n problems(_ctx: TransferResourceContext, write: TransferPackageItemWrite<EmailTopicPackageContent>) {\n const content = emailTopicPackageContent(write.content)\n return emailTopicPackageProblems({ ...content, name: write.rename?.name ?? content.name }, write.targetId)\n },\n\n async writeItems(\n ctx: TransferResourceContext,\n items: ReadonlyArray<TransferPackageItemWrite<EmailTopicPackageContent>>,\n writer: TransferApplyWriter,\n ): Promise<TransferApplyResult> {\n const firestore = deps.firestore()\n const topics = topicsOf(firestore, ctx.orgId)\n const results: TransferRowResult[] = []\n for (const write of items) {\n const before = await writer.alreadyApplied(write.row)\n if (before) {\n results.push(before)\n continue\n }\n const incoming = emailTopicPackageContent(write.content)\n const content = { ...incoming, name: write.rename?.name ?? incoming.name }\n const problems = emailTopicPackageProblems(content, write.targetId)\n let result: TransferRowResult\n if (problems.length) {\n result = { row: write.row, outcome: 'failed', message: problems.join(' ') }\n } else {\n const ref = topics.doc(write.targetId)\n result = await firestore.runTransaction(async (transaction): Promise<TransferRowResult> => {\n const current = await transaction.get(ref)\n // A built-in is in the catalog with no document at all, so it exists either way.\n const held = current.exists || isBuiltIn(write.targetId)\n if (write.decision === 'replace') {\n if (!held) return { row: write.row, outcome: 'failed', recordId: write.targetId, message: 'That topic no longer exists.' }\n transaction.set(ref, documentOf(content), { merge: true })\n return { row: write.row, outcome: 'updated', recordId: write.targetId }\n }\n if (held) {\n return { row: write.row, outcome: 'failed', recordId: write.targetId, message: 'A topic with this id already exists.' }\n }\n transaction.create(ref, documentOf(content))\n return { row: write.row, outcome: 'created', recordId: write.targetId }\n })\n }\n await writer.markApplied(result)\n results.push(result)\n }\n return { results, undo: [] }\n },\n\n async revertItems(\n ctx: TransferResourceContext,\n steps: ReadonlyArray<TransferPackageRevertStep<EmailTopicPackageContent>>,\n ): Promise<TransferPackageRevertResult> {\n const topics = topicsOf(deps.firestore(), ctx.orgId)\n const outcome: TransferPackageRevertResult = { done: [], refused: [] }\n for (const step of steps) {\n const ref = topics.doc(step.id)\n if (step.action === 'restore') {\n const previous = emailTopicPackageContent(step.content)\n const builtIn = DECLARED_SUBSCRIPTION_TOPICS.find((topic) => topic.id === step.id)\n const asDeclared =\n builtIn &&\n previous.name === builtIn.name &&\n previous.description === builtIn.description &&\n !previous.archived &&\n previous.doubleOptIn === null\n // A built-in nobody had changed held no document, and goes back to holding none.\n if (asDeclared) await ref.delete()\n else await ref.set(documentOf(previous), { merge: true })\n outcome.done.push(step.id)\n continue\n }\n const current = await ref.get()\n if (current.exists) await ref.set({ archived: true }, { merge: true })\n outcome.done.push(step.id)\n }\n return outcome\n },\n }\n}\n"],"names":["DECLARED_SUBSCRIPTION_TOPICS","mergeSubscriptionTopics","existingPackageItemsOf","firebaseAdmin","FieldValue","EMAIL_TOPICS_COLLECTION","emailTopicDocument","emailTopicsFromDocs","EMAIL_TOPICS_TRANSFER_KEY","emailTopicPackageContent","emailTopicPackageItem","emailTopicPackageProblems","defaultDeps","firestore","app","deleteField","delete","topicsOf","orgId","collection","doc","isBuiltIn","id","some","topic","readAll","deps","ids","stored","get","catalog","docs","wanted","Set","filter","has","map","createEmailTopicsPackage","documentOf","content","items","ctx","readItems","problems","_ctx","write","name","rename","targetId","writeItems","writer","topics","results","before","alreadyApplied","row","push","incoming","result","length","outcome","message","join","ref","runTransaction","transaction","current","held","exists","decision","recordId","set","merge","create","markApplied","undo","revertItems","steps","done","refused","step","action","previous","builtIn","find","asDeclared","description","archived","doubleOptIn"],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ /** What both of this plugin's transfer resources read the same way. */
18
+ /** `items` in slices of `size`. */
19
+ export declare function slices<T>(items: readonly T[], size: number): T[][];
20
+ /**
21
+ * How many of the selected ids name a document — what an export of the
22
+ * selection will hold, so one holding a since-removed id is not promised a
23
+ * row it lacks.
24
+ */
25
+ export declare function countExisting(collection: FirebaseFirestore.CollectionReference, ids: readonly string[]): Promise<number>;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ /** What both of this plugin's transfer resources read the same way. */ /** `items` in slices of `size`. */ export function slices(items, size) {
17
+ const out = [];
18
+ for(let at = 0; at < items.length; at += size)out.push(items.slice(at, at + size));
19
+ return out;
20
+ }
21
+ /**
22
+ * How many of the selected ids name a document — what an export of the
23
+ * selection will hold, so one holding a since-removed id is not promised a
24
+ * row it lacks.
25
+ */ export async function countExisting(collection, ids) {
26
+ let found = 0;
27
+ for (const chunk of slices(ids.filter((id)=>id && !id.includes('/')), 300)){
28
+ const snapshots = await collection.firestore.getAll(...chunk.map((id)=>collection.doc(id)));
29
+ found += snapshots.filter((snapshot)=>snapshot.exists).length;
30
+ }
31
+ return found;
32
+ }
33
+
34
+ //# sourceMappingURL=transfer-reads.server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/email/src/lib/transfer/transfer-reads.server.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/** What both of this plugin's transfer resources read the same way. */\n\n/** `items` in slices of `size`. */\nexport function slices<T>(items: readonly T[], size: number): T[][] {\n const out: T[][] = []\n for (let at = 0; at < items.length; at += size) out.push(items.slice(at, at + size))\n return out\n}\n\n/**\n * How many of the selected ids name a document — what an export of the\n * selection will hold, so one holding a since-removed id is not promised a\n * row it lacks.\n */\nexport async function countExisting(\n collection: FirebaseFirestore.CollectionReference,\n ids: readonly string[],\n): Promise<number> {\n let found = 0\n for (const chunk of slices(ids.filter((id) => id && !id.includes('/')), 300)) {\n const snapshots = await collection.firestore.getAll(...chunk.map((id) => collection.doc(id)))\n found += snapshots.filter((snapshot) => snapshot.exists).length\n }\n return found\n}\n"],"names":["slices","items","size","out","at","length","push","slice","countExisting","collection","ids","found","chunk","filter","id","includes","snapshots","firestore","getAll","map","doc","snapshot","exists"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,qEAAqE,GAErE,iCAAiC,GACjC,OAAO,SAASA,OAAUC,KAAmB,EAAEC,IAAY;IACzD,MAAMC,MAAa,EAAE;IACrB,IAAK,IAAIC,KAAK,GAAGA,KAAKH,MAAMI,MAAM,EAAED,MAAMF,KAAMC,IAAIG,IAAI,CAACL,MAAMM,KAAK,CAACH,IAAIA,KAAKF;IAC9E,OAAOC;AACT;AAEA;;;;CAIC,GACD,OAAO,eAAeK,cACpBC,UAAiD,EACjDC,GAAsB;IAEtB,IAAIC,QAAQ;IACZ,KAAK,MAAMC,SAASZ,OAAOU,IAAIG,MAAM,CAAC,CAACC,KAAOA,MAAM,CAACA,GAAGC,QAAQ,CAAC,OAAO,KAAM;QAC5E,MAAMC,YAAY,MAAMP,WAAWQ,SAAS,CAACC,MAAM,IAAIN,MAAMO,GAAG,CAAC,CAACL,KAAOL,WAAWW,GAAG,CAACN;QACxFH,SAASK,UAAUH,MAAM,CAAC,CAACQ,WAAaA,SAASC,MAAM,EAAEjB,MAAM;IACjE;IACA,OAAOM;AACT"}
@@ -1,22 +0,0 @@
1
- export interface ListImportDrawerProps {
2
- open: boolean;
3
- onClose: () => void;
4
- hostId: string;
5
- listId: string;
6
- listName: string;
7
- /**
8
- * Called whenever the run enrolls anybody.
9
- *
10
- * The audience page's subscriber total is a server aggregate taken once, so
11
- * an import that ran to completion behind a stale count would read as
12
- * having done nothing. Fired per run rather than once at the end: a large
13
- * import is watched, and a total that only moves at the end is a total that
14
- * looks stuck.
15
- */
16
- onMembershipChanged?: () => void;
17
- }
18
- export declare function ListImportDrawer(props: ListImportDrawerProps): import("react").JSX.Element;
19
- export declare namespace ListImportDrawer {
20
- var displayName: string;
21
- }
22
- export default ListImportDrawer;