@aglyn/plugins-workflows 1.0.0-beta.179 → 1.0.0-beta.181
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/package.json +12 -11
- package/src/lib/components/automation-trigger-conditions.component.d.ts +43 -0
- package/src/lib/components/automation-trigger-conditions.component.js +189 -0
- package/src/lib/components/automation-trigger-conditions.component.js.map +1 -0
- package/src/lib/components/host-actions-card.component.js +27 -176
- package/src/lib/components/host-actions-card.component.js.map +1 -1
- package/src/lib/components/org-automation-editor.component.d.ts +51 -0
- package/src/lib/components/org-automation-editor.component.js +310 -0
- package/src/lib/components/org-automation-editor.component.js.map +1 -0
- package/src/lib/components/org-automations-card.component.d.ts +28 -0
- package/src/lib/components/org-automations-card.component.js +373 -0
- package/src/lib/components/org-automations-card.component.js.map +1 -0
- package/src/lib/components/org-site-automation-list.component.d.ts +43 -0
- package/src/lib/components/org-site-automation-list.component.js +289 -0
- package/src/lib/components/org-site-automation-list.component.js.map +1 -0
- package/src/lib/components/site-org-automations-panel.component.d.ts +22 -0
- package/src/lib/components/site-org-automations-panel.component.js +201 -0
- package/src/lib/components/site-org-automations-panel.component.js.map +1 -0
- package/src/lib/components/use-org-automation-step-pickers.d.ts +20 -0
- package/src/lib/components/use-org-automation-step-pickers.js +108 -0
- package/src/lib/components/use-org-automation-step-pickers.js.map +1 -0
- package/src/lib/components/use-org-automations-api.d.ts +16 -0
- package/src/lib/components/use-org-automations-api.js +55 -0
- package/src/lib/components/use-org-automations-api.js.map +1 -0
- package/src/lib/components/workflows-console-page.js +63 -1
- package/src/lib/components/workflows-console-page.js.map +1 -1
- package/src/lib/components/workflows-console-sections.d.ts +19 -1
- package/src/lib/components/workflows-console-sections.js +37 -2
- package/src/lib/components/workflows-console-sections.js.map +1 -1
- package/src/lib/components/workflows-org-mount.d.ts +52 -0
- package/src/lib/components/workflows-org-mount.js +49 -0
- package/src/lib/components/workflows-org-mount.js.map +1 -0
- package/src/lib/engine/flow-enrollments.d.ts +26 -10
- package/src/lib/engine/flow-enrollments.js +14 -7
- package/src/lib/engine/flow-enrollments.js.map +1 -1
- package/src/lib/engine/run-event-actions.d.ts +57 -4
- package/src/lib/engine/run-event-actions.js +119 -19
- package/src/lib/engine/run-event-actions.js.map +1 -1
- package/src/lib/engine/run-org-automations.d.ts +75 -0
- package/src/lib/engine/run-org-automations.js +154 -0
- package/src/lib/engine/run-org-automations.js.map +1 -0
- package/src/lib/model/org-automations.d.ts +232 -0
- package/src/lib/model/org-automations.js +423 -0
- package/src/lib/model/org-automations.js.map +1 -0
- package/src/lib/plugin.js +26 -1
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/server/org-automations-routes.d.ts +88 -0
- package/src/lib/server/org-automations-routes.js +474 -0
- package/src/lib/server/org-automations-routes.js.map +1 -0
- package/src/lib/server-console.d.ts +2 -1
- package/src/lib/server-console.js +12 -2
- package/src/lib/server-console.js.map +1 -1
|
@@ -0,0 +1,75 @@
|
|
|
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 FlowEnrollment } from './flow-enrollments';
|
|
18
|
+
/**
|
|
19
|
+
* THE ORGANIZATION'S AUTOMATIONS, as the engine meets them (AGL-3302).
|
|
20
|
+
*
|
|
21
|
+
* Two doors lead here. An event arrives and the site's own actions are
|
|
22
|
+
* joined by the org automations placed on the site — `runEventActions` asks
|
|
23
|
+
* {@link findOrgAutomationsForEvent} and runs what it answers beside them. A
|
|
24
|
+
* person waiting inside an org automation comes due, or the event they were
|
|
25
|
+
* waiting for arrives, and {@link resumeOrgAutomationEnrollment} continues
|
|
26
|
+
* them against the organization's document.
|
|
27
|
+
*
|
|
28
|
+
* Both run on the event's SITE. The run environment is the site's, so the
|
|
29
|
+
* sending identity, the suppression lists, the consent group, the email meter
|
|
30
|
+
* and the run allowance are all the site's — an automation placed on five
|
|
31
|
+
* sites is five sites running it for themselves.
|
|
32
|
+
*/
|
|
33
|
+
/** The org automations one event runs on one site, and whose they are. */
|
|
34
|
+
export interface PlacedOrgAutomations {
|
|
35
|
+
orgId: string;
|
|
36
|
+
docs: FirebaseFirestore.QueryDocumentSnapshot[];
|
|
37
|
+
}
|
|
38
|
+
/** Test seam: forget every remembered organization. */
|
|
39
|
+
export declare function resetOrgAutomationLookupsForTests(): void;
|
|
40
|
+
/**
|
|
41
|
+
* The org automations this event runs on this site, or null when there are
|
|
42
|
+
* none — including when the event is not one an org automation can start on,
|
|
43
|
+
* which is answered before any read.
|
|
44
|
+
*
|
|
45
|
+
* One query: the event, switched on, and placed on this site — by the org
|
|
46
|
+
* token or the site's own — capped at {@link MAX_TRIGGERED_ORG_AUTOMATIONS}.
|
|
47
|
+
* `enabled` is in the query rather than filtered after it so a switched-off
|
|
48
|
+
* or deleted automation (deletion switches it off too) can never take one of
|
|
49
|
+
* the capped places; a PAUSE is per site and cannot be asked of an array in
|
|
50
|
+
* the same query, so a paused automation is dropped here, after it.
|
|
51
|
+
*
|
|
52
|
+
* Served by the composite index on `automations` — `visibleTo` CONTAINS,
|
|
53
|
+
* `trigger.event`, `enabled` — in `cloud/firebase-firestore.indexes.json`.
|
|
54
|
+
*
|
|
55
|
+
* Never throws: an organization that cannot be read runs no org automation,
|
|
56
|
+
* and the site's own actions run exactly as they would have.
|
|
57
|
+
*/
|
|
58
|
+
export declare function findOrgAutomationsForEvent(hostId: string, event: string): Promise<PlacedOrgAutomations | null>;
|
|
59
|
+
/**
|
|
60
|
+
* Continues one person waiting inside an org automation, on the site they
|
|
61
|
+
* were enrolled on — the org twin of the site action's resume, asking the
|
|
62
|
+
* same questions of the organization's document instead of the site's.
|
|
63
|
+
*
|
|
64
|
+
* - The kill switch is `orgs/{orgId}/automations/{id}`: deleted, switched
|
|
65
|
+
* off, taken off this site, or paused on this site, and the person stops.
|
|
66
|
+
* - The site must still belong to that organization, and its plan must
|
|
67
|
+
* still carry the actions builder.
|
|
68
|
+
* - The snapshot runs, never the automation's current steps.
|
|
69
|
+
* - Counted on this site's action-run meter, never refused by it: the person
|
|
70
|
+
* is already inside, and the gate belongs at enrollment.
|
|
71
|
+
*/
|
|
72
|
+
export declare function resumeOrgAutomationEnrollment(enrollment: FlowEnrollment, ref: FirebaseFirestore.DocumentReference, options: {
|
|
73
|
+
timedOut?: boolean;
|
|
74
|
+
nowMs: number;
|
|
75
|
+
}): Promise<'ran' | 'waiting' | 'exited' | 'deferred' | 'stopped'>;
|
|
@@ -0,0 +1,154 @@
|
|
|
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
|
+
*/ import { checkEntitlement, FLOW_TIMED_OUT_FIELD } from "@aglyn/aglyn/server";
|
|
18
|
+
import { scopeTokensForHost } from "@aglyn/aglyn/app-utils/scope-tokens";
|
|
19
|
+
import { firebaseAdmin, getOrgForHost, resolveOrgIdForHost } from "@aglyn/tenant-data-admin";
|
|
20
|
+
import { FieldValue } from "firebase-admin/firestore";
|
|
21
|
+
import { isOrgAutomationTriggerEvent, MAX_TRIGGERED_ORG_AUTOMATIONS, ORG_AUTOMATIONS_COLLECTION, orgAutomationRunsOnHost, orgAutomationStopReason } from "../model/org-automations.js";
|
|
22
|
+
import { deferFlowEnrollment, endFlowEnrollment } from "./flow-enrollments.js";
|
|
23
|
+
import { executeAction, FLOW_CLAIM_RETRY_MS, makeWorkflowContextLoader, stopFlowEnrollment } from "./run-event-actions.js";
|
|
24
|
+
/**
|
|
25
|
+
* How long a site's organization is remembered between events.
|
|
26
|
+
*
|
|
27
|
+
* The `hostIndex` read is the price of asking the organization at all, and it
|
|
28
|
+
* repeats on every server event of a busy site and again for each event a
|
|
29
|
+
* run's own steps raise. Remembered briefly rather than forever: a site that
|
|
30
|
+
* changes hands is picked up within the window, and the engine refuses to run
|
|
31
|
+
* a remembered organization's automations for a site whose current owner —
|
|
32
|
+
* read fresh by the entitlement gate before anything runs — is another.
|
|
33
|
+
*/ const ORG_LOOKUP_TTL_MS = 60000;
|
|
34
|
+
/** Entries kept before the memo starts over; far past one instance's working set. */ const ORG_LOOKUP_MAX_ENTRIES = 1000;
|
|
35
|
+
const orgLookups = new Map();
|
|
36
|
+
/** Test seam: forget every remembered organization. */ export function resetOrgAutomationLookupsForTests() {
|
|
37
|
+
orgLookups.clear();
|
|
38
|
+
}
|
|
39
|
+
async function orgIdForHost(hostId) {
|
|
40
|
+
const nowMs = Date.now();
|
|
41
|
+
const known = orgLookups.get(hostId);
|
|
42
|
+
if (known && nowMs - known.atMs < ORG_LOOKUP_TTL_MS) return known.orgId;
|
|
43
|
+
const orgId = await resolveOrgIdForHost(hostId);
|
|
44
|
+
if (orgLookups.size >= ORG_LOOKUP_MAX_ENTRIES) orgLookups.clear();
|
|
45
|
+
orgLookups.set(hostId, {
|
|
46
|
+
orgId,
|
|
47
|
+
atMs: nowMs
|
|
48
|
+
});
|
|
49
|
+
return orgId;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The org automations this event runs on this site, or null when there are
|
|
53
|
+
* none — including when the event is not one an org automation can start on,
|
|
54
|
+
* which is answered before any read.
|
|
55
|
+
*
|
|
56
|
+
* One query: the event, switched on, and placed on this site — by the org
|
|
57
|
+
* token or the site's own — capped at {@link MAX_TRIGGERED_ORG_AUTOMATIONS}.
|
|
58
|
+
* `enabled` is in the query rather than filtered after it so a switched-off
|
|
59
|
+
* or deleted automation (deletion switches it off too) can never take one of
|
|
60
|
+
* the capped places; a PAUSE is per site and cannot be asked of an array in
|
|
61
|
+
* the same query, so a paused automation is dropped here, after it.
|
|
62
|
+
*
|
|
63
|
+
* Served by the composite index on `automations` — `visibleTo` CONTAINS,
|
|
64
|
+
* `trigger.event`, `enabled` — in `cloud/firebase-firestore.indexes.json`.
|
|
65
|
+
*
|
|
66
|
+
* Never throws: an organization that cannot be read runs no org automation,
|
|
67
|
+
* and the site's own actions run exactly as they would have.
|
|
68
|
+
*/ export async function findOrgAutomationsForEvent(hostId, event) {
|
|
69
|
+
if (!isOrgAutomationTriggerEvent(event)) return null;
|
|
70
|
+
try {
|
|
71
|
+
const orgId = await orgIdForHost(hostId);
|
|
72
|
+
if (!orgId) return null;
|
|
73
|
+
const snapshot = await firebaseAdmin.app().firestore().collection('orgs').doc(orgId).collection(ORG_AUTOMATIONS_COLLECTION).where('trigger.event', '==', event).where('enabled', '==', true).where('visibleTo', 'array-contains-any', scopeTokensForHost(hostId)).limit(MAX_TRIGGERED_ORG_AUTOMATIONS).get();
|
|
74
|
+
const docs = snapshot.docs.filter((doc)=>orgAutomationRunsOnHost(doc.data(), hostId));
|
|
75
|
+
return docs.length ? {
|
|
76
|
+
orgId,
|
|
77
|
+
docs
|
|
78
|
+
} : null;
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.error('[org automations] lookup failed', hostId, event, error);
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Continues one person waiting inside an org automation, on the site they
|
|
86
|
+
* were enrolled on — the org twin of the site action's resume, asking the
|
|
87
|
+
* same questions of the organization's document instead of the site's.
|
|
88
|
+
*
|
|
89
|
+
* - The kill switch is `orgs/{orgId}/automations/{id}`: deleted, switched
|
|
90
|
+
* off, taken off this site, or paused on this site, and the person stops.
|
|
91
|
+
* - The site must still belong to that organization, and its plan must
|
|
92
|
+
* still carry the actions builder.
|
|
93
|
+
* - The snapshot runs, never the automation's current steps.
|
|
94
|
+
* - Counted on this site's action-run meter, never refused by it: the person
|
|
95
|
+
* is already inside, and the gate belongs at enrollment.
|
|
96
|
+
*/ export async function resumeOrgAutomationEnrollment(enrollment, ref, options) {
|
|
97
|
+
var _enrollment_orgId, _owner_org, _enrollment_payload;
|
|
98
|
+
const { nowMs } = options;
|
|
99
|
+
const hostId = enrollment.hostId;
|
|
100
|
+
const firestore = firebaseAdmin.app().firestore();
|
|
101
|
+
const hostRef = firestore.collection('hosts').doc(hostId);
|
|
102
|
+
const stop = (reason)=>stopFlowEnrollment(enrollment, ref, reason);
|
|
103
|
+
const orgId = String((_enrollment_orgId = enrollment.orgId) != null ? _enrollment_orgId : '');
|
|
104
|
+
const doc = orgId ? await firestore.collection('orgs').doc(orgId).collection(ORG_AUTOMATIONS_COLLECTION).doc(enrollment.actionId).get().catch(()=>null) : null;
|
|
105
|
+
const automation = (doc == null ? void 0 : doc.exists) ? doc.data() : null;
|
|
106
|
+
if (!automation) return await stop('the org automation was deleted');
|
|
107
|
+
const refusal = orgAutomationStopReason(automation, hostId);
|
|
108
|
+
if (refusal) return await stop(refusal);
|
|
109
|
+
const owner = await getOrgForHost(hostId).catch(()=>null);
|
|
110
|
+
if (!owner || owner.orgId !== orgId) {
|
|
111
|
+
return await stop('this site is no longer part of the organization that runs the org automation');
|
|
112
|
+
}
|
|
113
|
+
if (!checkEntitlement(owner.org, 'actions')) {
|
|
114
|
+
return await stop('this site’s plan no longer includes automations');
|
|
115
|
+
}
|
|
116
|
+
const env = {
|
|
117
|
+
hostId,
|
|
118
|
+
hostRef,
|
|
119
|
+
alerts: [],
|
|
120
|
+
// Admitted by the `actions` gate above.
|
|
121
|
+
actionsAllowed: true,
|
|
122
|
+
webhooksAllowed: checkEntitlement(owner.org, 'webhooks'),
|
|
123
|
+
crmAllowed: checkEntitlement(owner.org, 'crm'),
|
|
124
|
+
depth: 0,
|
|
125
|
+
org: (_owner_org = owner.org) != null ? _owner_org : null,
|
|
126
|
+
orgId: owner.orgId,
|
|
127
|
+
loadWorkflowContext: makeWorkflowContextLoader(hostRef)
|
|
128
|
+
};
|
|
129
|
+
const payload = _extends({}, (_enrollment_payload = enrollment.payload) != null ? _enrollment_payload : {}, options.timedOut ? {
|
|
130
|
+
[FLOW_TIMED_OUT_FIELD]: true
|
|
131
|
+
} : {});
|
|
132
|
+
const ending = await executeAction(env, enrollment.actionId, automation, enrollment.event, payload, {
|
|
133
|
+
startIndex: enrollment.nextStepIndex,
|
|
134
|
+
steps: enrollment.steps,
|
|
135
|
+
enrollmentRef: ref,
|
|
136
|
+
orgAutomation: {
|
|
137
|
+
orgId
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
if (ending === 'deferred') {
|
|
141
|
+
await deferFlowEnrollment(ref, nowMs + FLOW_CLAIM_RETRY_MS, nowMs);
|
|
142
|
+
return ending;
|
|
143
|
+
}
|
|
144
|
+
if (ending !== 'waiting') await endFlowEnrollment(ref);
|
|
145
|
+
const monthKey = new Date(nowMs).toISOString().slice(0, 7);
|
|
146
|
+
await hostRef.collection('counters').doc('actionRuns').set({
|
|
147
|
+
[monthKey]: FieldValue.increment(1)
|
|
148
|
+
}, {
|
|
149
|
+
merge: true
|
|
150
|
+
}).catch(()=>undefined);
|
|
151
|
+
return ending;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
//# sourceMappingURL=run-org-automations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/workflows/src/lib/engine/run-org-automations.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 {\n checkEntitlement,\n FLOW_TIMED_OUT_FIELD,\n type HostActionStep,\n} from '@aglyn/aglyn/server'\nimport { scopeTokensForHost } from '@aglyn/aglyn/app-utils/scope-tokens'\nimport {\n firebaseAdmin,\n getOrgForHost,\n resolveOrgIdForHost,\n} from '@aglyn/tenant-data-admin'\nimport type { HostEventPayload } from '@aglyn/tenant-runtime/host-event-listeners'\nimport { FieldValue } from 'firebase-admin/firestore'\nimport {\n isOrgAutomationTriggerEvent,\n MAX_TRIGGERED_ORG_AUTOMATIONS,\n ORG_AUTOMATIONS_COLLECTION,\n type OrgAutomation,\n orgAutomationRunsOnHost,\n orgAutomationStopReason,\n} from '../model/org-automations'\nimport {\n deferFlowEnrollment,\n endFlowEnrollment,\n type FlowEnrollment,\n} from './flow-enrollments'\nimport {\n type ActionRunEnv,\n executeAction,\n FLOW_CLAIM_RETRY_MS,\n makeWorkflowContextLoader,\n stopFlowEnrollment,\n} from './run-event-actions'\n\n/**\n * THE ORGANIZATION'S AUTOMATIONS, as the engine meets them (AGL-3302).\n *\n * Two doors lead here. An event arrives and the site's own actions are\n * joined by the org automations placed on the site — `runEventActions` asks\n * {@link findOrgAutomationsForEvent} and runs what it answers beside them. A\n * person waiting inside an org automation comes due, or the event they were\n * waiting for arrives, and {@link resumeOrgAutomationEnrollment} continues\n * them against the organization's document.\n *\n * Both run on the event's SITE. The run environment is the site's, so the\n * sending identity, the suppression lists, the consent group, the email meter\n * and the run allowance are all the site's — an automation placed on five\n * sites is five sites running it for themselves.\n */\n\n/** The org automations one event runs on one site, and whose they are. */\nexport interface PlacedOrgAutomations {\n orgId: string\n docs: FirebaseFirestore.QueryDocumentSnapshot[]\n}\n\n/**\n * How long a site's organization is remembered between events.\n *\n * The `hostIndex` read is the price of asking the organization at all, and it\n * repeats on every server event of a busy site and again for each event a\n * run's own steps raise. Remembered briefly rather than forever: a site that\n * changes hands is picked up within the window, and the engine refuses to run\n * a remembered organization's automations for a site whose current owner —\n * read fresh by the entitlement gate before anything runs — is another.\n */\nconst ORG_LOOKUP_TTL_MS = 60_000\n\n/** Entries kept before the memo starts over; far past one instance's working set. */\nconst ORG_LOOKUP_MAX_ENTRIES = 1_000\n\nconst orgLookups = new Map<string, { orgId: string | null; atMs: number }>()\n\n/** Test seam: forget every remembered organization. */\nexport function resetOrgAutomationLookupsForTests(): void {\n orgLookups.clear()\n}\n\nasync function orgIdForHost(hostId: string): Promise<string | null> {\n const nowMs = Date.now()\n const known = orgLookups.get(hostId)\n if (known && nowMs - known.atMs < ORG_LOOKUP_TTL_MS) return known.orgId\n const orgId = await resolveOrgIdForHost(hostId)\n if (orgLookups.size >= ORG_LOOKUP_MAX_ENTRIES) orgLookups.clear()\n orgLookups.set(hostId, { orgId, atMs: nowMs })\n return orgId\n}\n\n/**\n * The org automations this event runs on this site, or null when there are\n * none — including when the event is not one an org automation can start on,\n * which is answered before any read.\n *\n * One query: the event, switched on, and placed on this site — by the org\n * token or the site's own — capped at {@link MAX_TRIGGERED_ORG_AUTOMATIONS}.\n * `enabled` is in the query rather than filtered after it so a switched-off\n * or deleted automation (deletion switches it off too) can never take one of\n * the capped places; a PAUSE is per site and cannot be asked of an array in\n * the same query, so a paused automation is dropped here, after it.\n *\n * Served by the composite index on `automations` — `visibleTo` CONTAINS,\n * `trigger.event`, `enabled` — in `cloud/firebase-firestore.indexes.json`.\n *\n * Never throws: an organization that cannot be read runs no org automation,\n * and the site's own actions run exactly as they would have.\n */\nexport async function findOrgAutomationsForEvent(\n hostId: string,\n event: string,\n): Promise<PlacedOrgAutomations | null> {\n if (!isOrgAutomationTriggerEvent(event)) return null\n try {\n const orgId = await orgIdForHost(hostId)\n if (!orgId) return null\n const snapshot = await firebaseAdmin\n .app()\n .firestore()\n .collection('orgs')\n .doc(orgId)\n .collection(ORG_AUTOMATIONS_COLLECTION)\n .where('trigger.event', '==', event)\n .where('enabled', '==', true)\n .where('visibleTo', 'array-contains-any', scopeTokensForHost(hostId))\n .limit(MAX_TRIGGERED_ORG_AUTOMATIONS)\n .get()\n const docs = snapshot.docs.filter((doc) =>\n orgAutomationRunsOnHost(doc.data() as OrgAutomation, hostId),\n )\n return docs.length ? { orgId, docs } : null\n } catch (error) {\n console.error('[org automations] lookup failed', hostId, event, error)\n return null\n }\n}\n\n/**\n * Continues one person waiting inside an org automation, on the site they\n * were enrolled on — the org twin of the site action's resume, asking the\n * same questions of the organization's document instead of the site's.\n *\n * - The kill switch is `orgs/{orgId}/automations/{id}`: deleted, switched\n * off, taken off this site, or paused on this site, and the person stops.\n * - The site must still belong to that organization, and its plan must\n * still carry the actions builder.\n * - The snapshot runs, never the automation's current steps.\n * - Counted on this site's action-run meter, never refused by it: the person\n * is already inside, and the gate belongs at enrollment.\n */\nexport async function resumeOrgAutomationEnrollment(\n enrollment: FlowEnrollment,\n ref: FirebaseFirestore.DocumentReference,\n options: { timedOut?: boolean; nowMs: number },\n): Promise<'ran' | 'waiting' | 'exited' | 'deferred' | 'stopped'> {\n const { nowMs } = options\n const hostId = enrollment.hostId\n const firestore = firebaseAdmin.app().firestore()\n const hostRef = firestore.collection('hosts').doc(hostId)\n const stop = (reason: string) => stopFlowEnrollment(enrollment, ref, reason)\n\n const orgId = String(enrollment.orgId ?? '')\n const doc = orgId\n ? await firestore\n .collection('orgs')\n .doc(orgId)\n .collection(ORG_AUTOMATIONS_COLLECTION)\n .doc(enrollment.actionId)\n .get()\n .catch(() => null)\n : null\n const automation = doc?.exists ? (doc.data() as OrgAutomation) : null\n if (!automation) return await stop('the org automation was deleted')\n const refusal = orgAutomationStopReason(automation, hostId)\n if (refusal) return await stop(refusal)\n\n const owner = await getOrgForHost(hostId).catch(() => null)\n if (!owner || owner.orgId !== orgId) {\n return await stop(\n 'this site is no longer part of the organization that runs the org automation',\n )\n }\n if (!checkEntitlement(owner.org as any, 'actions')) {\n return await stop('this site’s plan no longer includes automations')\n }\n\n const env: ActionRunEnv = {\n hostId,\n hostRef,\n alerts: [],\n // Admitted by the `actions` gate above.\n actionsAllowed: true,\n webhooksAllowed: checkEntitlement(owner.org as any, 'webhooks'),\n crmAllowed: checkEntitlement(owner.org as any, 'crm'),\n depth: 0,\n org: owner.org ?? null,\n orgId: owner.orgId,\n loadWorkflowContext: makeWorkflowContextLoader(hostRef),\n }\n const payload: HostEventPayload = {\n ...((enrollment.payload ?? {}) as HostEventPayload),\n // The timeout BRANCH, as on a site action's resume.\n ...(options.timedOut ? { [FLOW_TIMED_OUT_FIELD]: true } : {}),\n }\n const ending = await executeAction(\n env,\n enrollment.actionId,\n automation,\n enrollment.event,\n payload,\n {\n startIndex: enrollment.nextStepIndex,\n steps: enrollment.steps as HostActionStep[],\n enrollmentRef: ref,\n orgAutomation: { orgId },\n },\n )\n if (ending === 'deferred') {\n await deferFlowEnrollment(ref, nowMs + FLOW_CLAIM_RETRY_MS, nowMs)\n return ending\n }\n if (ending !== 'waiting') await endFlowEnrollment(ref)\n const monthKey = new Date(nowMs).toISOString().slice(0, 7)\n await hostRef\n .collection('counters')\n .doc('actionRuns')\n .set({ [monthKey]: FieldValue.increment(1) }, { merge: true })\n .catch(() => undefined)\n return ending\n}\n"],"names":["checkEntitlement","FLOW_TIMED_OUT_FIELD","scopeTokensForHost","firebaseAdmin","getOrgForHost","resolveOrgIdForHost","FieldValue","isOrgAutomationTriggerEvent","MAX_TRIGGERED_ORG_AUTOMATIONS","ORG_AUTOMATIONS_COLLECTION","orgAutomationRunsOnHost","orgAutomationStopReason","deferFlowEnrollment","endFlowEnrollment","executeAction","FLOW_CLAIM_RETRY_MS","makeWorkflowContextLoader","stopFlowEnrollment","ORG_LOOKUP_TTL_MS","ORG_LOOKUP_MAX_ENTRIES","orgLookups","Map","resetOrgAutomationLookupsForTests","clear","orgIdForHost","hostId","nowMs","Date","now","known","get","atMs","orgId","size","set","findOrgAutomationsForEvent","event","snapshot","app","firestore","collection","doc","where","limit","docs","filter","data","length","error","console","resumeOrgAutomationEnrollment","enrollment","ref","options","owner","hostRef","stop","reason","String","actionId","catch","automation","exists","refusal","org","env","alerts","actionsAllowed","webhooksAllowed","crmAllowed","depth","loadWorkflowContext","payload","timedOut","ending","startIndex","nextStepIndex","steps","enrollmentRef","orgAutomation","monthKey","toISOString","slice","increment","merge","undefined"],"mappings":"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"}
|
|
@@ -0,0 +1,232 @@
|
|
|
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 HostActionStep, type HostActionTriggerCondition, type TriggerCombinator } from '@aglyn/aglyn/app-utils/actions';
|
|
18
|
+
import { type ScopeToken } from '@aglyn/aglyn/app-utils/scope-tokens';
|
|
19
|
+
/**
|
|
20
|
+
* ORG AUTOMATIONS (AGL-3302): one automation the organization writes once and
|
|
21
|
+
* places on the sites it chooses.
|
|
22
|
+
*
|
|
23
|
+
* A site's own actions stay where they are, at `hosts/{hostId}/actions`. They
|
|
24
|
+
* are not moved here, and the reasons are the four things a site action can
|
|
25
|
+
* do that an org automation must not:
|
|
26
|
+
*
|
|
27
|
+
* - call the site's functions and variables through a workflow step — the
|
|
28
|
+
* working set a computed variable binds to, which is one site's;
|
|
29
|
+
* - run the steps only a visitor's page can run (overlays, drawers, class
|
|
30
|
+
* toggles, a redirect to a screen), which the `actions` collection also
|
|
31
|
+
* holds for element interactions;
|
|
32
|
+
* - be fired by `/api/events/dispatch`, which is unauthenticated and ties an
|
|
33
|
+
* action id to a site only by its path;
|
|
34
|
+
* - wait, with the resume beat reading a missing document as the kill
|
|
35
|
+
* switch, so a migration would stop every person already inside a flow.
|
|
36
|
+
*
|
|
37
|
+
* An org automation is therefore its own document with a narrower
|
|
38
|
+
* vocabulary: triggers only the server raises, and steps that mean the same
|
|
39
|
+
* thing on whichever site they run. Each run executes AS the site the event
|
|
40
|
+
* happened on — that site's sender, suppressions, consent group, meters and
|
|
41
|
+
* run allowance — so placing one automation on five sites is five sites each
|
|
42
|
+
* running it for themselves, never one site acting for the others.
|
|
43
|
+
*
|
|
44
|
+
* Pure and client-safe: the console's editor, the server routes and the
|
|
45
|
+
* engine read the same definitions from here.
|
|
46
|
+
*/
|
|
47
|
+
/** `orgs/{orgId}/automations/{automationId}`. */
|
|
48
|
+
export declare const ORG_AUTOMATIONS_COLLECTION = "automations";
|
|
49
|
+
/**
|
|
50
|
+
* The two server doors onto the collection, under the plugin's `automations`
|
|
51
|
+
* API prefix: the organization's (`manage`) and a site's own pause
|
|
52
|
+
* (`pause`). Named here so the console's caller and the server's registration
|
|
53
|
+
* read one spelling.
|
|
54
|
+
*/
|
|
55
|
+
export declare const ORG_AUTOMATION_API_ROUTES: {
|
|
56
|
+
readonly manage: "automations/manage";
|
|
57
|
+
readonly pause: "automations/pause";
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* How many live org automations one organization may hold.
|
|
61
|
+
*
|
|
62
|
+
* A flat platform cap in the shape `ACTIONS_MAX_PER_HOST` takes, and smaller,
|
|
63
|
+
* because the two are different things: a site action is authored per site
|
|
64
|
+
* and per element, where an org automation is a shared building block placed
|
|
65
|
+
* on many sites at once. A hundred is far past what an organization writes by
|
|
66
|
+
* hand and keeps the org hub's list — which reads every live one — a single
|
|
67
|
+
* bounded window. Soft-deleted documents do not count.
|
|
68
|
+
*/
|
|
69
|
+
export declare const ORG_AUTOMATIONS_MAX = 100;
|
|
70
|
+
/**
|
|
71
|
+
* How many org automations one event may run on one site: the same fan-out
|
|
72
|
+
* bound the site's own actions take (`MAX_TRIGGERED_ACTIONS`), counted
|
|
73
|
+
* separately so the two cannot crowd each other out of the query.
|
|
74
|
+
*/
|
|
75
|
+
export declare const MAX_TRIGGERED_ORG_AUTOMATIONS = 10;
|
|
76
|
+
/**
|
|
77
|
+
* The run history's target type for a run of an org automation, on the
|
|
78
|
+
* activity feed of the site it ran on.
|
|
79
|
+
*/
|
|
80
|
+
export declare const ORG_AUTOMATION_RUN_TARGET = "orgAutomation";
|
|
81
|
+
/**
|
|
82
|
+
* The org activity target an org automation's own lifecycle rows are filed
|
|
83
|
+
* under: the plugin's namespaced resource (`pluginId:noun`), which the org's
|
|
84
|
+
* feed reads as "Automation" — core's target list names only core's resources.
|
|
85
|
+
*/
|
|
86
|
+
export declare const ORG_AUTOMATION_ACTIVITY_TARGET = "workflows:automation";
|
|
87
|
+
/**
|
|
88
|
+
* The events an org automation may start on: every host event the SERVER
|
|
89
|
+
* raises and a person can be the subject of.
|
|
90
|
+
*
|
|
91
|
+
* Listed rather than derived from `HOST_EVENT_TYPES`, deliberately. The engine
|
|
92
|
+
* queries the organization's automations on every event in this list, so a
|
|
93
|
+
* new host event must not become an org trigger — and a new read on every
|
|
94
|
+
* occurrence of it — by being added to the platform's list. Two are left out
|
|
95
|
+
* on purpose: `pageView` fires on every request to every published page, and
|
|
96
|
+
* an org query on it would be a read per visitor; `memberSignOut` is raised
|
|
97
|
+
* by no door today.
|
|
98
|
+
*/
|
|
99
|
+
export declare const ORG_AUTOMATION_TRIGGER_EVENTS: readonly ["formSubmission", "lead", "contactCreated", "contactStageChanged", "booking", "memberSignUp", "memberSignIn", "dealStageChanged", "dealWon", "dealLost", "taskCompleted"];
|
|
100
|
+
export type OrgAutomationTriggerEvent = (typeof ORG_AUTOMATION_TRIGGER_EVENTS)[number];
|
|
101
|
+
/** Whether an org automation may start on this event. */
|
|
102
|
+
export declare function isOrgAutomationTriggerEvent(event: unknown): event is OrgAutomationTriggerEvent;
|
|
103
|
+
/**
|
|
104
|
+
* The steps an org automation may hold: the server steps that read the same
|
|
105
|
+
* on every site.
|
|
106
|
+
*
|
|
107
|
+
* What is left out, and why:
|
|
108
|
+
* - every step the visitor's page runs — overlays, drawers, menus, class
|
|
109
|
+
* toggles, custom HTML and JavaScript, analytics, redirects, the site
|
|
110
|
+
* alert — because an org trigger is a server event with no page to act on;
|
|
111
|
+
* - `runWorkflow`, because a workflow is one site's and calls that site's
|
|
112
|
+
* functions and variables;
|
|
113
|
+
* - `webhookPost`, because a webhook's address and secret are one site's.
|
|
114
|
+
*
|
|
115
|
+
* `customEvent` stays: it fires a custom event ON the site the run is on,
|
|
116
|
+
* which that site's own actions may listen for — the documented way for an
|
|
117
|
+
* org automation to hand over to something site-specific.
|
|
118
|
+
*/
|
|
119
|
+
export declare const ORG_AUTOMATION_STEP_TYPES: readonly ["sendEmail", "notifyAdmins", "enrollList", "assignCampaign", "datasetAppend", "updateDataset", "setContactStage", "addContactTag", "assignContactOwner", "createCrmTask", "logCrmActivity", "customEvent", "wait", "waitForEvent", "exitFlow"];
|
|
120
|
+
export type OrgAutomationStepType = (typeof ORG_AUTOMATION_STEP_TYPES)[number];
|
|
121
|
+
/** Whether an org automation may run a step of this type. */
|
|
122
|
+
export declare function isOrgAutomationStepType(type: unknown): type is OrgAutomationStepType;
|
|
123
|
+
/** The org editor's "Do" picker: each allowed step with its label, in order. */
|
|
124
|
+
export declare const ORG_AUTOMATION_STEP_KINDS: ReadonlyArray<{
|
|
125
|
+
value: OrgAutomationStepType;
|
|
126
|
+
label: string;
|
|
127
|
+
}>;
|
|
128
|
+
/**
|
|
129
|
+
* Why an org automation cannot hold this step, or null when it can.
|
|
130
|
+
*
|
|
131
|
+
* Shared by the save route, which refuses it, and by the engine, which
|
|
132
|
+
* refuses to run one a writer stored anyway.
|
|
133
|
+
*/
|
|
134
|
+
export declare function orgAutomationStepRefusal(step: {
|
|
135
|
+
type?: unknown;
|
|
136
|
+
}): string | null;
|
|
137
|
+
/** The trigger an org automation starts on, as it is stored. */
|
|
138
|
+
export interface OrgAutomationTrigger {
|
|
139
|
+
event: OrgAutomationTriggerEvent;
|
|
140
|
+
/** Optional expression over the payload; runs only when truthy. */
|
|
141
|
+
filter?: string;
|
|
142
|
+
conditions: HostActionTriggerCondition[] | null;
|
|
143
|
+
combinator: TriggerCombinator | null;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* The fields an org automation is written with — by the save route, and by
|
|
147
|
+
* nothing else: the collection is closed to client writes.
|
|
148
|
+
*/
|
|
149
|
+
export interface OrgAutomationFields {
|
|
150
|
+
name: string;
|
|
151
|
+
trigger: OrgAutomationTrigger;
|
|
152
|
+
steps: HostActionStep[];
|
|
153
|
+
enabled: boolean;
|
|
154
|
+
/**
|
|
155
|
+
* The sites it runs on, as scope tokens: `['org']` is every site of the
|
|
156
|
+
* organization, now and later; `['host:{id}', …]` names up to thirty.
|
|
157
|
+
*/
|
|
158
|
+
visibleTo: ScopeToken[];
|
|
159
|
+
}
|
|
160
|
+
/** `orgs/{orgId}/automations/{id}`, as it is stored. */
|
|
161
|
+
export interface OrgAutomation extends OrgAutomationFields {
|
|
162
|
+
/**
|
|
163
|
+
* The sites that have paused it for themselves — each a site id, never a
|
|
164
|
+
* scope token. The "host level control": a site's admin or editor adds or
|
|
165
|
+
* removes their own site here, and only their own.
|
|
166
|
+
*/
|
|
167
|
+
pausedHostIds: string[];
|
|
168
|
+
createdBy?: string;
|
|
169
|
+
updatedBy?: string;
|
|
170
|
+
/**
|
|
171
|
+
* Set when it was deleted. Always written — `null` while live — so the
|
|
172
|
+
* org hub's list can ask for the live ones by equality.
|
|
173
|
+
*/
|
|
174
|
+
deletedAt: unknown;
|
|
175
|
+
}
|
|
176
|
+
/** A stored org automation with its id, as the console lists it. */
|
|
177
|
+
export type OrgAutomationRow = OrgAutomation & {
|
|
178
|
+
$id: string;
|
|
179
|
+
};
|
|
180
|
+
/** Longest name an org automation may carry: the actions editor's cap. */
|
|
181
|
+
export declare const ORG_AUTOMATION_NAME_MAX = 60;
|
|
182
|
+
/** What a caller sends to create or replace an org automation. */
|
|
183
|
+
export type OrgAutomationInput = Record<string, unknown>;
|
|
184
|
+
/**
|
|
185
|
+
* Reads an org automation out of an untrusted request body: the fields it may
|
|
186
|
+
* hold, in the shape they are stored, or the first reason it cannot be saved.
|
|
187
|
+
*
|
|
188
|
+
* The rules are the actions builder's own — `validateHostAction` checks the
|
|
189
|
+
* name, the trigger's conditions and every step's fields — and on top of them
|
|
190
|
+
* the two this vocabulary adds: the trigger must be an org trigger and every
|
|
191
|
+
* step an org step. The placement must be a scope a save can store:
|
|
192
|
+
* `normalizeVisibleTo` refuses an empty one and one over thirty sites rather
|
|
193
|
+
* than guessing between sharing wider and taking access away.
|
|
194
|
+
*
|
|
195
|
+
* Whether each named site belongs to the organization is the route's to ask,
|
|
196
|
+
* because it takes a read.
|
|
197
|
+
*/
|
|
198
|
+
export declare function readOrgAutomation(input: unknown): {
|
|
199
|
+
ok: true;
|
|
200
|
+
value: OrgAutomationFields;
|
|
201
|
+
} | {
|
|
202
|
+
ok: false;
|
|
203
|
+
problem: string;
|
|
204
|
+
};
|
|
205
|
+
/** A stored document's pause list, read defensively. */
|
|
206
|
+
export declare function orgAutomationPausedHostIds(automation: {
|
|
207
|
+
pausedHostIds?: unknown;
|
|
208
|
+
} | null | undefined): string[];
|
|
209
|
+
/**
|
|
210
|
+
* Why a stored org automation will not run on this site, or null when it
|
|
211
|
+
* will — the one answer the engine asks when an event arrives and again when
|
|
212
|
+
* a person waiting inside the automation is due.
|
|
213
|
+
*
|
|
214
|
+
* Deleted, switched off, taken off the site and paused there are the four
|
|
215
|
+
* ways an organization or a site says "not here", and every one of them
|
|
216
|
+
* stops the people already waiting on this site as well as the next run: the
|
|
217
|
+
* kill switch a site action's resume applies, for the same reason.
|
|
218
|
+
*/
|
|
219
|
+
export declare function orgAutomationStopReason(automation: {
|
|
220
|
+
deletedAt?: unknown;
|
|
221
|
+
enabled?: unknown;
|
|
222
|
+
visibleTo?: unknown;
|
|
223
|
+
pausedHostIds?: unknown;
|
|
224
|
+
} | null | undefined, hostId: string): string | null;
|
|
225
|
+
/** Whether a stored org automation runs on this site. */
|
|
226
|
+
export declare function orgAutomationRunsOnHost(automation: Parameters<typeof orgAutomationStopReason>[0], hostId: string): boolean;
|
|
227
|
+
/**
|
|
228
|
+
* A pause list after the placement changed: a site the automation no longer
|
|
229
|
+
* runs on has nothing left to pause, so its entry goes with it, and a site
|
|
230
|
+
* placed on it again later starts unpaused.
|
|
231
|
+
*/
|
|
232
|
+
export declare function prunePausedHostIds(pausedHostIds: readonly string[], visibleTo: readonly string[]): string[];
|