@aglyn/plugins-workflows 1.0.0-beta.218 → 1.0.0-beta.220
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 +33 -4
- package/package.json +13 -13
- package/src/index.d.ts +1 -0
- package/src/index.js.map +1 -1
- package/src/lib/components/host-actions-card.component.js +3 -2
- package/src/lib/components/host-actions-card.component.js.map +1 -1
- package/src/lib/components/host-run-history-card.component.d.ts +2 -2
- package/src/lib/components/host-run-history-card.component.js +3 -2
- package/src/lib/components/host-run-history-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 +4 -3
- package/src/lib/components/host-workflows-card.component.js.map +1 -1
- package/src/lib/components/use-automation-step-pickers.js +50 -42
- package/src/lib/components/use-automation-step-pickers.js.map +1 -1
- package/src/lib/components/use-org-automation-step-pickers.js +23 -11
- package/src/lib/components/use-org-automation-step-pickers.js.map +1 -1
- package/src/lib/components/workflow-zones.d.ts +49 -0
- package/src/lib/components/workflow-zones.js +3 -0
- package/src/lib/components/workflow-zones.js.map +1 -1
- package/src/lib/declarations.server.d.ts +6 -0
- package/src/lib/declarations.server.js +39 -0
- package/src/lib/declarations.server.js.map +1 -1
- package/src/lib/engine/crm-action-steps.d.ts +14 -1
- package/src/lib/engine/crm-action-steps.js +15 -0
- package/src/lib/engine/crm-action-steps.js.map +1 -1
- package/src/lib/engine/run-event-actions.d.ts +2 -1
- package/src/lib/engine/run-event-actions.js +66 -25
- package/src/lib/engine/run-event-actions.js.map +1 -1
- package/src/lib/engine/run-event-workflows.js +2 -1
- package/src/lib/engine/run-event-workflows.js.map +1 -1
- package/src/lib/engine/workflow-steps.d.ts +1 -1
- package/src/lib/engine/workflow-steps.js +1 -1
- package/src/lib/engine/workflow-steps.js.map +1 -1
- package/src/lib/model/automation-record.d.ts +47 -0
- package/src/lib/model/automation-record.js +79 -0
- package/src/lib/model/automation-record.js.map +1 -0
- package/src/lib/model/org-automations.js.map +1 -1
- package/src/lib/model/step-outcomes.d.ts +42 -0
- package/src/lib/model/step-outcomes.js +70 -0
- package/src/lib/model/step-outcomes.js.map +1 -0
- package/src/lib/model/webhooks.d.ts +36 -0
- package/src/lib/model/webhooks.js +24 -0
- package/src/lib/model/webhooks.js.map +1 -0
- package/src/lib/model/workflows-record-lists.d.ts +18 -0
- package/src/lib/model/workflows-record-lists.js +45 -0
- package/src/lib/model/workflows-record-lists.js.map +1 -0
- package/src/lib/model/workflows-record-routes.d.ts +22 -0
- package/src/lib/model/workflows-record-routes.js +73 -0
- package/src/lib/model/workflows-record-routes.js.map +1 -0
- package/src/lib/model/workflows-search-sources.d.ts +20 -0
- package/src/lib/model/workflows-search-sources.js +35 -0
- package/src/lib/model/workflows-search-sources.js.map +1 -0
- package/src/lib/model/workflows.d.ts +102 -0
- package/src/lib/model/workflows.js +167 -0
- package/src/lib/model/workflows.js.map +1 -0
- package/src/lib/plan-entitlements.d.ts +36 -0
- package/src/lib/plan-entitlements.js +44 -0
- package/src/lib/plan-entitlements.js.map +1 -0
- package/src/lib/plugin.js +37 -2
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/server/automation-record-index.d.ts +20 -0
- package/src/lib/server/automation-record-index.js +59 -0
- package/src/lib/server/automation-record-index.js.map +1 -0
- package/src/lib/server/workflow-dependents.d.ts +28 -0
- package/src/lib/server/workflow-dependents.js +61 -0
- package/src/lib/server/workflow-dependents.js.map +1 -0
- package/src/lib/server/workflow-variables.d.ts +20 -0
- package/src/lib/server/workflow-variables.js +75 -0
- package/src/lib/server/workflow-variables.js.map +1 -0
- package/src/lib/server.d.ts +1 -0
- package/src/lib/server.js +2 -1
- package/src/lib/server.js.map +1 -1
- package/src/lib/tenant-emails.d.ts +29 -0
- package/src/lib/tenant-emails.js +41 -0
- package/src/lib/tenant-emails.js.map +1 -0
- package/src/lib/testing/stand-in-dataset-list.d.ts +28 -0
- package/src/lib/testing/stand-in-dataset-list.js +55 -0
- package/src/lib/testing/stand-in-dataset-list.js.map +1 -0
- package/src/lib/testing/stand-in-overlay-list.d.ts +26 -0
- package/src/lib/testing/stand-in-overlay-list.js +53 -0
- package/src/lib/testing/stand-in-overlay-list.js.map +1 -0
- package/src/lib/usage-axes.d.ts +28 -0
- package/src/lib/usage-axes.js +72 -0
- package/src/lib/usage-axes.js.map +1 -0
|
@@ -14,15 +14,16 @@
|
|
|
14
14
|
* See the License for the specific language governing permissions and
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/ 'use client';
|
|
17
|
-
import { datasetPickerOptions } from "@aglyn/aglyn";
|
|
18
17
|
import { scopeCovers } from "@aglyn/aglyn/app-utils/scope-tokens";
|
|
18
|
+
import { pluginContainerKind } from "@aglyn/aglyn/plugin-manager/plugin-containers";
|
|
19
|
+
import { pluginRecordListQuery, pluginRecordsFromRows } from "@aglyn/aglyn/plugin-manager/plugin-record-lists";
|
|
19
20
|
import { useFirestore, useFirestoreCollection } from "@aglyn/tenant-feature-instance";
|
|
20
21
|
/*
|
|
21
22
|
* The MODULE, not the barrel, for the two pure helpers — the cards' specs
|
|
22
23
|
* mock `@aglyn/tenant-feature-instance` wholesale, and a query builder
|
|
23
24
|
* imported through that barrel disappears under the mock.
|
|
24
25
|
*/ import { ceilingedWindow, collectionCeiling } from "@aglyn/tenant-feature-instance/hooks/host-collection-queries";
|
|
25
|
-
import { collection
|
|
26
|
+
import { collection } from "firebase/firestore";
|
|
26
27
|
import { useMemo } from "react";
|
|
27
28
|
import { EDITOR_OPTION_CEILING } from "./use-automation-step-pickers.js";
|
|
28
29
|
/**
|
|
@@ -43,19 +44,24 @@ import { EDITOR_OPTION_CEILING } from "./use-automation-step-pickers.js";
|
|
|
43
44
|
* Read on the same latch and at the same ceiling as the site editor's, and
|
|
44
45
|
* paid only by an author who opens the editor.
|
|
45
46
|
*/ export function useOrgAutomationStepPickers(orgId, editorOpened, placement) {
|
|
47
|
+
var _pluginContainerKind;
|
|
46
48
|
const firestore = useFirestore();
|
|
47
49
|
/*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*/ const { data: datasetRead } = useFirestoreCollection(()=>editorOpened && orgId ?
|
|
50
|
+
* The organization's own datasets, listed through the source the data
|
|
51
|
+
* plugin publishes (AGL-3080): the org hub admits only org-wide members,
|
|
52
|
+
* who read every dataset, and placement narrows them below.
|
|
53
|
+
*/ const { data: datasetRead } = useFirestoreCollection(()=>editorOpened && orgId ? pluginRecordListQuery('dataset', firestore, {
|
|
54
|
+
orgId,
|
|
55
|
+
hostId: null,
|
|
56
|
+
limit: EDITOR_OPTION_CEILING + 1
|
|
57
|
+
}) : null, [
|
|
52
58
|
firestore,
|
|
53
59
|
orgId,
|
|
54
60
|
editorOpened
|
|
55
61
|
], {
|
|
56
62
|
idField: '$id'
|
|
57
63
|
});
|
|
58
|
-
const { rows:
|
|
64
|
+
const { rows: datasetRows, truncated: datasetsTruncated } = ceilingedWindow(datasetRead, EDITOR_OPTION_CEILING);
|
|
59
65
|
const { data: listRead } = useFirestoreCollection(()=>editorOpened && orgId ? collectionCeiling(collection(firestore, 'orgs', orgId, 'lists'), EDITOR_OPTION_CEILING) : null, [
|
|
60
66
|
firestore,
|
|
61
67
|
orgId,
|
|
@@ -64,10 +70,13 @@ import { EDITOR_OPTION_CEILING } from "./use-automation-step-pickers.js";
|
|
|
64
70
|
idField: '$id'
|
|
65
71
|
});
|
|
66
72
|
const { rows: listDocs, truncated: listsTruncated } = ceilingedWindow(listRead, EDITOR_OPTION_CEILING);
|
|
67
|
-
|
|
73
|
+
// The `campaign` container kind, where its declaration says it is stored.
|
|
74
|
+
const campaignCollection = (_pluginContainerKind = pluginContainerKind('campaign')) == null ? void 0 : _pluginContainerKind.orgCollection;
|
|
75
|
+
const { data: campaignRead } = useFirestoreCollection(()=>editorOpened && orgId && campaignCollection ? collectionCeiling(collection(firestore, 'orgs', orgId, campaignCollection), EDITOR_OPTION_CEILING) : null, [
|
|
68
76
|
firestore,
|
|
69
77
|
orgId,
|
|
70
|
-
editorOpened
|
|
78
|
+
editorOpened,
|
|
79
|
+
campaignCollection
|
|
71
80
|
], {
|
|
72
81
|
idField: '$id'
|
|
73
82
|
});
|
|
@@ -78,7 +87,10 @@ import { EDITOR_OPTION_CEILING } from "./use-automation-step-pickers.js";
|
|
|
78
87
|
workflowOptions: [],
|
|
79
88
|
overlayOptions: [],
|
|
80
89
|
webhookOptions: [],
|
|
81
|
-
datasetOptions:
|
|
90
|
+
datasetOptions: pluginRecordsFromRows('dataset', datasetRows).filter((dataset)=>scopeCovers(dataset.facts['visibleTo'], placement)).map((dataset)=>({
|
|
91
|
+
id: dataset.id,
|
|
92
|
+
name: dataset.name
|
|
93
|
+
})).sort((a, b)=>a.name.localeCompare(b.name)),
|
|
82
94
|
listOptions: (listDocs != null ? listDocs : []).filter((list)=>!list.deletedAt && list.name).map((list)=>({
|
|
83
95
|
id: list.$id,
|
|
84
96
|
name: list.name
|
|
@@ -89,7 +101,7 @@ import { EDITOR_OPTION_CEILING } from "./use-automation-step-pickers.js";
|
|
|
89
101
|
})).sort((a, b)=>a.name.localeCompare(b.name))
|
|
90
102
|
};
|
|
91
103
|
}, [
|
|
92
|
-
|
|
104
|
+
datasetRows,
|
|
93
105
|
listDocs,
|
|
94
106
|
campaignDocs,
|
|
95
107
|
placement
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/workflows/src/lib/components/use-org-automation-step-pickers.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'use client'\n\nimport {
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/workflows/src/lib/components/use-org-automation-step-pickers.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'use client'\n\nimport { scopeCovers } from '@aglyn/aglyn/app-utils/scope-tokens'\nimport { pluginContainerKind } from '@aglyn/aglyn/plugin-manager/plugin-containers'\nimport {\n pluginRecordListQuery,\n pluginRecordsFromRows,\n} from '@aglyn/aglyn/plugin-manager/plugin-record-lists'\nimport {\n useFirestore,\n useFirestoreCollection,\n} from '@aglyn/tenant-feature-instance'\n/*\n * The MODULE, not the barrel, for the two pure helpers — the cards' specs\n * mock `@aglyn/tenant-feature-instance` wholesale, and a query builder\n * imported through that barrel disappears under the mock.\n */\nimport {\n ceilingedWindow,\n collectionCeiling,\n} from '@aglyn/tenant-feature-instance/hooks/host-collection-queries'\nimport { collection } from 'firebase/firestore'\nimport { useMemo } from 'react'\nimport type { AutomationStepPickers } from './automation-step-fields.component'\nimport {\n type AutomationStepPickerData,\n EDITOR_OPTION_CEILING,\n} from './use-automation-step-pickers'\n\n/**\n * WHAT AN ORG AUTOMATION'S STEPS CAN BE POINTED AT (AGL-3302).\n *\n * The site editor's pickers read one site's records; an org automation runs\n * on every site it is placed on, so its pickers read the ORGANIZATION's —\n * the lists, the datasets and the campaign containers — and offer only what\n * every one of those sites can use. A dataset or a campaign shared with some\n * of the org's sites and not the others would resolve on one site and fail\n * the step on the next, so a record is offered only when its own sharing\n * covers the automation's placement (`scopeCovers`). A list belongs to the\n * whole organization and is always offered.\n *\n * The site-only pickers — workflows, overlays, webhooks — are empty: no step\n * an org automation may hold points at one.\n *\n * Read on the same latch and at the same ceiling as the site editor's, and\n * paid only by an author who opens the editor.\n */\nexport function useOrgAutomationStepPickers(\n orgId: string,\n editorOpened: boolean,\n placement: readonly string[],\n): AutomationStepPickerData {\n const firestore = useFirestore()\n /*\n * The organization's own datasets, listed through the source the data\n * plugin publishes (AGL-3080): the org hub admits only org-wide members,\n * who read every dataset, and placement narrows them below.\n */\n const { data: datasetRead } = useFirestoreCollection<any>(\n () =>\n editorOpened && orgId\n ? pluginRecordListQuery('dataset', firestore, {\n orgId,\n hostId: null,\n limit: EDITOR_OPTION_CEILING + 1,\n })\n : null,\n [firestore, orgId, editorOpened],\n { idField: '$id' },\n )\n const { rows: datasetRows, truncated: datasetsTruncated } =\n ceilingedWindow<any>(datasetRead, EDITOR_OPTION_CEILING)\n const { data: listRead } = useFirestoreCollection<any>(\n () =>\n editorOpened && orgId\n ? collectionCeiling(\n collection(firestore, 'orgs', orgId, 'lists'),\n EDITOR_OPTION_CEILING,\n )\n : null,\n [firestore, orgId, editorOpened],\n { idField: '$id' },\n )\n const { rows: listDocs, truncated: listsTruncated } = ceilingedWindow<any>(\n listRead,\n EDITOR_OPTION_CEILING,\n )\n // The `campaign` container kind, where its declaration says it is stored.\n const campaignCollection = pluginContainerKind('campaign')?.orgCollection\n const { data: campaignRead } = useFirestoreCollection<any>(\n () =>\n editorOpened && orgId && campaignCollection\n ? collectionCeiling(\n collection(firestore, 'orgs', orgId, campaignCollection),\n EDITOR_OPTION_CEILING,\n )\n : null,\n [firestore, orgId, editorOpened, campaignCollection],\n { idField: '$id' },\n )\n const { rows: campaignDocs, truncated: campaignsTruncated } =\n ceilingedWindow<any>(campaignRead, EDITOR_OPTION_CEILING)\n\n const pickers = useMemo<AutomationStepPickers>(() => {\n const covers = (row: any) =>\n scopeCovers(row?.visibleTo as string[] | undefined, placement)\n return {\n workflowOptions: [],\n overlayOptions: [],\n webhookOptions: [],\n datasetOptions: pluginRecordsFromRows('dataset', datasetRows)\n .filter((dataset) => scopeCovers(dataset.facts['visibleTo'] as string[] | undefined, placement))\n .map((dataset) => ({ id: dataset.id, name: dataset.name }))\n .sort((a, b) => a.name.localeCompare(b.name)),\n listOptions: (listDocs ?? [])\n .filter((list: any) => !list.deletedAt && list.name)\n .map((list: any) => ({ id: list.$id as string, name: list.name as string }))\n .sort((a, b) => a.name.localeCompare(b.name)),\n campaignOptions: (campaignDocs ?? [])\n .filter((campaign: any) => !campaign.deletedAt && campaign.name)\n .filter(covers)\n .map((campaign: any) => ({\n id: campaign.$id as string,\n name: campaign.name as string,\n }))\n .sort((a, b) => a.name.localeCompare(b.name)),\n }\n }, [datasetRows, listDocs, campaignDocs, placement])\n\n const truncated = [\n datasetsTruncated ? 'datasets' : null,\n listsTruncated ? 'audiences' : null,\n campaignsTruncated ? 'campaigns' : null,\n ].filter((name): name is string => name !== null)\n return { pickers, truncated }\n}\n"],"names":["scopeCovers","pluginContainerKind","pluginRecordListQuery","pluginRecordsFromRows","useFirestore","useFirestoreCollection","ceilingedWindow","collectionCeiling","collection","useMemo","EDITOR_OPTION_CEILING","useOrgAutomationStepPickers","orgId","editorOpened","placement","firestore","data","datasetRead","hostId","limit","idField","rows","datasetRows","truncated","datasetsTruncated","listRead","listDocs","listsTruncated","campaignCollection","orgCollection","campaignRead","campaignDocs","campaignsTruncated","pickers","covers","row","visibleTo","workflowOptions","overlayOptions","webhookOptions","datasetOptions","filter","dataset","facts","map","id","name","sort","a","b","localeCompare","listOptions","list","deletedAt","$id","campaignOptions","campaign"],"mappings":"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"}
|
|
@@ -34,3 +34,52 @@ export interface WorkflowUsageZoneProps {
|
|
|
34
34
|
onClose: () => void;
|
|
35
35
|
}
|
|
36
36
|
export declare const WORKFLOW_USAGE_ZONE: import("@aglyn/aglyn/plugin-manager/plugin-zones").PluginZone<WorkflowUsageZoneProps>;
|
|
37
|
+
/**
|
|
38
|
+
* The Automation page's Actions, beside Add action and Recipes (AGL-2919):
|
|
39
|
+
* another way to start an automation. `openAction` opens a listed action in
|
|
40
|
+
* the Actions editor, and answers `false` when the id names no action the
|
|
41
|
+
* list has read yet — which is how a widget whose draft was just written
|
|
42
|
+
* tells a list that has not caught up from one that opened it. A widget here
|
|
43
|
+
* writes nothing through the page.
|
|
44
|
+
*/
|
|
45
|
+
export interface HostAutomationsZoneProps {
|
|
46
|
+
hostId: string;
|
|
47
|
+
/** The org the page names; `undefined` while it resolves. */
|
|
48
|
+
orgId: string | undefined;
|
|
49
|
+
openAction: (actionId: string) => boolean;
|
|
50
|
+
}
|
|
51
|
+
/** One automation on the Automation page: an action, or a workflow (AGL-2919). */
|
|
52
|
+
export interface AutomationTarget {
|
|
53
|
+
type: 'action' | 'workflow';
|
|
54
|
+
id: string;
|
|
55
|
+
name: string;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The editor of one SAVED automation — an action or a workflow — on the
|
|
59
|
+
* Automation page (AGL-2919). A widget here reads the automation as it is
|
|
60
|
+
* stored and changes nothing in the editor.
|
|
61
|
+
*/
|
|
62
|
+
export interface AutomationEditorZoneProps {
|
|
63
|
+
hostId: string;
|
|
64
|
+
/** The org the page names; `undefined` while it resolves. */
|
|
65
|
+
orgId: string | undefined;
|
|
66
|
+
/** The automation the editor has open, as it is stored. */
|
|
67
|
+
target: AutomationTarget;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* One FAILED run in an automation's run history (AGL-2919), drawn once per
|
|
71
|
+
* failed row. A widget here reads the run as it was recorded and changes
|
|
72
|
+
* nothing.
|
|
73
|
+
*/
|
|
74
|
+
export interface AutomationRunZoneProps {
|
|
75
|
+
hostId: string;
|
|
76
|
+
/** The org the page names; `undefined` while it resolves. */
|
|
77
|
+
orgId: string | undefined;
|
|
78
|
+
/** The automation the run belongs to. */
|
|
79
|
+
target: AutomationTarget;
|
|
80
|
+
/** The run's entry in the site's activity log. */
|
|
81
|
+
runId: string;
|
|
82
|
+
}
|
|
83
|
+
export declare const HOST_AUTOMATIONS_ZONE: import("@aglyn/aglyn/plugin-manager/plugin-zones").PluginZone<HostAutomationsZoneProps>;
|
|
84
|
+
export declare const AUTOMATION_EDITOR_ZONE: import("@aglyn/aglyn/plugin-manager/plugin-zones").PluginZone<AutomationEditorZoneProps>;
|
|
85
|
+
export declare const AUTOMATION_RUN_ZONE: import("@aglyn/aglyn/plugin-manager/plugin-zones").PluginZone<AutomationRunZoneProps>;
|
|
@@ -15,5 +15,8 @@
|
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/ import { definePluginZone } from "@aglyn/aglyn/plugin-manager/plugin-zones";
|
|
17
17
|
export const WORKFLOW_USAGE_ZONE = definePluginZone('workflowUsage');
|
|
18
|
+
export const HOST_AUTOMATIONS_ZONE = definePluginZone('hostAutomations');
|
|
19
|
+
export const AUTOMATION_EDITOR_ZONE = definePluginZone('automationEditor');
|
|
20
|
+
export const AUTOMATION_RUN_ZONE = definePluginZone('automationRun');
|
|
18
21
|
|
|
19
22
|
//# sourceMappingURL=workflow-zones.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/workflows/src/lib/components/workflow-zones.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 { WhereUsedResult } from '@aglyn/aglyn/app-utils/where-used'\nimport { definePluginZone } from '@aglyn/aglyn/plugin-manager/plugin-zones'\n\n/**\n * Where a workflow is used, shown by whichever plugin draws references.\n *\n * The Automation page asks the platform's where-used scan what a workflow\n * computes and holds the answer. Laying that answer out — the rows, the deep\n * links into a published version, the flag on a legacy name token — is the\n * same dialog a variable and a function open, and it belongs to the plugin\n * that owns those. This page hosts the zone and hands it the answer.\n */\nexport interface WorkflowUsageZoneProps {\n hostId: string\n /** The scan this page already ran; `null` closes whatever is drawn. */\n usage: { name: string; result: WhereUsedResult } | null\n onClose: () => void\n}\n\nexport const WORKFLOW_USAGE_ZONE =\n definePluginZone<WorkflowUsageZoneProps>('workflowUsage')\n"],"names":["definePluginZone","WORKFLOW_USAGE_ZONE"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAGD,SAASA,gBAAgB,QAAQ,2CAA0C;AAkB3E,OAAO,MAAMC,sBACXD,iBAAyC,iBAAgB"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/workflows/src/lib/components/workflow-zones.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 { WhereUsedResult } from '@aglyn/aglyn/app-utils/where-used'\nimport { definePluginZone } from '@aglyn/aglyn/plugin-manager/plugin-zones'\n\n/**\n * Where a workflow is used, shown by whichever plugin draws references.\n *\n * The Automation page asks the platform's where-used scan what a workflow\n * computes and holds the answer. Laying that answer out — the rows, the deep\n * links into a published version, the flag on a legacy name token — is the\n * same dialog a variable and a function open, and it belongs to the plugin\n * that owns those. This page hosts the zone and hands it the answer.\n */\nexport interface WorkflowUsageZoneProps {\n hostId: string\n /** The scan this page already ran; `null` closes whatever is drawn. */\n usage: { name: string; result: WhereUsedResult } | null\n onClose: () => void\n}\n\nexport const WORKFLOW_USAGE_ZONE =\n definePluginZone<WorkflowUsageZoneProps>('workflowUsage')\n\n/**\n * The Automation page's Actions, beside Add action and Recipes (AGL-2919):\n * another way to start an automation. `openAction` opens a listed action in\n * the Actions editor, and answers `false` when the id names no action the\n * list has read yet — which is how a widget whose draft was just written\n * tells a list that has not caught up from one that opened it. A widget here\n * writes nothing through the page.\n */\nexport interface HostAutomationsZoneProps {\n hostId: string\n /** The org the page names; `undefined` while it resolves. */\n orgId: string | undefined\n openAction: (actionId: string) => boolean\n}\n\n/** One automation on the Automation page: an action, or a workflow (AGL-2919). */\nexport interface AutomationTarget {\n type: 'action' | 'workflow'\n id: string\n name: string\n}\n\n/**\n * The editor of one SAVED automation — an action or a workflow — on the\n * Automation page (AGL-2919). A widget here reads the automation as it is\n * stored and changes nothing in the editor.\n */\nexport interface AutomationEditorZoneProps {\n hostId: string\n /** The org the page names; `undefined` while it resolves. */\n orgId: string | undefined\n /** The automation the editor has open, as it is stored. */\n target: AutomationTarget\n}\n\n/**\n * One FAILED run in an automation's run history (AGL-2919), drawn once per\n * failed row. A widget here reads the run as it was recorded and changes\n * nothing.\n */\nexport interface AutomationRunZoneProps {\n hostId: string\n /** The org the page names; `undefined` while it resolves. */\n orgId: string | undefined\n /** The automation the run belongs to. */\n target: AutomationTarget\n /** The run's entry in the site's activity log. */\n runId: string\n}\n\nexport const HOST_AUTOMATIONS_ZONE =\n definePluginZone<HostAutomationsZoneProps>('hostAutomations')\n\nexport const AUTOMATION_EDITOR_ZONE =\n definePluginZone<AutomationEditorZoneProps>('automationEditor')\n\nexport const AUTOMATION_RUN_ZONE =\n definePluginZone<AutomationRunZoneProps>('automationRun')\n"],"names":["definePluginZone","WORKFLOW_USAGE_ZONE","HOST_AUTOMATIONS_ZONE","AUTOMATION_EDITOR_ZONE","AUTOMATION_RUN_ZONE"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAGD,SAASA,gBAAgB,QAAQ,2CAA0C;AAkB3E,OAAO,MAAMC,sBACXD,iBAAyC,iBAAgB;AAoD3D,OAAO,MAAME,wBACXF,iBAA2C,mBAAkB;AAE/D,OAAO,MAAMG,yBACXH,iBAA4C,oBAAmB;AAEjE,OAAO,MAAMI,sBACXJ,iBAAyC,iBAAgB"}
|
|
@@ -34,6 +34,12 @@ export declare const workflowsHostEventListener: HostEventListener;
|
|
|
34
34
|
* first of them arrives — which is what running this from both apps'
|
|
35
35
|
* `instrumentation.ts` gives.
|
|
36
36
|
*
|
|
37
|
+
* The indexes of the site's workflows, webhooks and actions, so another
|
|
38
|
+
* surface — an AI job — reads them without reaching for this plugin's
|
|
39
|
+
* collections; the workflows that call a function, which the "Used by"
|
|
40
|
+
* scan asks this plugin for; and the site variables a workflow computes,
|
|
41
|
+
* which every published page's compose asks for.
|
|
42
|
+
*
|
|
37
43
|
* Also called from the plugin's own API register functions, so a process
|
|
38
44
|
* whose boot did not run it still registers the listener the first time a
|
|
39
45
|
* plugin door loads. Registering twice replaces in place.
|
|
@@ -16,6 +16,9 @@
|
|
|
16
16
|
*/ // The registry from its own module, not the runtime barrel: boot needs the
|
|
17
17
|
// registry and nothing else, and the barrel reaches the data layer.
|
|
18
18
|
import { registerHostEventListener } from "@aglyn/tenant-runtime/host-event-listeners";
|
|
19
|
+
import { registerVariableComputer } from "@aglyn/aglyn/plugin-manager/computed-variables";
|
|
20
|
+
import { registerPluginDependentsSource } from "@aglyn/aglyn/plugin-manager/plugin-dependents";
|
|
21
|
+
import { registerPluginRecordIndex } from "@aglyn/aglyn/plugin-manager/plugin-record-index";
|
|
19
22
|
import { BUNDLE_ID } from "./constants/bundle-common.js";
|
|
20
23
|
/**
|
|
21
24
|
* The automation engine, as the runtime hears it.
|
|
@@ -55,11 +58,47 @@ import { BUNDLE_ID } from "./constants/bundle-common.js";
|
|
|
55
58
|
* first of them arrives — which is what running this from both apps'
|
|
56
59
|
* `instrumentation.ts` gives.
|
|
57
60
|
*
|
|
61
|
+
* The indexes of the site's workflows, webhooks and actions, so another
|
|
62
|
+
* surface — an AI job — reads them without reaching for this plugin's
|
|
63
|
+
* collections; the workflows that call a function, which the "Used by"
|
|
64
|
+
* scan asks this plugin for; and the site variables a workflow computes,
|
|
65
|
+
* which every published page's compose asks for.
|
|
66
|
+
*
|
|
58
67
|
* Also called from the plugin's own API register functions, so a process
|
|
59
68
|
* whose boot did not run it still registers the listener the first time a
|
|
60
69
|
* plugin door loads. Registering twice replaces in place.
|
|
61
70
|
*/ export function registerWorkflowsServerDeclarations() {
|
|
62
71
|
registerHostEventListener(BUNDLE_ID, workflowsHostEventListener);
|
|
72
|
+
// The readers and the Admin SDK arrive with the first read, not with the boot.
|
|
73
|
+
registerPluginRecordIndex('workflow', lazyIndex('workflowRecordIndex'), {
|
|
74
|
+
pluginId: BUNDLE_ID
|
|
75
|
+
});
|
|
76
|
+
registerPluginRecordIndex('webhook', lazyIndex('webhookRecordIndex'), {
|
|
77
|
+
pluginId: BUNDLE_ID
|
|
78
|
+
});
|
|
79
|
+
registerPluginRecordIndex('action', lazyIndex('actionRecordIndex'), {
|
|
80
|
+
pluginId: BUNDLE_ID
|
|
81
|
+
});
|
|
82
|
+
registerPluginDependentsSource({
|
|
83
|
+
kinds: [
|
|
84
|
+
'function'
|
|
85
|
+
],
|
|
86
|
+
find: async (request)=>(await import("./server/workflow-dependents.js")).findFunctionDependents(request)
|
|
87
|
+
}, {
|
|
88
|
+
pluginId: BUNDLE_ID
|
|
89
|
+
});
|
|
90
|
+
registerVariableComputer({
|
|
91
|
+
prepare: async (hostId)=>(await import("./server/workflow-variables.js")).prepareWorkflowVariables(hostId)
|
|
92
|
+
}, {
|
|
93
|
+
pluginId: BUNDLE_ID
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
function lazyIndex(name) {
|
|
97
|
+
const load = async ()=>(await import("./server/automation-record-index.js"))[name];
|
|
98
|
+
return {
|
|
99
|
+
list: async (request)=>(await load()).list(request),
|
|
100
|
+
get: async (request)=>(await load()).get(request)
|
|
101
|
+
};
|
|
63
102
|
}
|
|
64
103
|
|
|
65
104
|
//# sourceMappingURL=declarations.server.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/plugins/workflows/src/lib/declarations.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// The registry from its own module, not the runtime barrel: boot needs the\n// registry and nothing else, and the barrel reaches the data layer.\nimport {\n type HostEventListener,\n registerHostEventListener,\n} from '@aglyn/tenant-runtime/host-event-listeners'\nimport { BUNDLE_ID } from './constants/bundle-common'\n\n/**\n * The automation engine, as the runtime hears it.\n *\n * Light by construction: the engine is imported when the first event\n * arrives, not when the process starts, so a process that never sees an\n * event pays for this object and nothing else.\n */\nexport const workflowsHostEventListener: HostEventListener = {\n async onEvent(hostId, event, payload, context) {\n const [{ runEventAutomations }, { withRunTriggerActor }] = await Promise.all([\n import('./engine/run-event-automations'),\n import('./engine/run-trigger-actor'),\n ])\n // Every run this event causes records who caused it (AGL-3376).\n return await withRunTriggerActor(context?.actor, () =>\n runEventAutomations(hostId, event, payload),\n )\n },\n async onDispatch(hostId, automationId, event, payload, context) {\n const [{ runSingleAction }, { withRunTriggerActor }] = await Promise.all([\n import('./engine/run-event-actions'),\n import('./engine/run-trigger-actor'),\n ])\n // A page-fired automation is always a visitor on the published page.\n return await withRunTriggerActor(context?.actor ?? { kind: 'visitor' }, () =>\n runSingleAction(hostId, automationId, event, payload),\n )\n },\n}\n\n/**\n * The plugin's SERVER declarations: what the server must know at boot,\n * before any door of the plugin has been called.\n *\n * The engine's host-event listener, because the doors that raise the events\n * mostly never load this plugin. A form submission and a page view are core\n * routes; a contact captured by a Stripe webhook is a core capture door. Each\n * calls `emitHostEvent`, and the listener has to be registered before the\n * first of them arrives — which is what running this from both apps'\n * `instrumentation.ts` gives.\n *\n * Also called from the plugin's own API register functions, so a process\n * whose boot did not run it still registers the listener the first time a\n * plugin door loads. Registering twice replaces in place.\n */\nexport function registerWorkflowsServerDeclarations(): void {\n registerHostEventListener(BUNDLE_ID, workflowsHostEventListener)\n}\n"],"names":["registerHostEventListener","BUNDLE_ID","workflowsHostEventListener","onEvent","hostId","event","payload","context","runEventAutomations","withRunTriggerActor","Promise","all","actor","onDispatch","automationId","runSingleAction","kind","registerWorkflowsServerDeclarations"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,2EAA2E;AAC3E,oEAAoE;AACpE,SAEEA,yBAAyB,QACpB,6CAA4C;AACnD,SAASC,SAAS,QAAQ,+BAA2B;AAErD;;;;;;CAMC,GACD,OAAO,MAAMC,6BAAgD;IAC3D,MAAMC,SAAQC,MAAM,EAAEC,KAAK,EAAEC,OAAO,EAAEC,OAAO;QAC3C,MAAM,CAAC,EAAEC,mBAAmB,EAAE,EAAE,EAAEC,mBAAmB,EAAE,CAAC,GAAG,MAAMC,QAAQC,GAAG,CAAC;YAC3E,MAAM,CAAC;YACP,MAAM,CAAC;SACR;QACD,gEAAgE;QAChE,OAAO,MAAMF,oBAAoBF,2BAAAA,QAASK,KAAK,EAAE,IAC/CJ,oBAAoBJ,QAAQC,OAAOC;IAEvC;IACA,MAAMO,YAAWT,MAAM,EAAEU,YAAY,EAAET,KAAK,EAAEC,OAAO,EAAEC,OAAO;;QAC5D,MAAM,CAAC,EAAEQ,eAAe,EAAE,EAAE,EAAEN,mBAAmB,EAAE,CAAC,GAAG,MAAMC,QAAQC,GAAG,CAAC;YACvE,MAAM,CAAC;YACP,MAAM,CAAC;SACR;QACD,qEAAqE;QACrE,OAAO,MAAMF,4BAAoBF,2BAAAA,QAASK,KAAK,mBAAI;YAAEI,MAAM;QAAU,GAAG,IACtED,gBAAgBX,QAAQU,cAAcT,OAAOC;IAEjD;AACF,EAAC;AAED
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/plugins/workflows/src/lib/declarations.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// The registry from its own module, not the runtime barrel: boot needs the\n// registry and nothing else, and the barrel reaches the data layer.\nimport {\n type HostEventListener,\n registerHostEventListener,\n} from '@aglyn/tenant-runtime/host-event-listeners'\nimport { registerVariableComputer } from '@aglyn/aglyn/plugin-manager/computed-variables'\nimport { registerPluginDependentsSource } from '@aglyn/aglyn/plugin-manager/plugin-dependents'\nimport {\n registerPluginRecordIndex,\n type PluginRecordIndex,\n} from '@aglyn/aglyn/plugin-manager/plugin-record-index'\nimport { BUNDLE_ID } from './constants/bundle-common'\n\n/**\n * The automation engine, as the runtime hears it.\n *\n * Light by construction: the engine is imported when the first event\n * arrives, not when the process starts, so a process that never sees an\n * event pays for this object and nothing else.\n */\nexport const workflowsHostEventListener: HostEventListener = {\n async onEvent(hostId, event, payload, context) {\n const [{ runEventAutomations }, { withRunTriggerActor }] = await Promise.all([\n import('./engine/run-event-automations'),\n import('./engine/run-trigger-actor'),\n ])\n // Every run this event causes records who caused it (AGL-3376).\n return await withRunTriggerActor(context?.actor, () =>\n runEventAutomations(hostId, event, payload),\n )\n },\n async onDispatch(hostId, automationId, event, payload, context) {\n const [{ runSingleAction }, { withRunTriggerActor }] = await Promise.all([\n import('./engine/run-event-actions'),\n import('./engine/run-trigger-actor'),\n ])\n // A page-fired automation is always a visitor on the published page.\n return await withRunTriggerActor(context?.actor ?? { kind: 'visitor' }, () =>\n runSingleAction(hostId, automationId, event, payload),\n )\n },\n}\n\n/**\n * The plugin's SERVER declarations: what the server must know at boot,\n * before any door of the plugin has been called.\n *\n * The engine's host-event listener, because the doors that raise the events\n * mostly never load this plugin. A form submission and a page view are core\n * routes; a contact captured by a Stripe webhook is a core capture door. Each\n * calls `emitHostEvent`, and the listener has to be registered before the\n * first of them arrives — which is what running this from both apps'\n * `instrumentation.ts` gives.\n *\n * The indexes of the site's workflows, webhooks and actions, so another\n * surface — an AI job — reads them without reaching for this plugin's\n * collections; the workflows that call a function, which the \"Used by\"\n * scan asks this plugin for; and the site variables a workflow computes,\n * which every published page's compose asks for.\n *\n * Also called from the plugin's own API register functions, so a process\n * whose boot did not run it still registers the listener the first time a\n * plugin door loads. Registering twice replaces in place.\n */\nexport function registerWorkflowsServerDeclarations(): void {\n registerHostEventListener(BUNDLE_ID, workflowsHostEventListener)\n // The readers and the Admin SDK arrive with the first read, not with the boot.\n registerPluginRecordIndex('workflow', lazyIndex('workflowRecordIndex'), { pluginId: BUNDLE_ID })\n registerPluginRecordIndex('webhook', lazyIndex('webhookRecordIndex'), { pluginId: BUNDLE_ID })\n registerPluginRecordIndex('action', lazyIndex('actionRecordIndex'), { pluginId: BUNDLE_ID })\n registerPluginDependentsSource(\n {\n kinds: ['function'],\n find: async (request) =>\n (await import('./server/workflow-dependents')).findFunctionDependents(request),\n },\n { pluginId: BUNDLE_ID },\n )\n registerVariableComputer(\n {\n prepare: async (hostId) =>\n (await import('./server/workflow-variables')).prepareWorkflowVariables(hostId),\n },\n { pluginId: BUNDLE_ID },\n )\n}\n\ntype IndexName = 'workflowRecordIndex' | 'webhookRecordIndex' | 'actionRecordIndex'\n\nfunction lazyIndex(name: IndexName): PluginRecordIndex {\n const load = async () => (await import('./server/automation-record-index'))[name]\n return {\n list: async (request) => (await load()).list(request),\n get: async (request) => (await load()).get(request),\n }\n}\n"],"names":["registerHostEventListener","registerVariableComputer","registerPluginDependentsSource","registerPluginRecordIndex","BUNDLE_ID","workflowsHostEventListener","onEvent","hostId","event","payload","context","runEventAutomations","withRunTriggerActor","Promise","all","actor","onDispatch","automationId","runSingleAction","kind","registerWorkflowsServerDeclarations","lazyIndex","pluginId","kinds","find","request","findFunctionDependents","prepare","prepareWorkflowVariables","name","load","list","get"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,2EAA2E;AAC3E,oEAAoE;AACpE,SAEEA,yBAAyB,QACpB,6CAA4C;AACnD,SAASC,wBAAwB,QAAQ,iDAAgD;AACzF,SAASC,8BAA8B,QAAQ,gDAA+C;AAC9F,SACEC,yBAAyB,QAEpB,kDAAiD;AACxD,SAASC,SAAS,QAAQ,+BAA2B;AAErD;;;;;;CAMC,GACD,OAAO,MAAMC,6BAAgD;IAC3D,MAAMC,SAAQC,MAAM,EAAEC,KAAK,EAAEC,OAAO,EAAEC,OAAO;QAC3C,MAAM,CAAC,EAAEC,mBAAmB,EAAE,EAAE,EAAEC,mBAAmB,EAAE,CAAC,GAAG,MAAMC,QAAQC,GAAG,CAAC;YAC3E,MAAM,CAAC;YACP,MAAM,CAAC;SACR;QACD,gEAAgE;QAChE,OAAO,MAAMF,oBAAoBF,2BAAAA,QAASK,KAAK,EAAE,IAC/CJ,oBAAoBJ,QAAQC,OAAOC;IAEvC;IACA,MAAMO,YAAWT,MAAM,EAAEU,YAAY,EAAET,KAAK,EAAEC,OAAO,EAAEC,OAAO;;QAC5D,MAAM,CAAC,EAAEQ,eAAe,EAAE,EAAE,EAAEN,mBAAmB,EAAE,CAAC,GAAG,MAAMC,QAAQC,GAAG,CAAC;YACvE,MAAM,CAAC;YACP,MAAM,CAAC;SACR;QACD,qEAAqE;QACrE,OAAO,MAAMF,4BAAoBF,2BAAAA,QAASK,KAAK,mBAAI;YAAEI,MAAM;QAAU,GAAG,IACtED,gBAAgBX,QAAQU,cAAcT,OAAOC;IAEjD;AACF,EAAC;AAED;;;;;;;;;;;;;;;;;;;;CAoBC,GACD,OAAO,SAASW;IACdpB,0BAA0BI,WAAWC;IACrC,+EAA+E;IAC/EF,0BAA0B,YAAYkB,UAAU,wBAAwB;QAAEC,UAAUlB;IAAU;IAC9FD,0BAA0B,WAAWkB,UAAU,uBAAuB;QAAEC,UAAUlB;IAAU;IAC5FD,0BAA0B,UAAUkB,UAAU,sBAAsB;QAAEC,UAAUlB;IAAU;IAC1FF,+BACE;QACEqB,OAAO;YAAC;SAAW;QACnBC,MAAM,OAAOC,UACX,AAAC,CAAA,MAAM,MAAM,CAAC,kCAA8B,EAAGC,sBAAsB,CAACD;IAC1E,GACA;QAAEH,UAAUlB;IAAU;IAExBH,yBACE;QACE0B,SAAS,OAAOpB,SACd,AAAC,CAAA,MAAM,MAAM,CAAC,iCAA6B,EAAGqB,wBAAwB,CAACrB;IAC3E,GACA;QAAEe,UAAUlB;IAAU;AAE1B;AAIA,SAASiB,UAAUQ,IAAe;IAChC,MAAMC,OAAO,UAAY,AAAC,CAAA,MAAM,MAAM,CAAC,sCAAkC,CAAE,CAACD,KAAK;IACjF,OAAO;QACLE,MAAM,OAAON,UAAY,AAAC,CAAA,MAAMK,MAAK,EAAGC,IAAI,CAACN;QAC7CO,KAAK,OAAOP,UAAY,AAAC,CAAA,MAAMK,MAAK,EAAGE,GAAG,CAACP;IAC7C;AACF"}
|
|
@@ -14,8 +14,21 @@
|
|
|
14
14
|
* See the License for the specific language governing permissions and
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/
|
|
17
|
-
import { type
|
|
17
|
+
import { type CrmActivityLink } from '@aglyn/aglyn/server';
|
|
18
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;
|
|
19
32
|
/**
|
|
20
33
|
* The CRM steps of an action run (AGL-2605): what `setContactStage`,
|
|
21
34
|
* `addContactTag`, `assignContactOwner`, `createCrmTask` and
|
|
@@ -23,6 +23,21 @@ import { FieldValue } from "firebase-admin/firestore";
|
|
|
23
23
|
// The leaves, not the runtime barrel, for the reason `run-event-actions.ts`
|
|
24
24
|
// gives: the specs substitute each one.
|
|
25
25
|
import { OWNER_ASSIGNMENT_REFUSALS, reassignContactOwner } from "@aglyn/tenant-runtime/assign-contact-owner";
|
|
26
|
+
/**
|
|
27
|
+
* The steps that act on the CRM (AGL-2605) — named as a set because the
|
|
28
|
+
* executor dispatches all five to one module and the docs list them as one
|
|
29
|
+
* group, and a step added to the union but not here would be a server step
|
|
30
|
+
* the executor silently skipped.
|
|
31
|
+
*/ export const CRM_ACTION_STEP_TYPES = new Set([
|
|
32
|
+
'setContactStage',
|
|
33
|
+
'addContactTag',
|
|
34
|
+
'assignContactOwner',
|
|
35
|
+
'createCrmTask',
|
|
36
|
+
'logCrmActivity'
|
|
37
|
+
]);
|
|
38
|
+
export function isCrmActionStep(step) {
|
|
39
|
+
return CRM_ACTION_STEP_TYPES.has(step.type);
|
|
40
|
+
}
|
|
26
41
|
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
27
42
|
/**
|
|
28
43
|
* The contact an event payload names, as this site may see it.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/workflows/src/lib/engine/crm-action-steps.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 buildCrmEmailActivity,\n CONTACT_LIFECYCLE_STAGE_LABELS,\n CONTACT_TAG_MAX_LENGTH,\n contactFacetPath,\n CRM_ACTIVITY_LOG_FULL_MESSAGE,\n CRM_COLLECTIONS,\n crmActivityLogHasRoom,\n type CrmActionStep,\n type CrmActivity,\n type CrmActivityLink,\n type CrmTask,\n crmEmailDeliveryTags,\n crmScopeTokens,\n crmTaskListFields,\n crmTaskReminderAfterEdit,\n normalizeContactEmail,\n parseCrmMemberRef,\n readContactFacet,\n visibleToHost,\n} from '@aglyn/aglyn/server'\nimport {\n consentGroupForSite,\n countCrmActivitiesForRecord,\n firebaseAdmin,\n newCrmActivityRef,\n orgDataQueryForHost,\n recomputeCrmNextTaskAt,\n restampCrmListFieldsAt,\n writeCrmEmailActivity,\n} from '@aglyn/tenant-data-admin'\n// The leaf, not the barrel: this library's specs substitute the barrel\n// wholesale, and the lookup must reach the real index logic under them.\nimport { findContactByEmail } from '@aglyn/tenant-data-admin/server/contact-email-index'\nimport { FieldValue } from 'firebase-admin/firestore'\n// The leaves, not the runtime barrel, for the reason `run-event-actions.ts`\n// gives: the specs substitute each one.\nimport {\n OWNER_ASSIGNMENT_REFUSALS,\n reassignContactOwner,\n} from '@aglyn/tenant-runtime/assign-contact-owner'\nimport type { HostEventPayload } from '@aglyn/tenant-runtime/host-event-listeners'\n\n/**\n * The CRM steps of an action run (AGL-2605): what `setContactStage`,\n * `addContactTag`, `assignContactOwner`, `createCrmTask` and\n * `logCrmActivity` actually write.\n *\n * Split out of `run-event-actions.ts` because the five share one shape the\n * other server steps do not: each begins by finding THE PERSON the event is\n * about, and each writes either inside the site's facet on that person or a\n * record stamped with the site's scope. One resolver and one scope\n * expression, used five times, rather than five copies drifting apart.\n *\n * ## The person is the event's, never the step's\n *\n * A step carries no contact reference. The contact is whoever the event\n * names — by `contactId` when the emitting door knew the document, and by\n * `email` otherwise, which is what every pre-CRM event carries. Both\n * lookups are scoped to what THIS site may see: a document the id names\n * but the site cannot read is treated as absent, exactly as the scoped\n * query would have treated it, so an event replayed against the wrong site\n * reaches nobody.\n *\n * ## A missing person is a reported no-op\n *\n * Nothing is written and the step's error names the reason, which lands in\n * the run summary beside the trigger — the same place \"why didn't it\n * fire?\" is answered for every other step. Silence here would make a\n * mis-wired trigger (a form with no email field) indistinguishable from a\n * working one.\n */\n\nexport interface CrmStepEnv {\n hostId: string\n /** The owning org's billing doc, already read by the run's gate. */\n org: unknown\n orgId: string | null\n}\n\nexport interface CrmStepOutcome {\n /** The reason nothing was written, when nothing was. */\n error?: string\n /** The one fact worth carrying into the run summary. */\n detail?: string\n /**\n * An event this step's write earned, for the CALLER to fan out under its\n * own depth guard. Not emitted from here: a stage set by an automation is\n * itself a stage change, and the action listening for it must run — but\n * through the same nesting cap a `customEvent` chain has, or an action\n * that sets the stage it listens for would run until the meter ran dry.\n */\n emit?: { event: 'contactStageChanged'; payload: HostEventPayload }\n}\n\nconst DAY_MS = 24 * 60 * 60 * 1000\n\ninterface ResolvedContact {\n id: string\n ref: FirebaseFirestore.DocumentReference\n data: Record<string, unknown>\n}\n\n/**\n * The contact an event payload names, as this site may see it.\n *\n * Id first, because a CRM event carries the document the door just wrote\n * and a lookup by id is a read, not a query. The address second, through\n * the org's address index narrowed to this site (AGL-2633), for every\n * event that predates the CRM and knows only who filled in the form — so\n * an address a merge folded into another record still reaches the person\n * who now holds it.\n */\nasync function resolveEventContact(\n hostId: string,\n payload: HostEventPayload,\n): Promise<ResolvedContact | null> {\n const { ref: contactsRef } = await orgDataQueryForHost(hostId, 'contacts')\n const contactId = String(payload['contactId'] ?? '').trim()\n if (contactId) {\n const doc = await contactsRef.doc(contactId).get()\n if (doc.exists && visibleToHost(doc.get('visibleTo'), hostId)) {\n return { id: doc.id, ref: doc.ref, data: doc.data() ?? {} }\n }\n }\n const hit = await findContactByEmail(contactsRef, payload['email'], { hostId })\n return hit ? { id: hit.id, ref: hit.ref, data: hit.data() ?? {} } : null\n}\n\n/**\n * The uid a step names for somebody on the team — an owner, an assignee —\n * resolved against the org's roster, whichever way the step named them.\n *\n * A step names a member by uid when a picker wrote it and by address when a\n * person typed it, and the editor's one text field accepts either — so both\n * fields are read through `parseCrmMemberRef`, and an address typed into\n * the uid slot or a uid typed into the address slot still names the person.\n *\n * Both are RESOLVED, not trusted. `orgs/{orgId}/members/{uid}` is keyed by\n * the uid, so a uid costs one document read and must exist: `ownerUid` and\n * `assigneeUid` are fields every reader resolves as a member, and a\n * stranger's uid in one is a task nobody on the team can find. An address\n * is matched on the roster's own `email` field, and two production paths\n * create a member document WITHOUT one (a host-access re-grant, and an add\n * whose auth record carried none) — such a member is named by uid, which\n * is the reason the editor takes one.\n *\n * The roster is the only directory consulted. A project-level Auth lookup\n * by address would resolve people who are not on this organization at all\n * (AGL-1122). A reference that resolves neither way is an error, not a\n * stored string.\n */\nasync function resolveMemberUid(\n orgId: string | null,\n named: { uid?: string; email?: string },\n role: 'owner' | 'assignee',\n): Promise<{ uid: string; detail: string } | { error: string }> {\n const ref =\n parseCrmMemberRef(named.uid) ?? parseCrmMemberRef(named.email)\n if (!ref) return { error: `no ${role} named on the step` }\n if (!orgId) return { error: 'this site has no organization' }\n const members = firebaseAdmin\n .app()\n .firestore()\n .collection('orgs')\n .doc(orgId)\n .collection('members')\n if (ref.kind === 'uid') {\n const member = await members.doc(ref.uid).get()\n if (member.exists) return { uid: ref.uid, detail: ref.uid }\n return { error: `no team member with the id ${ref.uid}` }\n }\n const byField = (await members.where('email', '==', ref.email).limit(1).get())\n .docs[0]\n if (byField) return { uid: byField.id, detail: ref.email }\n return { error: `no team member with the address ${ref.email}` }\n}\n\n/**\n * Runs one CRM step for the person the event names. Never throws for a\n * missing person, an unknown owner or a site with no org — those are\n * answered as `error` so the run records them; a Firestore failure\n * propagates to the executor's catch like every other step's would.\n */\nexport async function runCrmActionStep(\n env: CrmStepEnv,\n actionId: string,\n step: CrmActionStep,\n payload: HostEventPayload,\n nowMs = Date.now(),\n): Promise<CrmStepOutcome> {\n const { hostId } = env\n const contact = await resolveEventContact(hostId, payload)\n if (!contact) {\n const named = String(payload['contactId'] ?? payload['email'] ?? '').trim()\n return {\n error: named\n ? `no contact this site can see for ${named}`\n : 'the event names no contact — no contactId or email in its payload',\n }\n }\n /*\n * THE HOLDER whose facet the write addresses — the site's consent group,\n * which is the site alone unless the org declared the site one of a set\n * that presents as one sender. Same resolution the capture doors use, so\n * a stage set here lands in the facet the console reads.\n */\n const group = await consentGroupForSite(hostId)\n const facet = readContactFacet(contact.data, group.groupId)\n const email = String(contact.data['email'] ?? '')\n\n if (step.type === 'setContactStage') {\n const previous = facet.lifecycleStage ?? ''\n const label = CONTACT_LIFECYCLE_STAGE_LABELS[step.lifecycleStage]\n /*\n * A stage set to what it already is changes nothing and ANNOUNCES\n * nothing. The write is skipped so the row's `updatedAt` does not move\n * for a non-event, and the absent emit is what stops an action that\n * sets the stage it listens for from re-running on its own write —\n * the depth guard bounds that chain, this ends it at once.\n */\n if (previous === step.lifecycleStage) {\n return { detail: `${label} (already)` }\n }\n await contact.ref.update({\n [contactFacetPath(group.groupId, 'lifecycleStage')]: step.lifecycleStage,\n updatedAt: FieldValue.serverTimestamp(),\n })\n // The stage is what the Contacts list filters by (AGL-3321).\n await restampCrmListFieldsAt(contact.ref, 'contacts')\n return {\n detail: label,\n emit: {\n event: 'contactStageChanged',\n payload: {\n contactId: contact.id,\n email,\n lifecycleStage: step.lifecycleStage,\n previousStage: previous,\n },\n },\n }\n }\n\n if (step.type === 'addContactTag') {\n const tag = String(step.tag ?? '')\n .trim()\n .slice(0, CONTACT_TAG_MAX_LENGTH)\n if (!tag) return { error: 'the step has no tag' }\n // `arrayUnion`, so the tag a merchant already applied by hand is not\n // duplicated and the tags beside it are kept.\n await contact.ref.update({\n [contactFacetPath(group.groupId, 'tags')]: FieldValue.arrayUnion(tag),\n updatedAt: FieldValue.serverTimestamp(),\n })\n // The tags are what the Contacts list filters by (AGL-3321).\n await restampCrmListFieldsAt(contact.ref, 'contacts')\n return { detail: tag }\n }\n\n if (step.type === 'assignContactOwner') {\n /*\n * Through the one server assignment (AGL-2618), in both of the step's\n * modes, rather than a facet write of its own: the helper is what\n * moves the round-robin pointer inside the transaction that writes the\n * owner, mirrors the owner onto the site's lead, and tells the new\n * owner. A member named on the step is still resolved here — by\n * address against the roster, as before — and handed over by uid; the\n * helper checks the roster document again, which is one read and the\n * same answer.\n */\n let assign: { memberUid: string } | { roundRobin: true }\n let named = 'round robin'\n if (step.roundRobin === true) {\n assign = { roundRobin: true }\n } else {\n const owner = await resolveMemberUid(\n env.orgId,\n { uid: step.ownerUid, email: step.ownerEmail },\n 'owner',\n )\n if ('error' in owner) return { error: owner.error }\n assign = { memberUid: owner.uid }\n named = owner.detail\n }\n const verdict = await reassignContactOwner({\n hostId,\n contactId: contact.id,\n email,\n assign,\n })\n if (verdict.outcome === 'none') {\n return { error: OWNER_ASSIGNMENT_REFUSALS[verdict.reason] }\n }\n if (verdict.outcome === 'unchanged') {\n return { detail: `${named} (already)` }\n }\n return {\n detail:\n step.roundRobin === true ? `round robin → ${verdict.ownerUid}` : named,\n }\n }\n\n // The two record creators: a task and an activity are documents of\n // their own beside the contact, stamped with the scope a contact\n // captured on this site would carry — `crmScopeTokens` is the create\n // path's own expression, so a record an automation made is visible to\n // exactly the sites a record a person made would be.\n if (!env.orgId) return { error: 'this site has no organization' }\n const orgRef = firebaseAdmin\n .app()\n .firestore()\n .collection('orgs')\n .doc(env.orgId)\n const visibleTo = crmScopeTokens(\n (env.org ?? null) as Record<string, unknown> | null,\n group,\n )\n const links = {\n contactId: contact.id,\n ...(facet.companyId ? { companyId: facet.companyId } : {}),\n }\n\n if (step.type === 'createCrmTask') {\n const title = String(step.title ?? '')\n .trim()\n .slice(0, 200)\n if (!title) return { error: 'the task has no title' }\n const dueInDays = Math.max(0, Math.round(Number(step.dueInDays) || 0))\n /*\n * The assignee the step names, else the person who OWNS the contact,\n * else nobody. A follow-up task belongs to whoever holds the\n * relationship, and an automation that had to name one person for\n * every contact it touches would assign the whole list to one rep. A\n * named assignee who cannot be resolved is an error rather than a\n * fallback to the owner: the author named somebody on purpose.\n */\n let assignee = facet.ownerUid ?? ''\n if (step.assigneeUid?.trim() || step.assigneeEmail?.trim()) {\n const named = await resolveMemberUid(\n env.orgId,\n { uid: step.assigneeUid, email: step.assigneeEmail },\n 'assignee',\n )\n if ('error' in named) return { error: named.error }\n assignee = named.uid\n }\n const dueAtMs = nowMs + dueInDays * DAY_MS\n const task: CrmTask = {\n title,\n kind: step.kind,\n priority: 'normal',\n status: 'open',\n dueAtMs,\n // The reminder a person's task gets (AGL-2659): the due time.\n remindAtMs: crmTaskReminderAfterEdit({ dueAtMs, previous: null }),\n ...(assignee ? { assigneeUid: assignee } : {}),\n createdByUid: '',\n sourceActionId: actionId,\n ...links,\n hostId,\n visibleTo,\n createdAt: FieldValue.serverTimestamp(),\n updatedAt: FieldValue.serverTimestamp(),\n }\n // What the Tasks list searches by (AGL-3321), beside the task.\n await orgRef.collection(CRM_COLLECTIONS.tasks).add({ ...task, ...crmTaskListFields(task) })\n // The contact and company the task names carry `nextTaskAtMs` (AGL-2661);\n // a figure that could not move is the Fields section's recompute's.\n await recomputeCrmNextTaskAt(firebaseAdmin.app().firestore(), env.orgId, [links]).catch((error: unknown) => {\n console.error('[workflow] next activity could not be recomputed', env.orgId, error)\n })\n return { detail: title.slice(0, 60) }\n }\n\n if (step.type === 'logCrmActivity') {\n const body = String(step.body ?? '')\n .trim()\n .slice(0, 2000)\n if (!body) return { error: 'the activity has no body' }\n /*\n * The per-record ceiling (AGL-2611), and this step is the writer it\n * exists for: a flow that logs on every page view fills one person's\n * log in an afternoon. Refused into the run history as an error, the\n * way an unresolvable assignee is, so the merchant reads why the flow\n * stopped writing rather than finding a log that silently stopped.\n */\n const logged = await countCrmActivitiesForRecord(orgRef, links)\n if (!crmActivityLogHasRoom(logged)) {\n return { error: CRM_ACTIVITY_LOG_FULL_MESSAGE }\n }\n const activity: CrmActivity = {\n kind: step.kind,\n body,\n atMs: nowMs,\n byUid: '',\n sourceActionId: actionId,\n ...links,\n hostId,\n visibleTo,\n createdAt: FieldValue.serverTimestamp(),\n updatedAt: FieldValue.serverTimestamp(),\n }\n await orgRef.collection(CRM_COLLECTIONS.activities).add(activity)\n return { detail: step.kind }\n }\n\n return { error: `unknown CRM step \"${(step as { type: string }).type}\"` }\n}\n\n/**\n * The activity row a `sendEmail` step's message will be logged as, prepared\n * BEFORE the send (AGL-2615): the minted reference whose id rides the\n * message as a tag, the links it files under, and the scope it is stamped\n * with. `null` when the message earns no row.\n */\nexport interface PreparedCrmEmailActivity {\n ref: FirebaseFirestore.DocumentReference\n /** The provider tags the delivery webhook finds the row by. */\n tags: { name: string; value: string }[]\n link: CrmActivityLink\n visibleTo: string[]\n}\n\n/**\n * Whether — and where — an automation's email lands on a timeline.\n *\n * A `sendEmail` step is not a CRM step: it mails whatever address the event\n * carries, to a person who may be nobody the CRM knows. It earns a row on\n * exactly one condition, that the message is ADDRESSED TO THE CONTACT the\n * event is about — the person `resolveEventContact` finds, at the address\n * the row holds. A welcome sequence to a new contact is that; an internal\n * alert routed to a merchant's own address through `toField` is not, and a\n * row for it would put the merchant's inbox on a customer's history.\n *\n * Prepared ahead of the send, for the reason the console route mints its\n * id first: the webhook has nothing but the tags on the message to find\n * the row with. Nothing is written here. A record at the activity ceiling\n * earns no row — the message still goes, because the ceiling bounds the\n * log and not the mail — and neither does a site with no org to hold one.\n *\n * **Never throws.** The row is bookkeeping beside a send, and a lookup that\n * fails must not become a message that never left — the posture every\n * meter beside `sendEmail` takes. A failure here is logged and the message\n * goes out untagged.\n */\nexport async function prepareCrmEmailActivity(\n env: CrmStepEnv,\n to: string,\n payload: HostEventPayload,\n): Promise<PreparedCrmEmailActivity | null> {\n if (!env.orgId) return null\n const address = normalizeContactEmail(to)\n if (!address) return null\n try {\n const contact = await resolveEventContact(env.hostId, payload)\n if (!contact || normalizeContactEmail(contact.data['email']) !== address) {\n return null\n }\n const group = await consentGroupForSite(env.hostId)\n const facet = readContactFacet(contact.data, group.groupId)\n const link: CrmActivityLink = {\n contactId: contact.id,\n ...(facet.companyId ? { companyId: facet.companyId } : {}),\n }\n const firestore = firebaseAdmin.app().firestore()\n const orgRef = firestore.collection('orgs').doc(env.orgId)\n if (!crmActivityLogHasRoom(await countCrmActivitiesForRecord(orgRef, link))) {\n return null\n }\n const ref = newCrmActivityRef(firestore, env.orgId)\n return {\n ref,\n tags: crmEmailDeliveryTags({\n orgId: env.orgId,\n hostId: env.hostId,\n activityId: ref.id,\n }),\n link,\n visibleTo: crmScopeTokens(\n (env.org ?? null) as Record<string, unknown> | null,\n group,\n ),\n }\n } catch (error) {\n console.error('[crm] automation email activity could not be prepared', env.hostId, error)\n return null\n }\n}\n\n/**\n * Writes the prepared row, once the provider has accepted the message.\n *\n * The same shape the console route logs — `buildCrmEmailActivity` is the\n * one builder — with the automation as its source and nobody as its\n * author. **Never throws**: the message has left, and a row that could not\n * be written is a gap on the timeline rather than a failed step.\n */\nexport async function logCrmEmailActivity(\n env: CrmStepEnv,\n prepared: PreparedCrmEmailActivity,\n message: { subject: string; body: string; to: string },\n actionId: string,\n nowMs = Date.now(),\n): Promise<void> {\n try {\n await writeCrmEmailActivity(\n prepared.ref,\n buildCrmEmailActivity({\n subject: message.subject,\n body: message.body,\n to: normalizeContactEmail(message.to) ?? message.to,\n atMs: nowMs,\n byUid: '',\n sourceActionId: actionId,\n link: prepared.link,\n hostId: env.hostId,\n visibleTo: prepared.visibleTo,\n }),\n )\n } catch (error) {\n console.error('[crm] automation email activity write failed', prepared.ref.id, error)\n }\n}\n"],"names":["buildCrmEmailActivity","CONTACT_LIFECYCLE_STAGE_LABELS","CONTACT_TAG_MAX_LENGTH","contactFacetPath","CRM_ACTIVITY_LOG_FULL_MESSAGE","CRM_COLLECTIONS","crmActivityLogHasRoom","crmEmailDeliveryTags","crmScopeTokens","crmTaskListFields","crmTaskReminderAfterEdit","normalizeContactEmail","parseCrmMemberRef","readContactFacet","visibleToHost","consentGroupForSite","countCrmActivitiesForRecord","firebaseAdmin","newCrmActivityRef","orgDataQueryForHost","recomputeCrmNextTaskAt","restampCrmListFieldsAt","writeCrmEmailActivity","findContactByEmail","FieldValue","OWNER_ASSIGNMENT_REFUSALS","reassignContactOwner","DAY_MS","resolveEventContact","hostId","payload","hit","ref","contactsRef","contactId","String","trim","doc","get","exists","id","data","resolveMemberUid","orgId","named","role","uid","email","error","members","app","firestore","collection","kind","member","detail","byField","where","limit","docs","runCrmActionStep","env","actionId","step","nowMs","Date","now","contact","group","facet","groupId","type","previous","lifecycleStage","label","update","updatedAt","serverTimestamp","emit","event","previousStage","tag","slice","arrayUnion","assign","roundRobin","owner","ownerUid","ownerEmail","memberUid","verdict","outcome","reason","orgRef","visibleTo","org","links","companyId","title","dueInDays","Math","max","round","Number","assignee","assigneeUid","assigneeEmail","dueAtMs","task","priority","status","remindAtMs","createdByUid","sourceActionId","createdAt","tasks","add","catch","console","body","logged","activity","atMs","byUid","activities","prepareCrmEmailActivity","to","address","link","tags","activityId","logCrmEmailActivity","prepared","message","subject"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/workflows/src/lib/engine/crm-action-steps.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 buildCrmEmailActivity,\n CONTACT_LIFECYCLE_STAGE_LABELS,\n CONTACT_TAG_MAX_LENGTH,\n contactFacetPath,\n CRM_ACTIVITY_LOG_FULL_MESSAGE,\n CRM_COLLECTIONS,\n crmActivityLogHasRoom,\n type CrmActivity,\n type CrmActivityLink,\n type CrmTask,\n crmEmailDeliveryTags,\n crmScopeTokens,\n crmTaskListFields,\n crmTaskReminderAfterEdit,\n normalizeContactEmail,\n parseCrmMemberRef,\n readContactFacet,\n visibleToHost,\n} from '@aglyn/aglyn/server'\nimport {\n consentGroupForSite,\n countCrmActivitiesForRecord,\n firebaseAdmin,\n newCrmActivityRef,\n orgDataQueryForHost,\n recomputeCrmNextTaskAt,\n restampCrmListFieldsAt,\n writeCrmEmailActivity,\n} from '@aglyn/tenant-data-admin'\n// The leaf, not the barrel: this library's specs substitute the barrel\n// wholesale, and the lookup must reach the real index logic under them.\nimport { findContactByEmail } from '@aglyn/tenant-data-admin/server/contact-email-index'\nimport { FieldValue } from 'firebase-admin/firestore'\n// The leaves, not the runtime barrel, for the reason `run-event-actions.ts`\n// gives: the specs substitute each one.\nimport {\n OWNER_ASSIGNMENT_REFUSALS,\n reassignContactOwner,\n} from '@aglyn/tenant-runtime/assign-contact-owner'\nimport type { HostEventPayload } from '@aglyn/tenant-runtime/host-event-listeners'\nimport type { HostActionStep, HostActionStepType } from '@aglyn/aglyn/app-utils/actions'\n\n/**\n * The steps that act on the CRM (AGL-2605) — named as a set because the\n * executor dispatches all five to one module and the docs list them as one\n * group, and a step added to the union but not here would be a server step\n * the executor silently skipped.\n */\nexport const CRM_ACTION_STEP_TYPES: ReadonlySet<HostActionStepType> = new Set([\n 'setContactStage',\n 'addContactTag',\n 'assignContactOwner',\n 'createCrmTask',\n 'logCrmActivity',\n] as const)\n\n/** The CRM steps, as the type the executor narrows to. */\nexport type CrmActionStep = Extract<\n HostActionStep,\n {\n type:\n | 'setContactStage'\n | 'addContactTag'\n | 'assignContactOwner'\n | 'createCrmTask'\n | 'logCrmActivity'\n }\n>\n\nexport function isCrmActionStep(step: HostActionStep): step is CrmActionStep {\n return CRM_ACTION_STEP_TYPES.has(step.type)\n}\n\n/**\n * The CRM steps of an action run (AGL-2605): what `setContactStage`,\n * `addContactTag`, `assignContactOwner`, `createCrmTask` and\n * `logCrmActivity` actually write.\n *\n * Split out of `run-event-actions.ts` because the five share one shape the\n * other server steps do not: each begins by finding THE PERSON the event is\n * about, and each writes either inside the site's facet on that person or a\n * record stamped with the site's scope. One resolver and one scope\n * expression, used five times, rather than five copies drifting apart.\n *\n * ## The person is the event's, never the step's\n *\n * A step carries no contact reference. The contact is whoever the event\n * names — by `contactId` when the emitting door knew the document, and by\n * `email` otherwise, which is what every pre-CRM event carries. Both\n * lookups are scoped to what THIS site may see: a document the id names\n * but the site cannot read is treated as absent, exactly as the scoped\n * query would have treated it, so an event replayed against the wrong site\n * reaches nobody.\n *\n * ## A missing person is a reported no-op\n *\n * Nothing is written and the step's error names the reason, which lands in\n * the run summary beside the trigger — the same place \"why didn't it\n * fire?\" is answered for every other step. Silence here would make a\n * mis-wired trigger (a form with no email field) indistinguishable from a\n * working one.\n */\n\nexport interface CrmStepEnv {\n hostId: string\n /** The owning org's billing doc, already read by the run's gate. */\n org: unknown\n orgId: string | null\n}\n\nexport interface CrmStepOutcome {\n /** The reason nothing was written, when nothing was. */\n error?: string\n /** The one fact worth carrying into the run summary. */\n detail?: string\n /**\n * An event this step's write earned, for the CALLER to fan out under its\n * own depth guard. Not emitted from here: a stage set by an automation is\n * itself a stage change, and the action listening for it must run — but\n * through the same nesting cap a `customEvent` chain has, or an action\n * that sets the stage it listens for would run until the meter ran dry.\n */\n emit?: { event: 'contactStageChanged'; payload: HostEventPayload }\n}\n\nconst DAY_MS = 24 * 60 * 60 * 1000\n\ninterface ResolvedContact {\n id: string\n ref: FirebaseFirestore.DocumentReference\n data: Record<string, unknown>\n}\n\n/**\n * The contact an event payload names, as this site may see it.\n *\n * Id first, because a CRM event carries the document the door just wrote\n * and a lookup by id is a read, not a query. The address second, through\n * the org's address index narrowed to this site (AGL-2633), for every\n * event that predates the CRM and knows only who filled in the form — so\n * an address a merge folded into another record still reaches the person\n * who now holds it.\n */\nasync function resolveEventContact(\n hostId: string,\n payload: HostEventPayload,\n): Promise<ResolvedContact | null> {\n const { ref: contactsRef } = await orgDataQueryForHost(hostId, 'contacts')\n const contactId = String(payload['contactId'] ?? '').trim()\n if (contactId) {\n const doc = await contactsRef.doc(contactId).get()\n if (doc.exists && visibleToHost(doc.get('visibleTo'), hostId)) {\n return { id: doc.id, ref: doc.ref, data: doc.data() ?? {} }\n }\n }\n const hit = await findContactByEmail(contactsRef, payload['email'], { hostId })\n return hit ? { id: hit.id, ref: hit.ref, data: hit.data() ?? {} } : null\n}\n\n/**\n * The uid a step names for somebody on the team — an owner, an assignee —\n * resolved against the org's roster, whichever way the step named them.\n *\n * A step names a member by uid when a picker wrote it and by address when a\n * person typed it, and the editor's one text field accepts either — so both\n * fields are read through `parseCrmMemberRef`, and an address typed into\n * the uid slot or a uid typed into the address slot still names the person.\n *\n * Both are RESOLVED, not trusted. `orgs/{orgId}/members/{uid}` is keyed by\n * the uid, so a uid costs one document read and must exist: `ownerUid` and\n * `assigneeUid` are fields every reader resolves as a member, and a\n * stranger's uid in one is a task nobody on the team can find. An address\n * is matched on the roster's own `email` field, and two production paths\n * create a member document WITHOUT one (a host-access re-grant, and an add\n * whose auth record carried none) — such a member is named by uid, which\n * is the reason the editor takes one.\n *\n * The roster is the only directory consulted. A project-level Auth lookup\n * by address would resolve people who are not on this organization at all\n * (AGL-1122). A reference that resolves neither way is an error, not a\n * stored string.\n */\nasync function resolveMemberUid(\n orgId: string | null,\n named: { uid?: string; email?: string },\n role: 'owner' | 'assignee',\n): Promise<{ uid: string; detail: string } | { error: string }> {\n const ref =\n parseCrmMemberRef(named.uid) ?? parseCrmMemberRef(named.email)\n if (!ref) return { error: `no ${role} named on the step` }\n if (!orgId) return { error: 'this site has no organization' }\n const members = firebaseAdmin\n .app()\n .firestore()\n .collection('orgs')\n .doc(orgId)\n .collection('members')\n if (ref.kind === 'uid') {\n const member = await members.doc(ref.uid).get()\n if (member.exists) return { uid: ref.uid, detail: ref.uid }\n return { error: `no team member with the id ${ref.uid}` }\n }\n const byField = (await members.where('email', '==', ref.email).limit(1).get())\n .docs[0]\n if (byField) return { uid: byField.id, detail: ref.email }\n return { error: `no team member with the address ${ref.email}` }\n}\n\n/**\n * Runs one CRM step for the person the event names. Never throws for a\n * missing person, an unknown owner or a site with no org — those are\n * answered as `error` so the run records them; a Firestore failure\n * propagates to the executor's catch like every other step's would.\n */\nexport async function runCrmActionStep(\n env: CrmStepEnv,\n actionId: string,\n step: CrmActionStep,\n payload: HostEventPayload,\n nowMs = Date.now(),\n): Promise<CrmStepOutcome> {\n const { hostId } = env\n const contact = await resolveEventContact(hostId, payload)\n if (!contact) {\n const named = String(payload['contactId'] ?? payload['email'] ?? '').trim()\n return {\n error: named\n ? `no contact this site can see for ${named}`\n : 'the event names no contact — no contactId or email in its payload',\n }\n }\n /*\n * THE HOLDER whose facet the write addresses — the site's consent group,\n * which is the site alone unless the org declared the site one of a set\n * that presents as one sender. Same resolution the capture doors use, so\n * a stage set here lands in the facet the console reads.\n */\n const group = await consentGroupForSite(hostId)\n const facet = readContactFacet(contact.data, group.groupId)\n const email = String(contact.data['email'] ?? '')\n\n if (step.type === 'setContactStage') {\n const previous = facet.lifecycleStage ?? ''\n const label = CONTACT_LIFECYCLE_STAGE_LABELS[step.lifecycleStage]\n /*\n * A stage set to what it already is changes nothing and ANNOUNCES\n * nothing. The write is skipped so the row's `updatedAt` does not move\n * for a non-event, and the absent emit is what stops an action that\n * sets the stage it listens for from re-running on its own write —\n * the depth guard bounds that chain, this ends it at once.\n */\n if (previous === step.lifecycleStage) {\n return { detail: `${label} (already)` }\n }\n await contact.ref.update({\n [contactFacetPath(group.groupId, 'lifecycleStage')]: step.lifecycleStage,\n updatedAt: FieldValue.serverTimestamp(),\n })\n // The stage is what the Contacts list filters by (AGL-3321).\n await restampCrmListFieldsAt(contact.ref, 'contacts')\n return {\n detail: label,\n emit: {\n event: 'contactStageChanged',\n payload: {\n contactId: contact.id,\n email,\n lifecycleStage: step.lifecycleStage,\n previousStage: previous,\n },\n },\n }\n }\n\n if (step.type === 'addContactTag') {\n const tag = String(step.tag ?? '')\n .trim()\n .slice(0, CONTACT_TAG_MAX_LENGTH)\n if (!tag) return { error: 'the step has no tag' }\n // `arrayUnion`, so the tag a merchant already applied by hand is not\n // duplicated and the tags beside it are kept.\n await contact.ref.update({\n [contactFacetPath(group.groupId, 'tags')]: FieldValue.arrayUnion(tag),\n updatedAt: FieldValue.serverTimestamp(),\n })\n // The tags are what the Contacts list filters by (AGL-3321).\n await restampCrmListFieldsAt(contact.ref, 'contacts')\n return { detail: tag }\n }\n\n if (step.type === 'assignContactOwner') {\n /*\n * Through the one server assignment (AGL-2618), in both of the step's\n * modes, rather than a facet write of its own: the helper is what\n * moves the round-robin pointer inside the transaction that writes the\n * owner, mirrors the owner onto the site's lead, and tells the new\n * owner. A member named on the step is still resolved here — by\n * address against the roster, as before — and handed over by uid; the\n * helper checks the roster document again, which is one read and the\n * same answer.\n */\n let assign: { memberUid: string } | { roundRobin: true }\n let named = 'round robin'\n if (step.roundRobin === true) {\n assign = { roundRobin: true }\n } else {\n const owner = await resolveMemberUid(\n env.orgId,\n { uid: step.ownerUid, email: step.ownerEmail },\n 'owner',\n )\n if ('error' in owner) return { error: owner.error }\n assign = { memberUid: owner.uid }\n named = owner.detail\n }\n const verdict = await reassignContactOwner({\n hostId,\n contactId: contact.id,\n email,\n assign,\n })\n if (verdict.outcome === 'none') {\n return { error: OWNER_ASSIGNMENT_REFUSALS[verdict.reason] }\n }\n if (verdict.outcome === 'unchanged') {\n return { detail: `${named} (already)` }\n }\n return {\n detail:\n step.roundRobin === true ? `round robin → ${verdict.ownerUid}` : named,\n }\n }\n\n // The two record creators: a task and an activity are documents of\n // their own beside the contact, stamped with the scope a contact\n // captured on this site would carry — `crmScopeTokens` is the create\n // path's own expression, so a record an automation made is visible to\n // exactly the sites a record a person made would be.\n if (!env.orgId) return { error: 'this site has no organization' }\n const orgRef = firebaseAdmin\n .app()\n .firestore()\n .collection('orgs')\n .doc(env.orgId)\n const visibleTo = crmScopeTokens(\n (env.org ?? null) as Record<string, unknown> | null,\n group,\n )\n const links = {\n contactId: contact.id,\n ...(facet.companyId ? { companyId: facet.companyId } : {}),\n }\n\n if (step.type === 'createCrmTask') {\n const title = String(step.title ?? '')\n .trim()\n .slice(0, 200)\n if (!title) return { error: 'the task has no title' }\n const dueInDays = Math.max(0, Math.round(Number(step.dueInDays) || 0))\n /*\n * The assignee the step names, else the person who OWNS the contact,\n * else nobody. A follow-up task belongs to whoever holds the\n * relationship, and an automation that had to name one person for\n * every contact it touches would assign the whole list to one rep. A\n * named assignee who cannot be resolved is an error rather than a\n * fallback to the owner: the author named somebody on purpose.\n */\n let assignee = facet.ownerUid ?? ''\n if (step.assigneeUid?.trim() || step.assigneeEmail?.trim()) {\n const named = await resolveMemberUid(\n env.orgId,\n { uid: step.assigneeUid, email: step.assigneeEmail },\n 'assignee',\n )\n if ('error' in named) return { error: named.error }\n assignee = named.uid\n }\n const dueAtMs = nowMs + dueInDays * DAY_MS\n const task: CrmTask = {\n title,\n kind: step.kind,\n priority: 'normal',\n status: 'open',\n dueAtMs,\n // The reminder a person's task gets (AGL-2659): the due time.\n remindAtMs: crmTaskReminderAfterEdit({ dueAtMs, previous: null }),\n ...(assignee ? { assigneeUid: assignee } : {}),\n createdByUid: '',\n sourceActionId: actionId,\n ...links,\n hostId,\n visibleTo,\n createdAt: FieldValue.serverTimestamp(),\n updatedAt: FieldValue.serverTimestamp(),\n }\n // What the Tasks list searches by (AGL-3321), beside the task.\n await orgRef.collection(CRM_COLLECTIONS.tasks).add({ ...task, ...crmTaskListFields(task) })\n // The contact and company the task names carry `nextTaskAtMs` (AGL-2661);\n // a figure that could not move is the Fields section's recompute's.\n await recomputeCrmNextTaskAt(firebaseAdmin.app().firestore(), env.orgId, [links]).catch((error: unknown) => {\n console.error('[workflow] next activity could not be recomputed', env.orgId, error)\n })\n return { detail: title.slice(0, 60) }\n }\n\n if (step.type === 'logCrmActivity') {\n const body = String(step.body ?? '')\n .trim()\n .slice(0, 2000)\n if (!body) return { error: 'the activity has no body' }\n /*\n * The per-record ceiling (AGL-2611), and this step is the writer it\n * exists for: a flow that logs on every page view fills one person's\n * log in an afternoon. Refused into the run history as an error, the\n * way an unresolvable assignee is, so the merchant reads why the flow\n * stopped writing rather than finding a log that silently stopped.\n */\n const logged = await countCrmActivitiesForRecord(orgRef, links)\n if (!crmActivityLogHasRoom(logged)) {\n return { error: CRM_ACTIVITY_LOG_FULL_MESSAGE }\n }\n const activity: CrmActivity = {\n kind: step.kind,\n body,\n atMs: nowMs,\n byUid: '',\n sourceActionId: actionId,\n ...links,\n hostId,\n visibleTo,\n createdAt: FieldValue.serverTimestamp(),\n updatedAt: FieldValue.serverTimestamp(),\n }\n await orgRef.collection(CRM_COLLECTIONS.activities).add(activity)\n return { detail: step.kind }\n }\n\n return { error: `unknown CRM step \"${(step as { type: string }).type}\"` }\n}\n\n/**\n * The activity row a `sendEmail` step's message will be logged as, prepared\n * BEFORE the send (AGL-2615): the minted reference whose id rides the\n * message as a tag, the links it files under, and the scope it is stamped\n * with. `null` when the message earns no row.\n */\nexport interface PreparedCrmEmailActivity {\n ref: FirebaseFirestore.DocumentReference\n /** The provider tags the delivery webhook finds the row by. */\n tags: { name: string; value: string }[]\n link: CrmActivityLink\n visibleTo: string[]\n}\n\n/**\n * Whether — and where — an automation's email lands on a timeline.\n *\n * A `sendEmail` step is not a CRM step: it mails whatever address the event\n * carries, to a person who may be nobody the CRM knows. It earns a row on\n * exactly one condition, that the message is ADDRESSED TO THE CONTACT the\n * event is about — the person `resolveEventContact` finds, at the address\n * the row holds. A welcome sequence to a new contact is that; an internal\n * alert routed to a merchant's own address through `toField` is not, and a\n * row for it would put the merchant's inbox on a customer's history.\n *\n * Prepared ahead of the send, for the reason the console route mints its\n * id first: the webhook has nothing but the tags on the message to find\n * the row with. Nothing is written here. A record at the activity ceiling\n * earns no row — the message still goes, because the ceiling bounds the\n * log and not the mail — and neither does a site with no org to hold one.\n *\n * **Never throws.** The row is bookkeeping beside a send, and a lookup that\n * fails must not become a message that never left — the posture every\n * meter beside `sendEmail` takes. A failure here is logged and the message\n * goes out untagged.\n */\nexport async function prepareCrmEmailActivity(\n env: CrmStepEnv,\n to: string,\n payload: HostEventPayload,\n): Promise<PreparedCrmEmailActivity | null> {\n if (!env.orgId) return null\n const address = normalizeContactEmail(to)\n if (!address) return null\n try {\n const contact = await resolveEventContact(env.hostId, payload)\n if (!contact || normalizeContactEmail(contact.data['email']) !== address) {\n return null\n }\n const group = await consentGroupForSite(env.hostId)\n const facet = readContactFacet(contact.data, group.groupId)\n const link: CrmActivityLink = {\n contactId: contact.id,\n ...(facet.companyId ? { companyId: facet.companyId } : {}),\n }\n const firestore = firebaseAdmin.app().firestore()\n const orgRef = firestore.collection('orgs').doc(env.orgId)\n if (!crmActivityLogHasRoom(await countCrmActivitiesForRecord(orgRef, link))) {\n return null\n }\n const ref = newCrmActivityRef(firestore, env.orgId)\n return {\n ref,\n tags: crmEmailDeliveryTags({\n orgId: env.orgId,\n hostId: env.hostId,\n activityId: ref.id,\n }),\n link,\n visibleTo: crmScopeTokens(\n (env.org ?? null) as Record<string, unknown> | null,\n group,\n ),\n }\n } catch (error) {\n console.error('[crm] automation email activity could not be prepared', env.hostId, error)\n return null\n }\n}\n\n/**\n * Writes the prepared row, once the provider has accepted the message.\n *\n * The same shape the console route logs — `buildCrmEmailActivity` is the\n * one builder — with the automation as its source and nobody as its\n * author. **Never throws**: the message has left, and a row that could not\n * be written is a gap on the timeline rather than a failed step.\n */\nexport async function logCrmEmailActivity(\n env: CrmStepEnv,\n prepared: PreparedCrmEmailActivity,\n message: { subject: string; body: string; to: string },\n actionId: string,\n nowMs = Date.now(),\n): Promise<void> {\n try {\n await writeCrmEmailActivity(\n prepared.ref,\n buildCrmEmailActivity({\n subject: message.subject,\n body: message.body,\n to: normalizeContactEmail(message.to) ?? message.to,\n atMs: nowMs,\n byUid: '',\n sourceActionId: actionId,\n link: prepared.link,\n hostId: env.hostId,\n visibleTo: prepared.visibleTo,\n }),\n )\n } catch (error) {\n console.error('[crm] automation email activity write failed', prepared.ref.id, error)\n }\n}\n"],"names":["buildCrmEmailActivity","CONTACT_LIFECYCLE_STAGE_LABELS","CONTACT_TAG_MAX_LENGTH","contactFacetPath","CRM_ACTIVITY_LOG_FULL_MESSAGE","CRM_COLLECTIONS","crmActivityLogHasRoom","crmEmailDeliveryTags","crmScopeTokens","crmTaskListFields","crmTaskReminderAfterEdit","normalizeContactEmail","parseCrmMemberRef","readContactFacet","visibleToHost","consentGroupForSite","countCrmActivitiesForRecord","firebaseAdmin","newCrmActivityRef","orgDataQueryForHost","recomputeCrmNextTaskAt","restampCrmListFieldsAt","writeCrmEmailActivity","findContactByEmail","FieldValue","OWNER_ASSIGNMENT_REFUSALS","reassignContactOwner","CRM_ACTION_STEP_TYPES","Set","isCrmActionStep","step","has","type","DAY_MS","resolveEventContact","hostId","payload","hit","ref","contactsRef","contactId","String","trim","doc","get","exists","id","data","resolveMemberUid","orgId","named","role","uid","email","error","members","app","firestore","collection","kind","member","detail","byField","where","limit","docs","runCrmActionStep","env","actionId","nowMs","Date","now","contact","group","facet","groupId","previous","lifecycleStage","label","update","updatedAt","serverTimestamp","emit","event","previousStage","tag","slice","arrayUnion","assign","roundRobin","owner","ownerUid","ownerEmail","memberUid","verdict","outcome","reason","orgRef","visibleTo","org","links","companyId","title","dueInDays","Math","max","round","Number","assignee","assigneeUid","assigneeEmail","dueAtMs","task","priority","status","remindAtMs","createdByUid","sourceActionId","createdAt","tasks","add","catch","console","body","logged","activity","atMs","byUid","activities","prepareCrmEmailActivity","to","address","link","tags","activityId","logCrmEmailActivity","prepared","message","subject"],"mappings":"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"}
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
* See the License for the specific language governing permissions and
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/
|
|
17
|
-
import { type HostAction, type HostActionAlert, type HostActionStep, type HostFunction, type HostVariable, type
|
|
17
|
+
import { type HostAction, type HostActionAlert, type HostActionStep, type HostFunction, type HostVariable, type PluginJobHostGate } from '@aglyn/aglyn/server';
|
|
18
|
+
import { type HostWorkflow } from '../model/workflows';
|
|
18
19
|
import { type FlowEnrollment, type FlowSweepCursor, type FlowSweepResult } from './flow-enrollments';
|
|
19
20
|
import type { HostEventPayload } from '@aglyn/tenant-runtime/host-event-listeners';
|
|
20
21
|
import { type AutomationWorkflow, type WorkflowStep } from './workflow-steps';
|