@dev.smartpricing/message-composer-utils 4.5.1-next.3 → 5.0.0-automations.0

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.
@@ -0,0 +1,216 @@
1
+ import type { AutomationChannel } from "./automation-catalog.js";
2
+ import type { Condition, ConditionOperator } from "./condition.js";
3
+ import type {
4
+ MessageGroupProduct,
5
+ MessageGroupValidationErrors,
6
+ } from "./message.js";
7
+
8
+ /**
9
+ * Automations as the composer's API serves them (ADR 0016). The envelope
10
+ * speaks the composer's names; `settings`, `metadata` and an Action's
11
+ * `config` keep the Automation Engine's shapes, open to keys it adds.
12
+ */
13
+
14
+ export const AUTOMATION_ACTION_STATUSES = ["active", "inactive"] as const;
15
+
16
+ export type AutomationActionStatus =
17
+ (typeof AUTOMATION_ACTION_STATUSES)[number];
18
+
19
+ export interface AutomationCondition {
20
+ field: string;
21
+ operator: string;
22
+ value?: unknown;
23
+ [key: string]: unknown;
24
+ }
25
+
26
+ export interface AutomationSettings {
27
+ trigger: {
28
+ source?: string | string[];
29
+ type: string | string[];
30
+ [key: string]: unknown;
31
+ };
32
+ conditions: AutomationCondition[];
33
+ [key: string]: unknown;
34
+ }
35
+
36
+ /** The engine's Action config: `template` is the id of a Message Group. */
37
+ export interface AutomationActionConfig {
38
+ template: string;
39
+ channel: string;
40
+ recipient: string;
41
+ delay: number;
42
+ [key: string]: unknown;
43
+ }
44
+
45
+ export interface AutomationAction {
46
+ id: string;
47
+ type: string;
48
+ status: AutomationActionStatus;
49
+ created_at: string;
50
+ updated_at: string;
51
+ config: AutomationActionConfig;
52
+ }
53
+
54
+ export interface Automation {
55
+ id: string;
56
+ organization_id: string;
57
+ product: MessageGroupProduct;
58
+ name: string;
59
+ created_at: string;
60
+ updated_at: string;
61
+ /** Derived: at least one Active Action. */
62
+ active: boolean;
63
+ settings: AutomationSettings;
64
+ metadata?: Record<string, unknown>;
65
+ actions: AutomationAction[];
66
+ }
67
+
68
+ /**
69
+ * An Action as a client submits it (ADR 0016). It has no `type`, recipient
70
+ * or sender to choose: the backend writes them at save (ADR 0017), and
71
+ * ignores a `recipient` or `sender` sent in `config`.
72
+ */
73
+ export interface AutomationActionInput {
74
+ status: AutomationActionStatus;
75
+ config: {
76
+ /** The id of the Message Group the Action sends. */
77
+ template: string;
78
+ channel: AutomationChannel;
79
+ delay: number;
80
+ /** The key of the Action's Date Reference. */
81
+ dateTimeReference: string;
82
+ [key: string]: unknown;
83
+ };
84
+ }
85
+
86
+ /** One Trigger, with no `source`: the backend sets it from the Product. */
87
+ export interface AutomationSettingsInput {
88
+ trigger: { type: string; [key: string]: unknown };
89
+ conditions: Condition[];
90
+ [key: string]: unknown;
91
+ }
92
+
93
+ /** Body of `POST /automations`: an Automation with its Actions. */
94
+ export interface CreateAutomationBody {
95
+ organization_id: string;
96
+ product: MessageGroupProduct;
97
+ name: string;
98
+ settings: AutomationSettingsInput;
99
+ metadata?: Record<string, unknown>;
100
+ actions: AutomationActionInput[];
101
+ }
102
+
103
+ /**
104
+ * An Action in the full list an update submits (ADR 0016): without an `id`
105
+ * it is new, with one it is the stored Action of that id.
106
+ */
107
+ export interface AutomationActionUpdateInput extends AutomationActionInput {
108
+ id?: string;
109
+ }
110
+
111
+ /**
112
+ * Body of `PUT /automations/:id`. It names no Organization: an Automation
113
+ * never changes it. `metadata` is merged into what is stored.
114
+ */
115
+ export interface UpdateAutomationBody {
116
+ product: MessageGroupProduct;
117
+ name: string;
118
+ settings: AutomationSettingsInput;
119
+ metadata?: Record<string, unknown>;
120
+ /** Every Action the Automation keeps: a stored one left out is deleted. */
121
+ actions: AutomationActionUpdateInput[];
122
+ }
123
+
124
+ /**
125
+ * Body of `POST /automations/:id/clone` (ADR 0016). The copy is made from
126
+ * the Automation as stored, never from what a client holds: the request
127
+ * only names it.
128
+ */
129
+ export interface CloneAutomationBody {
130
+ product: MessageGroupProduct;
131
+ name: string;
132
+ }
133
+
134
+ /**
135
+ * `details` of the 502 of an update that failed after its first write
136
+ * (ADR 0016): nothing is rolled back, so this is what a retry starts from.
137
+ */
138
+ export interface AutomationPartlySavedDetails {
139
+ /** The Automation as it now stands in the Automation Engine. */
140
+ automation: Automation;
141
+ /**
142
+ * The id of each submitted Action, by position: `null` for a new Action
143
+ * that was not created. A retry that sends these ids creates nothing twice.
144
+ */
145
+ action_ids: Array<string | null>;
146
+ }
147
+
148
+ /**
149
+ * One thing that keeps an Automation from being saved. `target` names what
150
+ * it is about; an Action and a Condition are pointed at by `position`, their
151
+ * index in the submitted list, since a new Action has no id to be named by.
152
+ * A client skips a `target` or a `reason` it does not know.
153
+ */
154
+ export type AutomationSaveProblem =
155
+ | { target: "trigger"; reason: "not_in_catalog"; type: string }
156
+ | {
157
+ target: "condition";
158
+ /** Index in `settings.conditions`. */
159
+ position: number;
160
+ reason: "field_not_in_catalog" | "operator_not_allowed";
161
+ field: string;
162
+ operator: ConditionOperator;
163
+ }
164
+ | ({ target: "action"; position: number } & (
165
+ | {
166
+ reason: "not_found" | "other_organization";
167
+ message_group_id: string;
168
+ }
169
+ | {
170
+ reason: "invalid";
171
+ message_group_id: string;
172
+ /** The errors of the Message Group validation. */
173
+ errors: MessageGroupValidationErrors;
174
+ }
175
+ | { reason: "date_reference_not_in_catalog"; date_reference: string }
176
+ | { reason: "channel_not_in_catalog"; channel: AutomationChannel }
177
+ ));
178
+
179
+ export type AutomationActionProblem = Extract<
180
+ AutomationSaveProblem,
181
+ { target: "action" }
182
+ >;
183
+
184
+ export type AutomationConditionProblem = Extract<
185
+ AutomationSaveProblem,
186
+ { target: "condition" }
187
+ >;
188
+
189
+ /**
190
+ * `details` of the 422 `AUTOMATION_ACTIONS_INVALID`: everything that keeps
191
+ * an Automation from being saved, found before anything is written. One
192
+ * flat list: the Trigger first, then Conditions and Actions by position.
193
+ */
194
+ export interface AutomationActionsInvalidDetails {
195
+ problems: AutomationSaveProblem[];
196
+ }
197
+
198
+ /** Why a selected Message Group is still there after its Automation went. */
199
+ export const AUTOMATION_MESSAGE_GROUP_KEPT_REASONS = ["delete_failed"] as const;
200
+
201
+ export type AutomationMessageGroupKeptReason =
202
+ (typeof AUTOMATION_MESSAGE_GROUP_KEPT_REASONS)[number];
203
+
204
+ /**
205
+ * Answer of a `DELETE /automations/:id` that selected Message Groups
206
+ * (ADR 0016): the Automation is gone, and each selected Message Group is
207
+ * either `deleted` or `kept`. A client skips a `reason` it does not know.
208
+ */
209
+ export interface AutomationDeleteResult {
210
+ deleted: Array<{ id: string; name: string }>;
211
+ kept: Array<{
212
+ id: string;
213
+ name: string;
214
+ reason: AutomationMessageGroupKeptReason;
215
+ }>;
216
+ }
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Conditions in the Automation Engine's shape, and the closed vocabulary the
3
+ * filter components and the backend share (ADR 0018). Nothing here knows
4
+ * about Automations: an audience page edits Conditions too.
5
+ */
6
+
7
+ /** Every operator the Automation Engine accepts. */
8
+ export const CONDITION_OPERATORS = [
9
+ "eq",
10
+ "neq",
11
+ "gt",
12
+ "gte",
13
+ "lt",
14
+ "lte",
15
+ "in",
16
+ "not_in",
17
+ "contains",
18
+ "exclude",
19
+ "exists",
20
+ ] as const;
21
+
22
+ export type ConditionOperator = (typeof CONDITION_OPERATORS)[number];
23
+
24
+ export const CONDITION_FIELD_TYPES = [
25
+ "select",
26
+ "multiselect",
27
+ "number",
28
+ "price",
29
+ "date",
30
+ "text",
31
+ ] as const;
32
+
33
+ export type ConditionFieldType = (typeof CONDITION_FIELD_TYPES)[number];
34
+
35
+ /** One test on a Trigger Event's data. `exists` takes no value. */
36
+ export interface Condition {
37
+ field: string;
38
+ operator: ConditionOperator;
39
+ value?: unknown;
40
+ }
41
+
42
+ /** The operators of the field types whose values have an order. */
43
+ const ORDERED_VALUE_OPERATORS = [
44
+ "eq",
45
+ "neq",
46
+ "gt",
47
+ "gte",
48
+ "lt",
49
+ "lte",
50
+ "exists",
51
+ ] as const satisfies readonly ConditionOperator[];
52
+
53
+ /** The operators a Condition on a field of each type can use (ADR 0018). */
54
+ export const CONDITION_OPERATORS_BY_FIELD_TYPE = {
55
+ select: ["eq", "neq", "exists"],
56
+ multiselect: ["in", "not_in", "exists"],
57
+ number: ORDERED_VALUE_OPERATORS,
58
+ price: ORDERED_VALUE_OPERATORS,
59
+ date: ORDERED_VALUE_OPERATORS,
60
+ text: ["eq", "neq", "contains", "exclude", "exists"],
61
+ } as const satisfies Record<ConditionFieldType, readonly ConditionOperator[]>;
62
+
63
+ export function operatorFitsFieldType(
64
+ operator: ConditionOperator,
65
+ fieldType: ConditionFieldType,
66
+ ): boolean {
67
+ const operators: readonly ConditionOperator[] =
68
+ CONDITION_OPERATORS_BY_FIELD_TYPE[fieldType];
69
+ return operators.includes(operator);
70
+ }
71
+
72
+ /**
73
+ * What a caller passes to the layer's filter components (`FiltersEditor`,
74
+ * `FiltersSummary`) and what they report back (ADR 0018).
75
+ */
76
+
77
+ export type FilterOptionValue = string | number;
78
+
79
+ export interface FilterOption {
80
+ value: FilterOptionValue;
81
+ label: string;
82
+ }
83
+
84
+ /** One field a Condition can test, label and options already resolved. */
85
+ export interface FilterField {
86
+ /** The `field` of the Conditions on it. */
87
+ key: string;
88
+ label: string;
89
+ type: ConditionFieldType;
90
+ /** For `select` and `multiselect`. A list that is not there shows as empty. */
91
+ options?: FilterOption[];
92
+ }
93
+
94
+ /**
95
+ * Why a Condition shows as an unavailable row: its field is not in `fields`,
96
+ * or the editor cannot edit it (an operator or a value that does not fit the
97
+ * field's type, one Condition too many on a field).
98
+ */
99
+ export type FilterUnavailableReason = "unknown_field" | "not_editable";
100
+
101
+ /** What is wrong with a list of Conditions. The caller decides what blocks. */
102
+ export interface FiltersReport {
103
+ /** The fields of the Conditions that are on none of `fields`, each once. */
104
+ unknownFields: string[];
105
+ /** Every Condition shown as an unavailable row, the unknown fields included. */
106
+ unavailable: { condition: Condition; reason: FilterUnavailableReason }[];
107
+ /** Every selected option value that is not in its field's `options`. */
108
+ unavailableOptions: { field: string; value: FilterOptionValue }[];
109
+ /**
110
+ * Rows with a field picked and no complete value. They are not in the
111
+ * emitted Conditions. Always 0 from `FiltersSummary`.
112
+ */
113
+ halfFilled: number;
114
+ }
@@ -15,3 +15,6 @@ export * from "./dynamic-values.js";
15
15
  export * from "./brand.js";
16
16
  export * from "./organization.js";
17
17
  export * from "./pagination.js";
18
+ export * from "./automation.js";
19
+ export * from "./condition.js";
20
+ export * from "./automation-catalog.js";