@aglyn/plugins-workflows 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 +22 -14
- package/package.json +12 -12
- package/src/lib/components/automation-step-fields.component.d.ts +16 -16
- package/src/lib/components/automation-step-fields.component.js +146 -12
- package/src/lib/components/automation-step-fields.component.js.map +1 -1
- package/src/lib/components/automation-trigger-conditions.component.d.ts +25 -3
- package/src/lib/components/automation-trigger-conditions.component.js +89 -10
- package/src/lib/components/automation-trigger-conditions.component.js.map +1 -1
- package/src/lib/components/host-actions-card.component.js +126 -84
- package/src/lib/components/host-actions-card.component.js.map +1 -1
- package/src/lib/components/host-webhooks-card.component.js +2 -1
- package/src/lib/components/host-webhooks-card.component.js.map +1 -1
- package/src/lib/components/host-workflows-card.component.js +31 -10
- package/src/lib/components/host-workflows-card.component.js.map +1 -1
- package/src/lib/components/org-automation-editor.component.d.ts +2 -1
- package/src/lib/components/org-automation-editor.component.js +20 -11
- package/src/lib/components/org-automation-editor.component.js.map +1 -1
- package/src/lib/components/org-automations-card.component.js +2 -1
- package/src/lib/components/org-automations-card.component.js.map +1 -1
- package/src/lib/components/run-quota-line.component.d.ts +17 -4
- package/src/lib/components/run-quota-line.component.js +16 -7
- package/src/lib/components/run-quota-line.component.js.map +1 -1
- package/src/lib/components/site-org-automations-panel.component.js +2 -1
- package/src/lib/components/site-org-automations-panel.component.js.map +1 -1
- package/src/lib/components/workflows-console-page.js +18 -7
- package/src/lib/components/workflows-console-page.js.map +1 -1
- package/src/lib/declarations.d.ts +28 -0
- package/src/lib/declarations.js +37 -0
- package/src/lib/declarations.js.map +1 -0
- package/src/lib/declarations.server.js +17 -0
- package/src/lib/declarations.server.js.map +1 -1
- package/src/lib/engine/email-merge.d.ts +69 -0
- package/src/lib/engine/email-merge.js +98 -0
- package/src/lib/engine/email-merge.js.map +1 -0
- package/src/lib/engine/run-event-actions.d.ts +35 -10
- package/src/lib/engine/run-event-actions.js +256 -369
- package/src/lib/engine/run-event-actions.js.map +1 -1
- package/src/lib/engine/run-event-automations.d.ts +1 -1
- package/src/lib/engine/run-event-automations.js.map +1 -1
- package/src/lib/engine/run-event-workflows.d.ts +2 -1
- package/src/lib/engine/run-event-workflows.js +71 -30
- package/src/lib/engine/run-event-workflows.js.map +1 -1
- package/src/lib/engine/run-meter.d.ts +98 -0
- package/src/lib/engine/run-meter.js +121 -0
- package/src/lib/engine/run-meter.js.map +1 -0
- package/src/lib/engine/run-org-automations.d.ts +3 -2
- package/src/lib/engine/run-org-automations.js +17 -12
- package/src/lib/engine/run-org-automations.js.map +1 -1
- package/src/lib/engine/triggered-docs.d.ts +39 -0
- package/src/lib/engine/triggered-docs.js +47 -0
- package/src/lib/engine/triggered-docs.js.map +1 -0
- package/src/lib/engine/workflow-steps.d.ts +1 -1
- package/src/lib/engine/workflow-steps.js +3 -2
- package/src/lib/engine/workflow-steps.js.map +1 -1
- package/src/lib/model/host-actions.d.ts +284 -0
- package/src/lib/model/host-actions.js +316 -0
- package/src/lib/model/host-actions.js.map +1 -0
- package/src/lib/model/org-automations.d.ts +8 -5
- package/src/lib/model/org-automations.js +14 -8
- package/src/lib/model/org-automations.js.map +1 -1
- package/src/lib/model/step-outcomes.d.ts +2 -2
- package/src/lib/model/step-outcomes.js +1 -1
- package/src/lib/model/step-outcomes.js.map +1 -1
- package/src/lib/plugin.js +10 -0
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/server/action-test-run-route.d.ts +3 -2
- package/src/lib/server/action-test-run-route.js +3 -2
- package/src/lib/server/action-test-run-route.js.map +1 -1
- package/src/lib/server/org-automations-routes.d.ts +42 -1
- package/src/lib/server/org-automations-routes.js +120 -72
- package/src/lib/server/org-automations-routes.js.map +1 -1
- package/src/lib/server-automation-drafts.d.ts +2 -2
- package/src/lib/server-automation-drafts.js +11 -7
- package/src/lib/server-automation-drafts.js.map +1 -1
- package/src/lib/server.js +19 -13
- package/src/lib/server.js.map +1 -1
- package/src/lib/testing/stand-in-crm-steps.d.ts +45 -0
- package/src/lib/testing/stand-in-crm-steps.js +97 -0
- package/src/lib/testing/stand-in-crm-steps.js.map +1 -0
- package/src/lib/testing/stand-in-dataset-list.js +11 -3
- package/src/lib/testing/stand-in-dataset-list.js.map +1 -1
- package/src/lib/testing/stand-in-dataset-steps.d.ts +43 -0
- package/src/lib/testing/stand-in-dataset-steps.js +65 -0
- package/src/lib/testing/stand-in-dataset-steps.js.map +1 -0
- package/src/lib/testing/stand-in-person-records.d.ts +35 -0
- package/src/lib/testing/stand-in-person-records.js +54 -0
- package/src/lib/testing/stand-in-person-records.js.map +1 -0
- package/src/lib/testing/stand-in-recipes.d.ts +36 -0
- package/src/lib/testing/stand-in-recipes.js +206 -0
- package/src/lib/testing/stand-in-recipes.js.map +1 -0
- package/src/lib/transfer/org-automations-package.d.ts +54 -0
- package/src/lib/transfer/org-automations-package.js +167 -0
- package/src/lib/transfer/org-automations-package.js.map +1 -0
- package/src/lib/transfer/org-automations-package.server.d.ts +38 -0
- package/src/lib/transfer/org-automations-package.server.js +296 -0
- package/src/lib/transfer/org-automations-package.server.js.map +1 -0
- package/src/lib/usage-axes.js +5 -1
- package/src/lib/usage-axes.js.map +1 -1
- package/src/lib/engine/crm-action-steps.d.ts +0 -142
- package/src/lib/engine/crm-action-steps.js +0 -452
- package/src/lib/engine/crm-action-steps.js.map +0 -1
|
@@ -0,0 +1,296 @@
|
|
|
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
|
+
* ORG AUTOMATIONS IN A WORKSPACE PACKAGE — the server half (AGL-3535).
|
|
19
|
+
*
|
|
20
|
+
* Written through `automations/manage`'s own writes
|
|
21
|
+
* (`createOrgAutomationRecord`, `updateOrgAutomationRecord`,
|
|
22
|
+
* `deleteOrgAutomationRecord`): its reader, its placement check, its cap,
|
|
23
|
+
* its soft delete and its activity lines. The importer is held to that
|
|
24
|
+
* door's rules too — an org-wide owner, admin or editor, in a workspace
|
|
25
|
+
* running Workflows on a plan with org automations — so an import can do
|
|
26
|
+
* nothing that door would refuse. A new automation lands switched off; a
|
|
27
|
+
* replaced one keeps whether it was on.
|
|
28
|
+
*=========================================*/ import { DECLARED_SUBSCRIPTION_TOPICS } from "@aglyn/aglyn/app-utils/subscription-topics";
|
|
29
|
+
import { existingPackageItemsOf } from "@aglyn/aglyn/data-transfer";
|
|
30
|
+
import { isPluginEnabled } from "@aglyn/aglyn/plugin-manager/enabled-plugins";
|
|
31
|
+
import { checkEntitlement, isOrgWideMember, planLabelGrantingFeature } from "@aglyn/aglyn/server";
|
|
32
|
+
import firebaseAdmin from "@aglyn/tenant-data-admin/server/firebase-admin";
|
|
33
|
+
import { getOrgDoc, logOrgActivity, resolveOrgMembership } from "@aglyn/tenant-data-admin/server/organizations";
|
|
34
|
+
import { BUNDLE_ID } from "../constants/bundle-common.js";
|
|
35
|
+
import { ORG_AUTOMATION_ACTIVITY_TARGET, ORG_AUTOMATIONS_COLLECTION, ORG_AUTOMATIONS_MAX, readOrgAutomation } from "../model/org-automations.js";
|
|
36
|
+
import { createOrgAutomationRecord, deleteOrgAutomationRecord, updateOrgAutomationRecord } from "../server/org-automations-routes.js";
|
|
37
|
+
import { ORG_AUTOMATION_REFERENCE_KINDS, ORG_AUTOMATIONS_TRANSFER_KEY, orgAutomationPackageContent, rawOrgAutomationContent } from "./org-automations-package.js";
|
|
38
|
+
function defaultDeps() {
|
|
39
|
+
return {
|
|
40
|
+
firestore: ()=>firebaseAdmin.app().firestore(),
|
|
41
|
+
readOrg: async (orgId)=>{
|
|
42
|
+
var _ref;
|
|
43
|
+
return (_ref = await getOrgDoc(orgId)) != null ? _ref : null;
|
|
44
|
+
},
|
|
45
|
+
memberOf: async (uid, orgId)=>{
|
|
46
|
+
var _ref;
|
|
47
|
+
var _this;
|
|
48
|
+
return (_ref = (_this = await resolveOrgMembership(uid, orgId)) == null ? void 0 : _this.member) != null ? _ref : null;
|
|
49
|
+
},
|
|
50
|
+
logOrgActivity: (orgId, uid, action, target)=>logOrgActivity(orgId, {
|
|
51
|
+
uid,
|
|
52
|
+
email: null
|
|
53
|
+
}, action, _extends({
|
|
54
|
+
type: ORG_AUTOMATION_ACTIVITY_TARGET
|
|
55
|
+
}, target))
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
const automationsOf = (firestore, orgId)=>firestore.collection('orgs').doc(orgId).collection(ORG_AUTOMATIONS_COLLECTION);
|
|
59
|
+
/** Why `automations/manage` would refuse this importer, or `null` when it would not. */ async function importerRefusal(deps, ctx) {
|
|
60
|
+
var _member_role;
|
|
61
|
+
if (!ctx.actorUid) return 'Org automations are imported by a member of the organization.';
|
|
62
|
+
const member = await deps.memberOf(ctx.actorUid, ctx.orgId);
|
|
63
|
+
if (!member || !isOrgWideMember(member) || ![
|
|
64
|
+
'owner',
|
|
65
|
+
'admin',
|
|
66
|
+
'editor'
|
|
67
|
+
].includes(String((_member_role = member['role']) != null ? _member_role : ''))) {
|
|
68
|
+
return 'Org automations are written by an organization owner, admin or editor.';
|
|
69
|
+
}
|
|
70
|
+
const org = await deps.readOrg(ctx.orgId);
|
|
71
|
+
if (!org || !isPluginEnabled(org, BUNDLE_ID)) return 'Workflows is not on for this workspace.';
|
|
72
|
+
if (!checkEntitlement(org, 'actions')) {
|
|
73
|
+
var _planLabelGrantingFeature;
|
|
74
|
+
return `Org automations need the ${(_planLabelGrantingFeature = planLabelGrantingFeature('actions')) != null ? _planLabelGrantingFeature : 'Pro'} plan.`;
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
async function readAll(deps, orgId, ids) {
|
|
79
|
+
const collection = automationsOf(deps.firestore(), orgId);
|
|
80
|
+
const docs = ids ? (await Promise.all(ids.map((id)=>collection.doc(id).get()))).filter((doc)=>doc.exists) : (await collection.where('deletedAt', '==', null).get()).docs;
|
|
81
|
+
return docs.flatMap((doc)=>{
|
|
82
|
+
if (doc.get('deletedAt')) return [];
|
|
83
|
+
const content = orgAutomationPackageContent(doc.data());
|
|
84
|
+
return content ? [
|
|
85
|
+
{
|
|
86
|
+
kind: ORG_AUTOMATIONS_TRANSFER_KEY,
|
|
87
|
+
id: doc.id,
|
|
88
|
+
name: content.name,
|
|
89
|
+
content
|
|
90
|
+
}
|
|
91
|
+
] : [];
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
/** The fields one write stores: the content, read by the route's reader, switched on only if it already was. */ function fieldsOf(content, name, enabled) {
|
|
95
|
+
return readOrgAutomation(_extends({}, content, {
|
|
96
|
+
name,
|
|
97
|
+
enabled
|
|
98
|
+
}));
|
|
99
|
+
}
|
|
100
|
+
export function createOrgAutomationsPackage(deps = defaultDeps()) {
|
|
101
|
+
/** Whether the automation is on now; a new one is off. */ const enabledNow = async (orgId, id)=>(await automationsOf(deps.firestore(), orgId).doc(id).get()).get('enabled') === true;
|
|
102
|
+
return {
|
|
103
|
+
async items (ctx) {
|
|
104
|
+
return existingPackageItemsOf(ORG_AUTOMATIONS_TRANSFER_KEY, await readAll(deps, ctx.orgId));
|
|
105
|
+
},
|
|
106
|
+
async readItems (ctx, ids) {
|
|
107
|
+
return readAll(deps, ctx.orgId, ids);
|
|
108
|
+
},
|
|
109
|
+
async problems (ctx, write) {
|
|
110
|
+
var _ref;
|
|
111
|
+
var _write_rename;
|
|
112
|
+
const refusal = await importerRefusal(deps, ctx);
|
|
113
|
+
if (refusal) return [
|
|
114
|
+
refusal
|
|
115
|
+
];
|
|
116
|
+
const content = rawOrgAutomationContent(write.content);
|
|
117
|
+
const read = fieldsOf(content, (_ref = (_write_rename = write.rename) == null ? void 0 : _write_rename.name) != null ? _ref : content.name, false);
|
|
118
|
+
if (read.ok === false) return [
|
|
119
|
+
read.problem
|
|
120
|
+
];
|
|
121
|
+
const problems = [];
|
|
122
|
+
if (write.decision !== 'replace') {
|
|
123
|
+
const collection = automationsOf(deps.firestore(), ctx.orgId);
|
|
124
|
+
if ((await collection.doc(write.targetId).get()).exists) {
|
|
125
|
+
problems.push('An automation with this id was deleted from this workspace, so it cannot come back under it.');
|
|
126
|
+
}
|
|
127
|
+
const live = (await collection.where('deletedAt', '==', null).count().get()).data().count;
|
|
128
|
+
if (live >= ORG_AUTOMATIONS_MAX) {
|
|
129
|
+
problems.push(`An organization holds at most ${ORG_AUTOMATIONS_MAX} org automations — delete one you no longer use first.`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return problems;
|
|
133
|
+
},
|
|
134
|
+
async referenceTargets (ctx, kinds) {
|
|
135
|
+
const org = deps.firestore().collection('orgs').doc(ctx.orgId);
|
|
136
|
+
const named = async (collection, field)=>(await org.collection(collection).get()).docs.filter((doc)=>!doc.get('deletedAt')).map((doc)=>{
|
|
137
|
+
var _ref, _doc_get;
|
|
138
|
+
const name = String((_ref = (_doc_get = doc.get(field)) != null ? _doc_get : doc.get('label')) != null ? _ref : '').trim();
|
|
139
|
+
return _extends({
|
|
140
|
+
id: doc.id
|
|
141
|
+
}, name ? {
|
|
142
|
+
name
|
|
143
|
+
} : {});
|
|
144
|
+
});
|
|
145
|
+
const answers = [];
|
|
146
|
+
if (kinds.includes(ORG_AUTOMATION_REFERENCE_KINDS.list)) {
|
|
147
|
+
answers.push({
|
|
148
|
+
kind: ORG_AUTOMATION_REFERENCE_KINDS.list,
|
|
149
|
+
label: 'Email list',
|
|
150
|
+
targets: await named('lists', 'name')
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
if (kinds.includes(ORG_AUTOMATION_REFERENCE_KINDS.topic)) {
|
|
154
|
+
// Asked only when the email plugin, which owns the topic catalog and answers `email.topics`
|
|
155
|
+
// (AGL-3550), is not running here — and without it the catalog is the built-ins the plugins declare.
|
|
156
|
+
const targets = DECLARED_SUBSCRIPTION_TOPICS.map((topic)=>({
|
|
157
|
+
id: topic.id,
|
|
158
|
+
name: topic.name
|
|
159
|
+
}));
|
|
160
|
+
answers.push({
|
|
161
|
+
kind: ORG_AUTOMATION_REFERENCE_KINDS.topic,
|
|
162
|
+
label: 'Subscription topic',
|
|
163
|
+
targets
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
if (kinds.includes(ORG_AUTOMATION_REFERENCE_KINDS.member)) {
|
|
167
|
+
answers.push({
|
|
168
|
+
kind: ORG_AUTOMATION_REFERENCE_KINDS.member,
|
|
169
|
+
label: 'Member',
|
|
170
|
+
targets: await named('members', 'email')
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
if (kinds.includes(ORG_AUTOMATION_REFERENCE_KINDS.dataset)) {
|
|
174
|
+
answers.push({
|
|
175
|
+
kind: ORG_AUTOMATION_REFERENCE_KINDS.dataset,
|
|
176
|
+
label: 'Dataset',
|
|
177
|
+
targets: await named('datasets', 'name')
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
return answers;
|
|
181
|
+
},
|
|
182
|
+
async writeItems (ctx, items, writer) {
|
|
183
|
+
const refusal = await importerRefusal(deps, ctx);
|
|
184
|
+
const firestore = deps.firestore();
|
|
185
|
+
const results = [];
|
|
186
|
+
for (const write of items){
|
|
187
|
+
var _ref;
|
|
188
|
+
var _write_rename;
|
|
189
|
+
const before = await writer.alreadyApplied(write.row);
|
|
190
|
+
if (before) {
|
|
191
|
+
results.push(before);
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
const content = rawOrgAutomationContent(write.content);
|
|
195
|
+
const name = (_ref = (_write_rename = write.rename) == null ? void 0 : _write_rename.name) != null ? _ref : content.name;
|
|
196
|
+
const replacing = write.decision === 'replace';
|
|
197
|
+
const read = fieldsOf(content, name, replacing ? await enabledNow(ctx.orgId, write.targetId) : false);
|
|
198
|
+
let result;
|
|
199
|
+
if (refusal) result = {
|
|
200
|
+
row: write.row,
|
|
201
|
+
outcome: 'failed',
|
|
202
|
+
message: refusal
|
|
203
|
+
};
|
|
204
|
+
else if (read.ok === false) result = {
|
|
205
|
+
row: write.row,
|
|
206
|
+
outcome: 'failed',
|
|
207
|
+
message: read.problem
|
|
208
|
+
};
|
|
209
|
+
else {
|
|
210
|
+
var _ctx_actorUid;
|
|
211
|
+
const input = {
|
|
212
|
+
orgId: ctx.orgId,
|
|
213
|
+
id: write.targetId,
|
|
214
|
+
fields: read.value,
|
|
215
|
+
actorUid: (_ctx_actorUid = ctx.actorUid) != null ? _ctx_actorUid : ''
|
|
216
|
+
};
|
|
217
|
+
const written = replacing ? await updateOrgAutomationRecord(firestore, input) : await createOrgAutomationRecord(firestore, input);
|
|
218
|
+
if (written.ok === false) result = {
|
|
219
|
+
row: write.row,
|
|
220
|
+
outcome: 'failed',
|
|
221
|
+
recordId: write.targetId,
|
|
222
|
+
message: written.error
|
|
223
|
+
};
|
|
224
|
+
else {
|
|
225
|
+
var _ctx_actorUid1;
|
|
226
|
+
await deps.logOrgActivity(ctx.orgId, (_ctx_actorUid1 = ctx.actorUid) != null ? _ctx_actorUid1 : '', replacing ? 'Edited an org automation' : 'Created an org automation', {
|
|
227
|
+
id: written.id,
|
|
228
|
+
name: read.value.name
|
|
229
|
+
});
|
|
230
|
+
result = {
|
|
231
|
+
row: write.row,
|
|
232
|
+
outcome: replacing ? 'updated' : 'created',
|
|
233
|
+
recordId: written.id
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
await writer.markApplied(result);
|
|
238
|
+
results.push(result);
|
|
239
|
+
}
|
|
240
|
+
return {
|
|
241
|
+
results,
|
|
242
|
+
undo: []
|
|
243
|
+
};
|
|
244
|
+
},
|
|
245
|
+
async revertItems (ctx, steps) {
|
|
246
|
+
const firestore = deps.firestore();
|
|
247
|
+
const outcome = {
|
|
248
|
+
done: [],
|
|
249
|
+
refused: []
|
|
250
|
+
};
|
|
251
|
+
for (const step of steps){
|
|
252
|
+
var _ctx_actorUid;
|
|
253
|
+
if (step.action === 'delete') {
|
|
254
|
+
var _ctx_actorUid1;
|
|
255
|
+
const deleted = await deleteOrgAutomationRecord(firestore, {
|
|
256
|
+
orgId: ctx.orgId,
|
|
257
|
+
id: step.id,
|
|
258
|
+
actorUid: (_ctx_actorUid1 = ctx.actorUid) != null ? _ctx_actorUid1 : ''
|
|
259
|
+
});
|
|
260
|
+
if (deleted && !deleted.again) {
|
|
261
|
+
var _ctx_actorUid2;
|
|
262
|
+
await deps.logOrgActivity(ctx.orgId, (_ctx_actorUid2 = ctx.actorUid) != null ? _ctx_actorUid2 : '', 'Deleted an org automation', {
|
|
263
|
+
id: step.id,
|
|
264
|
+
name: deleted.name
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
outcome.done.push(step.id);
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
const content = rawOrgAutomationContent(step.content);
|
|
271
|
+
const read = fieldsOf(content, content.name, await enabledNow(ctx.orgId, step.id));
|
|
272
|
+
if (read.ok === false) {
|
|
273
|
+
outcome.refused.push({
|
|
274
|
+
id: step.id,
|
|
275
|
+
reason: read.problem
|
|
276
|
+
});
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
const written = await updateOrgAutomationRecord(firestore, {
|
|
280
|
+
orgId: ctx.orgId,
|
|
281
|
+
id: step.id,
|
|
282
|
+
fields: read.value,
|
|
283
|
+
actorUid: (_ctx_actorUid = ctx.actorUid) != null ? _ctx_actorUid : ''
|
|
284
|
+
});
|
|
285
|
+
if (written.ok === false) outcome.refused.push({
|
|
286
|
+
id: step.id,
|
|
287
|
+
reason: written.error
|
|
288
|
+
});
|
|
289
|
+
else outcome.done.push(step.id);
|
|
290
|
+
}
|
|
291
|
+
return outcome;
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
//# sourceMappingURL=org-automations-package.server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/workflows/src/lib/transfer/org-automations-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 * ORG AUTOMATIONS IN A WORKSPACE PACKAGE — the server half (AGL-3535).\n *\n * Written through `automations/manage`'s own writes\n * (`createOrgAutomationRecord`, `updateOrgAutomationRecord`,\n * `deleteOrgAutomationRecord`): its reader, its placement check, its cap,\n * its soft delete and its activity lines. The importer is held to that\n * door's rules too — an org-wide owner, admin or editor, in a workspace\n * running Workflows on a plan with org automations — so an import can do\n * nothing that door would refuse. A new automation lands switched off; a\n * replaced one keeps whether it was on.\n *=========================================*/\n\nimport { DECLARED_SUBSCRIPTION_TOPICS } from '@aglyn/aglyn/app-utils/subscription-topics'\nimport { existingPackageItemsOf, type ExistingPackageItem, type TransferReferenceKind, type TransferRowResult } from '@aglyn/aglyn/data-transfer'\nimport { isPluginEnabled } from '@aglyn/aglyn/plugin-manager/enabled-plugins'\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 { checkEntitlement, isOrgWideMember, planLabelGrantingFeature } from '@aglyn/aglyn/server'\nimport firebaseAdmin from '@aglyn/tenant-data-admin/server/firebase-admin'\nimport { getOrgDoc, logOrgActivity, resolveOrgMembership } from '@aglyn/tenant-data-admin/server/organizations'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport {\n ORG_AUTOMATION_ACTIVITY_TARGET,\n ORG_AUTOMATIONS_COLLECTION,\n ORG_AUTOMATIONS_MAX,\n readOrgAutomation,\n} from '../model/org-automations'\nimport {\n createOrgAutomationRecord,\n deleteOrgAutomationRecord,\n updateOrgAutomationRecord,\n} from '../server/org-automations-routes'\nimport {\n ORG_AUTOMATION_REFERENCE_KINDS,\n ORG_AUTOMATIONS_TRANSFER_KEY,\n orgAutomationPackageContent,\n rawOrgAutomationContent,\n type OrgAutomationPackageContent,\n} from './org-automations-package'\n\ntype Firestore = FirebaseFirestore.Firestore\n\nexport interface OrgAutomationsPackageDeps {\n firestore(): Firestore\n readOrg(orgId: string): Promise<Record<string, unknown> | null>\n memberOf(uid: string, orgId: string): Promise<Record<string, unknown> | null>\n logOrgActivity(orgId: string, uid: string, action: string, target: { id: string; name: string }): Promise<void>\n}\n\nfunction defaultDeps(): OrgAutomationsPackageDeps {\n return {\n firestore: () => firebaseAdmin.app().firestore() as unknown as Firestore,\n readOrg: async (orgId) => ((await getOrgDoc(orgId)) as Record<string, unknown> | null) ?? null,\n memberOf: async (uid, orgId) => ((await resolveOrgMembership(uid, orgId))?.member as Record<string, unknown> | undefined) ?? null,\n logOrgActivity: (orgId, uid, action, target) =>\n logOrgActivity(orgId, { uid, email: null }, action, { type: ORG_AUTOMATION_ACTIVITY_TARGET, ...target }),\n }\n}\n\nconst automationsOf = (firestore: Firestore, orgId: string) =>\n firestore.collection('orgs').doc(orgId).collection(ORG_AUTOMATIONS_COLLECTION)\n\n/** Why `automations/manage` would refuse this importer, or `null` when it would not. */\nasync function importerRefusal(deps: OrgAutomationsPackageDeps, ctx: TransferResourceContext): Promise<string | null> {\n if (!ctx.actorUid) return 'Org automations are imported by a member of the organization.'\n const member = await deps.memberOf(ctx.actorUid, ctx.orgId)\n if (!member || !isOrgWideMember(member as never) || !['owner', 'admin', 'editor'].includes(String(member['role'] ?? ''))) {\n return 'Org automations are written by an organization owner, admin or editor.'\n }\n const org = await deps.readOrg(ctx.orgId)\n if (!org || !isPluginEnabled(org as never, BUNDLE_ID)) return 'Workflows is not on for this workspace.'\n if (!checkEntitlement(org as never, 'actions')) {\n return `Org automations need the ${planLabelGrantingFeature('actions') ?? 'Pro'} plan.`\n }\n return null\n}\n\nasync function readAll(\n deps: OrgAutomationsPackageDeps,\n orgId: string,\n ids?: readonly string[],\n): Promise<Array<TransferPackageItemContent<OrgAutomationPackageContent>>> {\n const collection = automationsOf(deps.firestore(), orgId)\n const docs = ids\n ? (await Promise.all(ids.map((id) => collection.doc(id).get()))).filter((doc) => doc.exists)\n : (await collection.where('deletedAt', '==', null).get()).docs\n return docs.flatMap((doc) => {\n if (doc.get('deletedAt')) return []\n const content = orgAutomationPackageContent(doc.data())\n return content ? [{ kind: ORG_AUTOMATIONS_TRANSFER_KEY, id: doc.id, name: content.name, content }] : []\n })\n}\n\n/** The fields one write stores: the content, read by the route's reader, switched on only if it already was. */\nfunction fieldsOf(content: OrgAutomationPackageContent, name: string, enabled: boolean) {\n return readOrgAutomation({ ...content, name, enabled })\n}\n\nexport function createOrgAutomationsPackage(deps: OrgAutomationsPackageDeps = defaultDeps()) {\n /** Whether the automation is on now; a new one is off. */\n const enabledNow = async (orgId: string, id: string) =>\n (await automationsOf(deps.firestore(), orgId).doc(id).get()).get('enabled') === true\n\n return {\n async items(ctx: TransferResourceContext): Promise<ExistingPackageItem[]> {\n return existingPackageItemsOf(ORG_AUTOMATIONS_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 async problems(ctx: TransferResourceContext, write: TransferPackageItemWrite<OrgAutomationPackageContent>) {\n const refusal = await importerRefusal(deps, ctx)\n if (refusal) return [refusal]\n const content = rawOrgAutomationContent(write.content)\n const read = fieldsOf(content, write.rename?.name ?? content.name, false)\n if (read.ok === false) return [read.problem]\n const problems: string[] = []\n if (write.decision !== 'replace') {\n const collection = automationsOf(deps.firestore(), ctx.orgId)\n if ((await collection.doc(write.targetId).get()).exists) {\n problems.push('An automation with this id was deleted from this workspace, so it cannot come back under it.')\n }\n const live = (await collection.where('deletedAt', '==', null).count().get()).data().count\n if (live >= ORG_AUTOMATIONS_MAX) {\n problems.push(`An organization holds at most ${ORG_AUTOMATIONS_MAX} org automations — delete one you no longer use first.`)\n }\n }\n return problems\n },\n\n async referenceTargets(ctx: TransferResourceContext, kinds: readonly string[]): Promise<TransferReferenceKind[]> {\n const org = deps.firestore().collection('orgs').doc(ctx.orgId)\n const named = async (collection: string, field: string) =>\n (await org.collection(collection).get()).docs\n .filter((doc) => !doc.get('deletedAt'))\n .map((doc) => {\n const name = String(doc.get(field) ?? doc.get('label') ?? '').trim()\n return { id: doc.id, ...(name ? { name } : {}) }\n })\n const answers: TransferReferenceKind[] = []\n if (kinds.includes(ORG_AUTOMATION_REFERENCE_KINDS.list)) {\n answers.push({ kind: ORG_AUTOMATION_REFERENCE_KINDS.list, label: 'Email list', targets: await named('lists', 'name') })\n }\n if (kinds.includes(ORG_AUTOMATION_REFERENCE_KINDS.topic)) {\n // Asked only when the email plugin, which owns the topic catalog and answers `email.topics`\n // (AGL-3550), is not running here — and without it the catalog is the built-ins the plugins declare.\n const targets = DECLARED_SUBSCRIPTION_TOPICS.map((topic) => ({ id: topic.id, name: topic.name }))\n answers.push({ kind: ORG_AUTOMATION_REFERENCE_KINDS.topic, label: 'Subscription topic', targets })\n }\n if (kinds.includes(ORG_AUTOMATION_REFERENCE_KINDS.member)) {\n answers.push({ kind: ORG_AUTOMATION_REFERENCE_KINDS.member, label: 'Member', targets: await named('members', 'email') })\n }\n if (kinds.includes(ORG_AUTOMATION_REFERENCE_KINDS.dataset)) {\n answers.push({ kind: ORG_AUTOMATION_REFERENCE_KINDS.dataset, label: 'Dataset', targets: await named('datasets', 'name') })\n }\n return answers\n },\n\n async writeItems(\n ctx: TransferResourceContext,\n items: ReadonlyArray<TransferPackageItemWrite<OrgAutomationPackageContent>>,\n writer: TransferApplyWriter,\n ): Promise<TransferApplyResult> {\n const refusal = await importerRefusal(deps, ctx)\n const firestore = deps.firestore()\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 content = rawOrgAutomationContent(write.content)\n const name = write.rename?.name ?? content.name\n const replacing = write.decision === 'replace'\n const read = fieldsOf(content, name, replacing ? await enabledNow(ctx.orgId, write.targetId) : false)\n let result: TransferRowResult\n if (refusal) result = { row: write.row, outcome: 'failed', message: refusal }\n else if (read.ok === false) result = { row: write.row, outcome: 'failed', message: read.problem }\n else {\n const input = { orgId: ctx.orgId, id: write.targetId, fields: read.value, actorUid: ctx.actorUid ?? '' }\n const written = replacing ? await updateOrgAutomationRecord(firestore, input) : await createOrgAutomationRecord(firestore, input)\n if (written.ok === false) result = { row: write.row, outcome: 'failed', recordId: write.targetId, message: written.error }\n else {\n await deps.logOrgActivity(ctx.orgId, ctx.actorUid ?? '', replacing ? 'Edited an org automation' : 'Created an org automation', {\n id: written.id,\n name: read.value.name,\n })\n result = { row: write.row, outcome: replacing ? 'updated' : 'created', recordId: written.id }\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<OrgAutomationPackageContent>>,\n ): Promise<TransferPackageRevertResult> {\n const firestore = deps.firestore()\n const outcome: TransferPackageRevertResult = { done: [], refused: [] }\n for (const step of steps) {\n if (step.action === 'delete') {\n const deleted = await deleteOrgAutomationRecord(firestore, { orgId: ctx.orgId, id: step.id, actorUid: ctx.actorUid ?? '' })\n if (deleted && !deleted.again) {\n await deps.logOrgActivity(ctx.orgId, ctx.actorUid ?? '', 'Deleted an org automation', { id: step.id, name: deleted.name })\n }\n outcome.done.push(step.id)\n continue\n }\n const content = rawOrgAutomationContent(step.content)\n const read = fieldsOf(content, content.name, await enabledNow(ctx.orgId, step.id))\n if (read.ok === false) {\n outcome.refused.push({ id: step.id, reason: read.problem })\n continue\n }\n const written = await updateOrgAutomationRecord(firestore, { orgId: ctx.orgId, id: step.id, fields: read.value, actorUid: ctx.actorUid ?? '' })\n if (written.ok === false) outcome.refused.push({ id: step.id, reason: written.error })\n else outcome.done.push(step.id)\n }\n return outcome\n },\n }\n}\n"],"names":["DECLARED_SUBSCRIPTION_TOPICS","existingPackageItemsOf","isPluginEnabled","checkEntitlement","isOrgWideMember","planLabelGrantingFeature","firebaseAdmin","getOrgDoc","logOrgActivity","resolveOrgMembership","BUNDLE_ID","ORG_AUTOMATION_ACTIVITY_TARGET","ORG_AUTOMATIONS_COLLECTION","ORG_AUTOMATIONS_MAX","readOrgAutomation","createOrgAutomationRecord","deleteOrgAutomationRecord","updateOrgAutomationRecord","ORG_AUTOMATION_REFERENCE_KINDS","ORG_AUTOMATIONS_TRANSFER_KEY","orgAutomationPackageContent","rawOrgAutomationContent","defaultDeps","firestore","app","readOrg","orgId","memberOf","uid","member","action","target","email","type","automationsOf","collection","doc","importerRefusal","deps","ctx","actorUid","includes","String","org","readAll","ids","docs","Promise","all","map","id","get","filter","exists","where","flatMap","content","data","kind","name","fieldsOf","enabled","createOrgAutomationsPackage","enabledNow","items","readItems","problems","write","refusal","read","rename","ok","problem","decision","targetId","push","live","count","referenceTargets","kinds","named","field","trim","answers","list","label","targets","topic","dataset","writeItems","writer","results","before","alreadyApplied","row","replacing","result","outcome","message","input","fields","value","written","recordId","error","markApplied","undo","revertItems","steps","done","refused","step","deleted","again","reason"],"mappings":"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"}
|
package/src/lib/usage-axes.js
CHANGED
|
@@ -45,6 +45,9 @@
|
|
|
45
45
|
],
|
|
46
46
|
entitlement: 'workflowRunsPerMonth',
|
|
47
47
|
hostCounter: 'workflowRuns',
|
|
48
|
+
// The band is the workspace's: every run is counted on the org's
|
|
49
|
+
// counter too, and the run gates are held to it (`run-meter.ts`).
|
|
50
|
+
orgCounter: 'workflowRuns',
|
|
48
51
|
// The runtime stops workflows silently at the monthly cap, so the
|
|
49
52
|
// owner is warned approaching it and told on reaching it why their
|
|
50
53
|
// automations went quiet.
|
|
@@ -63,7 +66,8 @@
|
|
|
63
66
|
fields: [
|
|
64
67
|
'actionRuns'
|
|
65
68
|
],
|
|
66
|
-
entitlement: 'actionRunsPerMonth'
|
|
69
|
+
entitlement: 'actionRunsPerMonth',
|
|
70
|
+
orgCounter: 'actionRuns'
|
|
67
71
|
}
|
|
68
72
|
]
|
|
69
73
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/plugins/workflows/src/lib/usage-axes.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\nimport type { PluginUsageAxesDeclaration } from '@aglyn/aglyn/plugin-manager/plugin-usage-axes'\n\n/**\n * THE WORKFLOWS PLUGIN'S METERS (AGL-3080): workflow and action runs, in the\n * platform's cost model and utilization table. Compiled into core by the\n * manifest generator (`register.usageAxes`).\n *\n * ONE cost axis for both counters: a run is the same handful of reads, two\n * writes and a moment of compute whichever builder produced it, so both are\n * priced at `perRun` on one line. TWO bands, because each builder sells its\n * own; the workflow band also warns the workspace as it fills.\n */\nexport function workflowsUsageAxes(): PluginUsageAxesDeclaration {\n return {\n costAxes: [\n {\n id: 'runs',\n order: 90,\n fields: ['workflowRuns', 'actionRuns'],\n rate: 'perRun',\n },\n ],\n bands: [\n {\n id: 'workflowRuns',\n label: 'Workflow runs',\n order: 100,\n fields: ['workflowRuns'],\n entitlement: 'workflowRunsPerMonth',\n hostCounter: 'workflowRuns',\n // The runtime stops workflows silently at the monthly cap, so the\n // owner is warned approaching it and told on reaching it why their\n // automations went quiet.\n alert: {\n label: 'monthly workflow runs',\n noun: 'workflow runs',\n outcome: 'stops',\n reached:\n 'Workflows pause until next month and nothing is charged — ' +\n 'upgrade in Billing to keep them running.',\n approach:\n 'Nothing changes and nothing is charged — at the included amount, ' +\n 'workflows pause until next month unless you upgrade in Billing.',\n },\n },\n {\n id: 'actionRuns',\n label: 'Action runs',\n order: 110,\n fields: ['actionRuns'],\n entitlement: 'actionRunsPerMonth',\n },\n ],\n }\n}\n"],"names":["workflowsUsageAxes","costAxes","id","order","fields","rate","bands","label","entitlement","hostCounter","alert","noun","outcome","reached","approach"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAID;;;;;;;;;CASC,GACD,OAAO,SAASA;IACd,OAAO;QACLC,UAAU;YACR;gBACEC,IAAI;gBACJC,OAAO;gBACPC,QAAQ;oBAAC;oBAAgB;iBAAa;gBACtCC,MAAM;YACR;SACD;QACDC,OAAO;YACL;gBACEJ,IAAI;gBACJK,OAAO;gBACPJ,OAAO;gBACPC,QAAQ;oBAAC;iBAAe;gBACxBI,aAAa;gBACbC,aAAa;gBACb,kEAAkE;gBAClE,mEAAmE;gBACnE,0BAA0B;gBAC1BC,OAAO;
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/plugins/workflows/src/lib/usage-axes.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\nimport type { PluginUsageAxesDeclaration } from '@aglyn/aglyn/plugin-manager/plugin-usage-axes'\n\n/**\n * THE WORKFLOWS PLUGIN'S METERS (AGL-3080): workflow and action runs, in the\n * platform's cost model and utilization table. Compiled into core by the\n * manifest generator (`register.usageAxes`).\n *\n * ONE cost axis for both counters: a run is the same handful of reads, two\n * writes and a moment of compute whichever builder produced it, so both are\n * priced at `perRun` on one line. TWO bands, because each builder sells its\n * own; the workflow band also warns the workspace as it fills.\n */\nexport function workflowsUsageAxes(): PluginUsageAxesDeclaration {\n return {\n costAxes: [\n {\n id: 'runs',\n order: 90,\n fields: ['workflowRuns', 'actionRuns'],\n rate: 'perRun',\n },\n ],\n bands: [\n {\n id: 'workflowRuns',\n label: 'Workflow runs',\n order: 100,\n fields: ['workflowRuns'],\n entitlement: 'workflowRunsPerMonth',\n hostCounter: 'workflowRuns',\n // The band is the workspace's: every run is counted on the org's\n // counter too, and the run gates are held to it (`run-meter.ts`).\n orgCounter: 'workflowRuns',\n // The runtime stops workflows silently at the monthly cap, so the\n // owner is warned approaching it and told on reaching it why their\n // automations went quiet.\n alert: {\n label: 'monthly workflow runs',\n noun: 'workflow runs',\n outcome: 'stops',\n reached:\n 'Workflows pause until next month and nothing is charged — ' +\n 'upgrade in Billing to keep them running.',\n approach:\n 'Nothing changes and nothing is charged — at the included amount, ' +\n 'workflows pause until next month unless you upgrade in Billing.',\n },\n },\n {\n id: 'actionRuns',\n label: 'Action runs',\n order: 110,\n fields: ['actionRuns'],\n entitlement: 'actionRunsPerMonth',\n orgCounter: 'actionRuns',\n },\n ],\n }\n}\n"],"names":["workflowsUsageAxes","costAxes","id","order","fields","rate","bands","label","entitlement","hostCounter","orgCounter","alert","noun","outcome","reached","approach"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAID;;;;;;;;;CASC,GACD,OAAO,SAASA;IACd,OAAO;QACLC,UAAU;YACR;gBACEC,IAAI;gBACJC,OAAO;gBACPC,QAAQ;oBAAC;oBAAgB;iBAAa;gBACtCC,MAAM;YACR;SACD;QACDC,OAAO;YACL;gBACEJ,IAAI;gBACJK,OAAO;gBACPJ,OAAO;gBACPC,QAAQ;oBAAC;iBAAe;gBACxBI,aAAa;gBACbC,aAAa;gBACb,iEAAiE;gBACjE,kEAAkE;gBAClEC,YAAY;gBACZ,kEAAkE;gBAClE,mEAAmE;gBACnE,0BAA0B;gBAC1BC,OAAO;oBACLJ,OAAO;oBACPK,MAAM;oBACNC,SAAS;oBACTC,SACE,+DACA;oBACFC,UACE,sEACA;gBACJ;YACF;YACA;gBACEb,IAAI;gBACJK,OAAO;gBACPJ,OAAO;gBACPC,QAAQ;oBAAC;iBAAa;gBACtBI,aAAa;gBACbE,YAAY;YACd;SACD;IACH;AACF"}
|
|
@@ -1,142 +0,0 @@
|
|
|
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 CrmActivityLink } from '@aglyn/aglyn/server';
|
|
18
|
-
import type { HostEventPayload } from '@aglyn/tenant-runtime/host-event-listeners';
|
|
19
|
-
import type { HostActionStep, HostActionStepType } from '@aglyn/aglyn/app-utils/actions';
|
|
20
|
-
/**
|
|
21
|
-
* The steps that act on the CRM (AGL-2605) — named as a set because the
|
|
22
|
-
* executor dispatches all five to one module and the docs list them as one
|
|
23
|
-
* group, and a step added to the union but not here would be a server step
|
|
24
|
-
* the executor silently skipped.
|
|
25
|
-
*/
|
|
26
|
-
export declare const CRM_ACTION_STEP_TYPES: ReadonlySet<HostActionStepType>;
|
|
27
|
-
/** The CRM steps, as the type the executor narrows to. */
|
|
28
|
-
export type CrmActionStep = Extract<HostActionStep, {
|
|
29
|
-
type: 'setContactStage' | 'addContactTag' | 'assignContactOwner' | 'createCrmTask' | 'logCrmActivity';
|
|
30
|
-
}>;
|
|
31
|
-
export declare function isCrmActionStep(step: HostActionStep): step is CrmActionStep;
|
|
32
|
-
/**
|
|
33
|
-
* The CRM steps of an action run (AGL-2605): what `setContactStage`,
|
|
34
|
-
* `addContactTag`, `assignContactOwner`, `createCrmTask` and
|
|
35
|
-
* `logCrmActivity` actually write.
|
|
36
|
-
*
|
|
37
|
-
* Split out of `run-event-actions.ts` because the five share one shape the
|
|
38
|
-
* other server steps do not: each begins by finding THE PERSON the event is
|
|
39
|
-
* about, and each writes either inside the site's facet on that person or a
|
|
40
|
-
* record stamped with the site's scope. One resolver and one scope
|
|
41
|
-
* expression, used five times, rather than five copies drifting apart.
|
|
42
|
-
*
|
|
43
|
-
* ## The person is the event's, never the step's
|
|
44
|
-
*
|
|
45
|
-
* A step carries no contact reference. The contact is whoever the event
|
|
46
|
-
* names — by `contactId` when the emitting door knew the document, and by
|
|
47
|
-
* `email` otherwise, which is what every pre-CRM event carries. Both
|
|
48
|
-
* lookups are scoped to what THIS site may see: a document the id names
|
|
49
|
-
* but the site cannot read is treated as absent, exactly as the scoped
|
|
50
|
-
* query would have treated it, so an event replayed against the wrong site
|
|
51
|
-
* reaches nobody.
|
|
52
|
-
*
|
|
53
|
-
* ## A missing person is a reported no-op
|
|
54
|
-
*
|
|
55
|
-
* Nothing is written and the step's error names the reason, which lands in
|
|
56
|
-
* the run summary beside the trigger — the same place "why didn't it
|
|
57
|
-
* fire?" is answered for every other step. Silence here would make a
|
|
58
|
-
* mis-wired trigger (a form with no email field) indistinguishable from a
|
|
59
|
-
* working one.
|
|
60
|
-
*/
|
|
61
|
-
export interface CrmStepEnv {
|
|
62
|
-
hostId: string;
|
|
63
|
-
/** The owning org's billing doc, already read by the run's gate. */
|
|
64
|
-
org: unknown;
|
|
65
|
-
orgId: string | null;
|
|
66
|
-
}
|
|
67
|
-
export interface CrmStepOutcome {
|
|
68
|
-
/** The reason nothing was written, when nothing was. */
|
|
69
|
-
error?: string;
|
|
70
|
-
/** The one fact worth carrying into the run summary. */
|
|
71
|
-
detail?: string;
|
|
72
|
-
/**
|
|
73
|
-
* An event this step's write earned, for the CALLER to fan out under its
|
|
74
|
-
* own depth guard. Not emitted from here: a stage set by an automation is
|
|
75
|
-
* itself a stage change, and the action listening for it must run — but
|
|
76
|
-
* through the same nesting cap a `customEvent` chain has, or an action
|
|
77
|
-
* that sets the stage it listens for would run until the meter ran dry.
|
|
78
|
-
*/
|
|
79
|
-
emit?: {
|
|
80
|
-
event: 'contactStageChanged';
|
|
81
|
-
payload: HostEventPayload;
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* Runs one CRM step for the person the event names. Never throws for a
|
|
86
|
-
* missing person, an unknown owner or a site with no org — those are
|
|
87
|
-
* answered as `error` so the run records them; a Firestore failure
|
|
88
|
-
* propagates to the executor's catch like every other step's would.
|
|
89
|
-
*/
|
|
90
|
-
export declare function runCrmActionStep(env: CrmStepEnv, actionId: string, step: CrmActionStep, payload: HostEventPayload, nowMs?: number): Promise<CrmStepOutcome>;
|
|
91
|
-
/**
|
|
92
|
-
* The activity row a `sendEmail` step's message will be logged as, prepared
|
|
93
|
-
* BEFORE the send (AGL-2615): the minted reference whose id rides the
|
|
94
|
-
* message as a tag, the links it files under, and the scope it is stamped
|
|
95
|
-
* with. `null` when the message earns no row.
|
|
96
|
-
*/
|
|
97
|
-
export interface PreparedCrmEmailActivity {
|
|
98
|
-
ref: FirebaseFirestore.DocumentReference;
|
|
99
|
-
/** The provider tags the delivery webhook finds the row by. */
|
|
100
|
-
tags: {
|
|
101
|
-
name: string;
|
|
102
|
-
value: string;
|
|
103
|
-
}[];
|
|
104
|
-
link: CrmActivityLink;
|
|
105
|
-
visibleTo: string[];
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* Whether — and where — an automation's email lands on a timeline.
|
|
109
|
-
*
|
|
110
|
-
* A `sendEmail` step is not a CRM step: it mails whatever address the event
|
|
111
|
-
* carries, to a person who may be nobody the CRM knows. It earns a row on
|
|
112
|
-
* exactly one condition, that the message is ADDRESSED TO THE CONTACT the
|
|
113
|
-
* event is about — the person `resolveEventContact` finds, at the address
|
|
114
|
-
* the row holds. A welcome sequence to a new contact is that; an internal
|
|
115
|
-
* alert routed to a merchant's own address through `toField` is not, and a
|
|
116
|
-
* row for it would put the merchant's inbox on a customer's history.
|
|
117
|
-
*
|
|
118
|
-
* Prepared ahead of the send, for the reason the console route mints its
|
|
119
|
-
* id first: the webhook has nothing but the tags on the message to find
|
|
120
|
-
* the row with. Nothing is written here. A record at the activity ceiling
|
|
121
|
-
* earns no row — the message still goes, because the ceiling bounds the
|
|
122
|
-
* log and not the mail — and neither does a site with no org to hold one.
|
|
123
|
-
*
|
|
124
|
-
* **Never throws.** The row is bookkeeping beside a send, and a lookup that
|
|
125
|
-
* fails must not become a message that never left — the posture every
|
|
126
|
-
* meter beside `sendEmail` takes. A failure here is logged and the message
|
|
127
|
-
* goes out untagged.
|
|
128
|
-
*/
|
|
129
|
-
export declare function prepareCrmEmailActivity(env: CrmStepEnv, to: string, payload: HostEventPayload): Promise<PreparedCrmEmailActivity | null>;
|
|
130
|
-
/**
|
|
131
|
-
* Writes the prepared row, once the provider has accepted the message.
|
|
132
|
-
*
|
|
133
|
-
* The same shape the console route logs — `buildCrmEmailActivity` is the
|
|
134
|
-
* one builder — with the automation as its source and nobody as its
|
|
135
|
-
* author. **Never throws**: the message has left, and a row that could not
|
|
136
|
-
* be written is a gap on the timeline rather than a failed step.
|
|
137
|
-
*/
|
|
138
|
-
export declare function logCrmEmailActivity(env: CrmStepEnv, prepared: PreparedCrmEmailActivity, message: {
|
|
139
|
-
subject: string;
|
|
140
|
-
body: string;
|
|
141
|
-
to: string;
|
|
142
|
-
}, actionId: string, nowMs?: number): Promise<void>;
|