@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.
- package/README.md +5 -4
- package/package.json +16 -17
- package/src/lib/components/email-template-detail.d.ts +5 -13
- package/src/lib/components/email-template-detail.js +29 -426
- package/src/lib/components/email-template-detail.js.map +1 -1
- package/src/lib/components/email-zones.d.ts +15 -0
- package/src/lib/components/email-zones.js +1 -0
- package/src/lib/components/email-zones.js.map +1 -1
- package/src/lib/components/list-detail-card.d.ts +0 -36
- package/src/lib/components/list-detail-card.js +125 -84
- package/src/lib/components/list-detail-card.js.map +1 -1
- package/src/lib/components/list-members-panel.d.ts +17 -0
- package/src/lib/components/list-members-panel.js +14 -0
- package/src/lib/components/list-members-panel.js.map +1 -1
- package/src/lib/components/site-package-email-preview.d.ts +13 -0
- package/src/lib/components/site-package-email-preview.js +47 -0
- package/src/lib/components/site-package-email-preview.js.map +1 -0
- package/src/lib/components/suppressions-card.js +75 -6
- package/src/lib/components/suppressions-card.js.map +1 -1
- package/src/lib/components/use-org-email-topics.js +2 -9
- package/src/lib/components/use-org-email-topics.js.map +1 -1
- package/src/lib/constants/site-package.d.ts +21 -0
- package/src/lib/constants/site-package.js +21 -0
- package/src/lib/constants/site-package.js.map +1 -0
- package/src/lib/declarations.console-server.d.ts +12 -0
- package/src/lib/declarations.console-server.js +73 -0
- package/src/lib/declarations.console-server.js.map +1 -1
- package/src/lib/list-import.d.ts +64 -75
- package/src/lib/list-import.js +43 -158
- package/src/lib/list-import.js.map +1 -1
- package/src/lib/model/email-topic-catalog.d.ts +24 -0
- package/src/lib/model/email-topic-catalog.js +19 -0
- package/src/lib/model/email-topic-catalog.js.map +1 -1
- package/src/lib/model/index.d.ts +0 -25
- package/src/lib/model/index.js +0 -27
- package/src/lib/model/index.js.map +1 -1
- package/src/lib/plugin.d.ts +8 -0
- package/src/lib/plugin.js +76 -2
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/server/person-eraser.d.ts +39 -0
- package/src/lib/server/person-eraser.js +44 -0
- package/src/lib/server/person-eraser.js.map +1 -0
- package/src/lib/server-console.js +9 -9
- package/src/lib/server-console.js.map +1 -1
- package/src/lib/server-list-gate.d.ts +7 -0
- package/src/lib/server-list-gate.js +10 -3
- package/src/lib/server-list-gate.js.map +1 -1
- package/src/lib/server-list-import.d.ts +20 -177
- package/src/lib/server-list-import.js +34 -594
- package/src/lib/server-list-import.js.map +1 -1
- package/src/lib/server.js +16 -29
- package/src/lib/server.js.map +1 -1
- package/src/lib/testing/stand-in-send-tally.d.ts +29 -0
- package/src/lib/testing/stand-in-send-tally.js +52 -0
- package/src/lib/testing/stand-in-send-tally.js.map +1 -0
- package/src/lib/transfer/email-transfer-catalog.d.ts +151 -0
- package/src/lib/transfer/email-transfer-catalog.js +536 -0
- package/src/lib/transfer/email-transfer-catalog.js.map +1 -0
- package/src/lib/transfer/list-import-steps.d.ts +44 -0
- package/src/lib/transfer/list-import-steps.js +201 -0
- package/src/lib/transfer/list-import-steps.js.map +1 -0
- package/src/lib/transfer/list-members.server.d.ts +165 -0
- package/src/lib/transfer/list-members.server.js +911 -0
- package/src/lib/transfer/list-members.server.js.map +1 -0
- package/src/lib/transfer/suppressions.server.d.ts +69 -0
- package/src/lib/transfer/suppressions.server.js +282 -0
- package/src/lib/transfer/suppressions.server.js.map +1 -0
- package/src/lib/transfer/topics-package.d.ts +56 -0
- package/src/lib/transfer/topics-package.js +87 -0
- package/src/lib/transfer/topics-package.js.map +1 -0
- package/src/lib/transfer/topics-package.server.d.ts +33 -0
- package/src/lib/transfer/topics-package.server.js +171 -0
- package/src/lib/transfer/topics-package.server.js.map +1 -0
- package/src/lib/transfer/transfer-reads.server.d.ts +25 -0
- package/src/lib/transfer/transfer-reads.server.js +34 -0
- package/src/lib/transfer/transfer-reads.server.js.map +1 -0
- package/src/lib/components/list-import-drawer.d.ts +0 -22
- package/src/lib/components/list-import-drawer.js +0 -662
- package/src/lib/components/list-import-drawer.js.map +0 -1
- package/src/lib/model/template-report.d.ts +0 -158
- package/src/lib/model/template-report.js +0 -249
- 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;
|