@rippling/rippling-sdk 0.2.0-alpha.102 → 0.2.0-alpha.104

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.
Files changed (189) hide show
  1. package/examples/manifest/workflow-creation/workflow-creation.ts +142 -0
  2. package/lib/manifest/index.d.mts +2 -1
  3. package/lib/manifest/index.d.mts.map +1 -1
  4. package/lib/manifest/index.d.ts +2 -1
  5. package/lib/manifest/index.d.ts.map +1 -1
  6. package/lib/manifest/index.js +5 -2
  7. package/lib/manifest/index.js.map +1 -1
  8. package/lib/manifest/index.mjs +2 -1
  9. package/lib/manifest/index.mjs.map +1 -1
  10. package/lib/manifest/workflow/actions/index.d.mts +365 -0
  11. package/lib/manifest/workflow/actions/index.d.mts.map +1 -0
  12. package/lib/manifest/workflow/actions/index.d.ts +365 -0
  13. package/lib/manifest/workflow/actions/index.d.ts.map +1 -0
  14. package/lib/manifest/workflow/actions/index.js +47 -0
  15. package/lib/manifest/workflow/actions/index.js.map +1 -0
  16. package/lib/manifest/workflow/actions/index.mjs +44 -0
  17. package/lib/manifest/workflow/actions/index.mjs.map +1 -0
  18. package/lib/manifest/workflow/index.d.mts +384 -0
  19. package/lib/manifest/workflow/index.d.mts.map +1 -0
  20. package/lib/manifest/workflow/index.d.ts +384 -0
  21. package/lib/manifest/workflow/index.d.ts.map +1 -0
  22. package/lib/manifest/workflow/index.js +447 -0
  23. package/lib/manifest/workflow/index.js.map +1 -0
  24. package/lib/manifest/workflow/index.mjs +442 -0
  25. package/lib/manifest/workflow/index.mjs.map +1 -0
  26. package/package.json +1 -1
  27. package/resources/departments.d.mts +3 -5
  28. package/resources/departments.d.mts.map +1 -1
  29. package/resources/departments.d.ts +3 -5
  30. package/resources/departments.d.ts.map +1 -1
  31. package/resources/departments.js +2 -4
  32. package/resources/departments.js.map +1 -1
  33. package/resources/departments.mjs +2 -4
  34. package/resources/departments.mjs.map +1 -1
  35. package/resources/hires.d.mts +4 -8
  36. package/resources/hires.d.mts.map +1 -1
  37. package/resources/hires.d.ts +4 -8
  38. package/resources/hires.d.ts.map +1 -1
  39. package/resources/hires.js +4 -4
  40. package/resources/hires.mjs +4 -4
  41. package/resources/job-assignments.d.mts +10 -13
  42. package/resources/job-assignments.d.mts.map +1 -1
  43. package/resources/job-assignments.d.ts +10 -13
  44. package/resources/job-assignments.d.ts.map +1 -1
  45. package/resources/job-assignments.js +2 -5
  46. package/resources/job-assignments.js.map +1 -1
  47. package/resources/job-assignments.mjs +2 -5
  48. package/resources/job-assignments.mjs.map +1 -1
  49. package/resources/job-codes.d.mts +10 -13
  50. package/resources/job-codes.d.mts.map +1 -1
  51. package/resources/job-codes.d.ts +10 -13
  52. package/resources/job-codes.d.ts.map +1 -1
  53. package/resources/job-codes.js +2 -5
  54. package/resources/job-codes.js.map +1 -1
  55. package/resources/job-codes.mjs +2 -5
  56. package/resources/job-codes.mjs.map +1 -1
  57. package/resources/job-dimensions.d.mts +6 -8
  58. package/resources/job-dimensions.d.mts.map +1 -1
  59. package/resources/job-dimensions.d.ts +6 -8
  60. package/resources/job-dimensions.d.ts.map +1 -1
  61. package/resources/job-dimensions.js +2 -4
  62. package/resources/job-dimensions.js.map +1 -1
  63. package/resources/job-dimensions.mjs +2 -4
  64. package/resources/job-dimensions.mjs.map +1 -1
  65. package/resources/kiosk-badges.d.mts +3 -5
  66. package/resources/kiosk-badges.d.mts.map +1 -1
  67. package/resources/kiosk-badges.d.ts +3 -5
  68. package/resources/kiosk-badges.d.ts.map +1 -1
  69. package/resources/kiosk-badges.js +2 -4
  70. package/resources/kiosk-badges.js.map +1 -1
  71. package/resources/kiosk-badges.mjs +2 -4
  72. package/resources/kiosk-badges.mjs.map +1 -1
  73. package/resources/leave-accruals.d.mts +4 -4
  74. package/resources/leave-accruals.d.mts.map +1 -1
  75. package/resources/leave-accruals.d.ts +4 -4
  76. package/resources/leave-accruals.d.ts.map +1 -1
  77. package/resources/leave-requests.d.mts +18 -23
  78. package/resources/leave-requests.d.mts.map +1 -1
  79. package/resources/leave-requests.d.ts +18 -23
  80. package/resources/leave-requests.d.ts.map +1 -1
  81. package/resources/leave-requests.js +2 -7
  82. package/resources/leave-requests.js.map +1 -1
  83. package/resources/leave-requests.mjs +2 -7
  84. package/resources/leave-requests.mjs.map +1 -1
  85. package/resources/packages/packages.d.mts +17 -17
  86. package/resources/packages/packages.d.mts.map +1 -1
  87. package/resources/packages/packages.d.ts +17 -17
  88. package/resources/packages/packages.d.ts.map +1 -1
  89. package/resources/packages/packages.js +5 -5
  90. package/resources/packages/packages.mjs +5 -5
  91. package/resources/sandboxes.d.mts +7 -5
  92. package/resources/sandboxes.d.mts.map +1 -1
  93. package/resources/sandboxes.d.ts +7 -5
  94. package/resources/sandboxes.d.ts.map +1 -1
  95. package/resources/sandboxes.js +3 -1
  96. package/resources/sandboxes.js.map +1 -1
  97. package/resources/sandboxes.mjs +3 -1
  98. package/resources/sandboxes.mjs.map +1 -1
  99. package/resources/shift-assignments.d.mts +11 -6
  100. package/resources/shift-assignments.d.mts.map +1 -1
  101. package/resources/shift-assignments.d.ts +11 -6
  102. package/resources/shift-assignments.d.ts.map +1 -1
  103. package/resources/shift-assignments.js +8 -4
  104. package/resources/shift-assignments.js.map +1 -1
  105. package/resources/shift-assignments.mjs +8 -4
  106. package/resources/shift-assignments.mjs.map +1 -1
  107. package/resources/shift-inputs.d.mts +10 -15
  108. package/resources/shift-inputs.d.mts.map +1 -1
  109. package/resources/shift-inputs.d.ts +10 -15
  110. package/resources/shift-inputs.d.ts.map +1 -1
  111. package/resources/shift-inputs.js +2 -7
  112. package/resources/shift-inputs.js.map +1 -1
  113. package/resources/shift-inputs.mjs +2 -7
  114. package/resources/shift-inputs.mjs.map +1 -1
  115. package/resources/sites.d.mts +19 -6
  116. package/resources/sites.d.mts.map +1 -1
  117. package/resources/sites.d.ts +19 -6
  118. package/resources/sites.d.ts.map +1 -1
  119. package/resources/software-deployments.d.mts +6 -8
  120. package/resources/software-deployments.d.mts.map +1 -1
  121. package/resources/software-deployments.d.ts +6 -8
  122. package/resources/software-deployments.d.ts.map +1 -1
  123. package/resources/software-deployments.js +2 -4
  124. package/resources/software-deployments.js.map +1 -1
  125. package/resources/software-deployments.mjs +2 -4
  126. package/resources/software-deployments.mjs.map +1 -1
  127. package/resources/supergroups/members.d.mts +3 -1
  128. package/resources/supergroups/members.d.mts.map +1 -1
  129. package/resources/supergroups/members.d.ts +3 -1
  130. package/resources/supergroups/members.d.ts.map +1 -1
  131. package/resources/supergroups/members.js +3 -1
  132. package/resources/supergroups/members.js.map +1 -1
  133. package/resources/supergroups/members.mjs +3 -1
  134. package/resources/supergroups/members.mjs.map +1 -1
  135. package/resources/unassigned-shifts.d.mts +11 -6
  136. package/resources/unassigned-shifts.d.mts.map +1 -1
  137. package/resources/unassigned-shifts.d.ts +11 -6
  138. package/resources/unassigned-shifts.d.ts.map +1 -1
  139. package/resources/unassigned-shifts.js +8 -4
  140. package/resources/unassigned-shifts.js.map +1 -1
  141. package/resources/unassigned-shifts.mjs +8 -4
  142. package/resources/unassigned-shifts.mjs.map +1 -1
  143. package/resources/variable-compensation-payouts.d.mts +4 -4
  144. package/resources/variable-compensation-payouts.d.mts.map +1 -1
  145. package/resources/variable-compensation-payouts.d.ts +4 -4
  146. package/resources/variable-compensation-payouts.d.ts.map +1 -1
  147. package/resources/work-locations.d.mts +4 -7
  148. package/resources/work-locations.d.mts.map +1 -1
  149. package/resources/work-locations.d.ts +4 -7
  150. package/resources/work-locations.d.ts.map +1 -1
  151. package/resources/work-locations.js +2 -5
  152. package/resources/work-locations.js.map +1 -1
  153. package/resources/work-locations.mjs +2 -5
  154. package/resources/work-locations.mjs.map +1 -1
  155. package/src/lib/manifest/index.ts +40 -0
  156. package/src/lib/manifest/workflow/actions/index.ts +338 -0
  157. package/src/lib/manifest/workflow/index.ts +953 -0
  158. package/src/resources/departments.ts +7 -5
  159. package/src/resources/hires.ts +4 -9
  160. package/src/resources/job-assignments.ts +16 -15
  161. package/src/resources/job-codes.ts +16 -15
  162. package/src/resources/job-dimensions.ts +11 -9
  163. package/src/resources/kiosk-badges.ts +3 -5
  164. package/src/resources/leave-accruals.ts +8 -4
  165. package/src/resources/leave-requests.ts +21 -26
  166. package/src/resources/packages/packages.ts +17 -17
  167. package/src/resources/sandboxes.ts +7 -5
  168. package/src/resources/shift-assignments.ts +11 -8
  169. package/src/resources/shift-inputs.ts +11 -16
  170. package/src/resources/sites.ts +19 -6
  171. package/src/resources/software-deployments.ts +7 -9
  172. package/src/resources/supergroups/members.ts +3 -1
  173. package/src/resources/unassigned-shifts.ts +11 -8
  174. package/src/resources/variable-compensation-payouts.ts +0 -4
  175. package/src/resources/work-locations.ts +8 -7
  176. package/src/version.ts +1 -1
  177. package/version.d.mts +1 -1
  178. package/version.d.ts +1 -1
  179. package/version.js +1 -1
  180. package/version.mjs +1 -1
  181. package/lib/manifest/workflow.d.mts +0 -158
  182. package/lib/manifest/workflow.d.mts.map +0 -1
  183. package/lib/manifest/workflow.d.ts +0 -158
  184. package/lib/manifest/workflow.d.ts.map +0 -1
  185. package/lib/manifest/workflow.js +0 -51
  186. package/lib/manifest/workflow.js.map +0 -1
  187. package/lib/manifest/workflow.mjs +0 -47
  188. package/lib/manifest/workflow.mjs.map +0 -1
  189. package/src/lib/manifest/workflow.ts +0 -248
@@ -0,0 +1,953 @@
1
+ import { generateId } from '../_helpers';
2
+ import type { BusinessEventKey } from '../business-event';
3
+ import type { ManifestFunction } from '../manifest-function';
4
+ import type { ManifestComponentDeletion, ManifestScope } from '../manifest-builder';
5
+ import type { WorkflowActionPayloads } from './actions';
6
+
7
+ export type WorkflowVersionStatus = 'VERSION_STATUS_DRAFT' | 'VERSION_STATUS_PUBLISHED';
8
+ export type WorkflowTriggerStatus =
9
+ | 'TRIGGER_STATUS_ACTIVE'
10
+ | 'TRIGGER_STATUS_INACTIVE'
11
+ | 'TRIGGER_STATUS_SYSTEM_DISABLED';
12
+ export type WorkflowConditionBuilderMode = 'CONDITION_BUILDER_MODE_BASIC' | 'CONDITION_BUILDER_MODE_ADVANCED';
13
+
14
+ export interface WorkflowExecutionRateLimit {
15
+ execution_limit: number;
16
+ }
17
+
18
+ export type WorkflowCustomVariableType =
19
+ | 'CUSTOM_VARIABLE_TYPE_TEXT'
20
+ | 'CUSTOM_VARIABLE_TYPE_NUMBER'
21
+ | 'CUSTOM_VARIABLE_TYPE_BOOLEAN'
22
+ | 'CUSTOM_VARIABLE_TYPE_DATE'
23
+ | 'CUSTOM_VARIABLE_TYPE_DATETIME';
24
+
25
+ export interface WorkflowCustomVariable {
26
+ name: string;
27
+ description?: string;
28
+ data_type: WorkflowCustomVariableType;
29
+ }
30
+
31
+ export type WorkflowCategoryName =
32
+ | 'WORKFLOW_CATEGORY_NAME_BILLING_INCLUDED'
33
+ | 'WORKFLOW_CATEGORY_NAME_CUSTOMER_CLOUD_SOURCE'
34
+ | 'WORKFLOW_CATEGORY_NAME_TRANSFORMATIONS_SCHEDULES';
35
+
36
+ export interface WorkflowCategory {
37
+ name: WorkflowCategoryName;
38
+ sub_categories?: string[];
39
+ }
40
+
41
+ export interface WorkflowConditionalBranch {
42
+ rql_formula?: string;
43
+ node_id?: string;
44
+ }
45
+
46
+ export interface WorkflowFork {
47
+ branches: string[];
48
+ }
49
+
50
+ export interface WorkflowLoop {
51
+ inner_node: string;
52
+ }
53
+
54
+ export interface WorkflowGroup {
55
+ inner_node: string;
56
+ }
57
+
58
+ export interface WorkflowSwitch {
59
+ branches: WorkflowConditionalBranch[];
60
+ default_node?: string;
61
+ }
62
+
63
+ export interface WorkflowSummaryBullet {
64
+ text?: string;
65
+ sub_bullets?: WorkflowSummaryBullet[];
66
+ metadata?: Record<string, string>;
67
+ }
68
+
69
+ export interface WorkflowSummary {
70
+ text?: string;
71
+ bullets?: WorkflowSummaryBullet[];
72
+ does_not_do?: string[];
73
+ }
74
+
75
+ export interface WorkflowFunctionProcessingContext {
76
+ function_summary?: string;
77
+ function_summary_steps?: WorkflowSummaryBullet[];
78
+ function_api_name?: string;
79
+ action_config_ids?: string[];
80
+ summary?: WorkflowSummary;
81
+ }
82
+
83
+ export interface WorkflowDigestProcessingContext {
84
+ digest_config_id?: string;
85
+ }
86
+
87
+ export type WorkflowProcessingContext =
88
+ | { function_context: WorkflowFunctionProcessingContext; digest_context?: never }
89
+ | { function_context?: never; digest_context: WorkflowDigestProcessingContext }
90
+ | { function_context?: never; digest_context?: never };
91
+
92
+ export interface WorkflowNode {
93
+ /** Studio's role-scoped Action Registry lists config IDs via GetActionConfigSummaries; the public SDK does not expose that RPC. */
94
+ config_id: string;
95
+ /** Fetch this config_id's input JSON Schema with Action Registry GetActionConfig; payloads vary by role and company. */
96
+ payload?: Record<string, unknown>;
97
+ next?: string;
98
+ step_alias?: string;
99
+ fork?: WorkflowFork;
100
+ loop?: WorkflowLoop;
101
+ group?: WorkflowGroup;
102
+ switch?: WorkflowSwitch;
103
+ processing_context?: WorkflowProcessingContext;
104
+ summary?: WorkflowSummary;
105
+ }
106
+ /** Typed authoring from rippling-main's Action Registry snapshot; raw WorkflowNode supports newer actions. */
107
+ export function workflowAction<K extends keyof WorkflowActionPayloads>(
108
+ configId: K,
109
+ payload: NoInfer<WorkflowActionPayloads[K]>,
110
+ options: Omit<WorkflowNode, 'config_id' | 'payload'> = {},
111
+ ): WorkflowNode {
112
+ return { ...options, config_id: configId, payload };
113
+ }
114
+
115
+ export interface WorkflowGraph {
116
+ root_node_id: string;
117
+ nodes: Record<string, WorkflowNode>;
118
+ }
119
+
120
+ export type WorkflowAutomationEventType =
121
+ | 'AUTOMATION_EVENT_TYPE_BUSINESS'
122
+ | 'AUTOMATION_EVENT_TYPE_RECORD_CHANGE'
123
+ | 'AUTOMATION_EVENT_TYPE_SCHEDULED'
124
+ | 'AUTOMATION_EVENT_TYPE_MANUAL'
125
+ | 'AUTOMATION_EVENT_TYPE_DATE_RELATIVE'
126
+ | 'AUTOMATION_EVENT_TYPE_WEBHOOK'
127
+ | 'AUTOMATION_EVENT_TYPE_INTEGRATION';
128
+
129
+ export type WorkflowRecordChangeOperationType =
130
+ | 'RECORD_CHANGE_OPERATION_TYPE_CREATE'
131
+ | 'RECORD_CHANGE_OPERATION_TYPE_UPDATE'
132
+ | 'RECORD_CHANGE_OPERATION_TYPE_DELETE'
133
+ | 'RECORD_CHANGE_OPERATION_TYPE_CREATE_OR_UPDATE';
134
+
135
+ export type WorkflowDateRelativeOperatorType =
136
+ | 'DATE_RELATIVE_TRIGGER_OPERATOR_TYPE_EQ'
137
+ | 'DATE_RELATIVE_TRIGGER_OPERATOR_TYPE_WITHIN'
138
+ | 'DATE_RELATIVE_TRIGGER_OPERATOR_TYPE_BETWEEN';
139
+
140
+ export type WorkflowScheduledFrequencyType =
141
+ | 'SCHEDULED_TRIGGER_FREQUENCY_TYPE_DAILY'
142
+ | 'SCHEDULED_TRIGGER_FREQUENCY_TYPE_WEEKDAY_DAILY'
143
+ | 'SCHEDULED_TRIGGER_FREQUENCY_TYPE_WEEKLY'
144
+ | 'SCHEDULED_TRIGGER_FREQUENCY_TYPE_BIWEEKLY'
145
+ | 'SCHEDULED_TRIGGER_FREQUENCY_TYPE_MONTHLY'
146
+ | 'SCHEDULED_TRIGGER_FREQUENCY_TYPE_MONTHLY_ON_DAY_OF_WEEK'
147
+ | 'SCHEDULED_TRIGGER_FREQUENCY_TYPE_ONE_TIME'
148
+ | 'SCHEDULED_TRIGGER_FREQUENCY_TYPE_QUARTERLY'
149
+ | 'SCHEDULED_TRIGGER_FREQUENCY_TYPE_ANNUALLY'
150
+ | 'SCHEDULED_TRIGGER_FREQUENCY_TYPE_CUSTOM'
151
+ | 'SCHEDULED_TRIGGER_FREQUENCY_TYPE_CRON';
152
+
153
+ export type WorkflowCustomScheduleFrequencyUnit =
154
+ | 'CUSTOM_SCHEDULE_TRIGGER_FREQUENCY_UNIT_DAYS'
155
+ | 'CUSTOM_SCHEDULE_TRIGGER_FREQUENCY_UNIT_WEEKS'
156
+ | 'CUSTOM_SCHEDULE_TRIGGER_FREQUENCY_UNIT_MONTHS'
157
+ | 'CUSTOM_SCHEDULE_TRIGGER_FREQUENCY_UNIT_YEARS'
158
+ | 'CUSTOM_SCHEDULE_TRIGGER_FREQUENCY_UNIT_HOURS'
159
+ | 'CUSTOM_SCHEDULE_TRIGGER_FREQUENCY_UNIT_MINUTES';
160
+
161
+ export interface WorkflowTimeOfDaySchedule {
162
+ hour_of_day: number;
163
+ minute_of_hour?: number;
164
+ }
165
+
166
+ export interface WorkflowOneTimeSchedule extends WorkflowTimeOfDaySchedule {
167
+ year: number;
168
+ month_of_year: number;
169
+ day_of_month: number;
170
+ }
171
+
172
+ export interface WorkflowWeeklySchedule extends WorkflowTimeOfDaySchedule {
173
+ day_of_week: number[];
174
+ }
175
+
176
+ export interface WorkflowMonthlySchedule extends WorkflowTimeOfDaySchedule {
177
+ day_of_month: number;
178
+ start_month?: number;
179
+ }
180
+
181
+ export interface WorkflowMonthlyOnDayOfWeekSchedule extends WorkflowTimeOfDaySchedule {
182
+ day_of_week: number;
183
+ day_of_month: number;
184
+ }
185
+
186
+ export interface WorkflowQuarterlySchedule extends WorkflowTimeOfDaySchedule {
187
+ day_of_month: number;
188
+ month_of_year: number;
189
+ }
190
+
191
+ export interface WorkflowAnnualSchedule extends WorkflowTimeOfDaySchedule {
192
+ day_of_month: number;
193
+ month_of_year: number;
194
+ start_month?: number;
195
+ }
196
+
197
+ export interface WorkflowCronSchedule {
198
+ cron_expression: string;
199
+ }
200
+
201
+ type WorkflowCustomScheduleFields = {
202
+ daily_schedule: WorkflowTimeOfDaySchedule;
203
+ weekly_schedule: WorkflowWeeklySchedule;
204
+ monthly_schedule: WorkflowMonthlySchedule;
205
+ annual_schedule: WorkflowAnnualSchedule;
206
+ };
207
+
208
+ type WorkflowCustomScheduleChoice =
209
+ | {
210
+ custom_schedule_frequency: 'CUSTOM_SCHEDULE_TRIGGER_FREQUENCY_UNIT_DAYS';
211
+ daily_schedule: WorkflowTimeOfDaySchedule;
212
+ weekly_schedule?: never;
213
+ monthly_schedule?: never;
214
+ annual_schedule?: never;
215
+ }
216
+ | {
217
+ custom_schedule_frequency: 'CUSTOM_SCHEDULE_TRIGGER_FREQUENCY_UNIT_WEEKS';
218
+ weekly_schedule: WorkflowWeeklySchedule;
219
+ daily_schedule?: never;
220
+ monthly_schedule?: never;
221
+ annual_schedule?: never;
222
+ }
223
+ | {
224
+ custom_schedule_frequency: 'CUSTOM_SCHEDULE_TRIGGER_FREQUENCY_UNIT_MONTHS';
225
+ monthly_schedule: WorkflowMonthlySchedule;
226
+ daily_schedule?: never;
227
+ weekly_schedule?: never;
228
+ annual_schedule?: never;
229
+ }
230
+ | {
231
+ custom_schedule_frequency: 'CUSTOM_SCHEDULE_TRIGGER_FREQUENCY_UNIT_YEARS';
232
+ annual_schedule: WorkflowAnnualSchedule;
233
+ daily_schedule?: never;
234
+ weekly_schedule?: never;
235
+ monthly_schedule?: never;
236
+ }
237
+ | ({
238
+ custom_schedule_frequency:
239
+ | 'CUSTOM_SCHEDULE_TRIGGER_FREQUENCY_UNIT_HOURS'
240
+ | 'CUSTOM_SCHEDULE_TRIGGER_FREQUENCY_UNIT_MINUTES';
241
+ } & { [K in keyof WorkflowCustomScheduleFields]?: never });
242
+
243
+ export type WorkflowCustomSchedule = WorkflowCustomScheduleChoice & {
244
+ custom_schedule_frequency_value: number;
245
+ hour_of_day?: number;
246
+ minute_of_hour?: number;
247
+ };
248
+
249
+ type WorkflowScheduledKeys = {
250
+ SCHEDULED_TRIGGER_FREQUENCY_TYPE_DAILY: { daily_schedule: WorkflowTimeOfDaySchedule };
251
+ SCHEDULED_TRIGGER_FREQUENCY_TYPE_WEEKDAY_DAILY: { weekday_schedule: WorkflowTimeOfDaySchedule };
252
+ SCHEDULED_TRIGGER_FREQUENCY_TYPE_WEEKLY: { weekly_schedule: WorkflowWeeklySchedule };
253
+ SCHEDULED_TRIGGER_FREQUENCY_TYPE_BIWEEKLY: { bi_weekly_schedule: WorkflowWeeklySchedule };
254
+ SCHEDULED_TRIGGER_FREQUENCY_TYPE_MONTHLY: { monthly_schedule: WorkflowMonthlySchedule };
255
+ SCHEDULED_TRIGGER_FREQUENCY_TYPE_MONTHLY_ON_DAY_OF_WEEK: {
256
+ monthly_on_day_of_week_schedule: WorkflowMonthlyOnDayOfWeekSchedule;
257
+ };
258
+ SCHEDULED_TRIGGER_FREQUENCY_TYPE_ONE_TIME: { one_time_schedule: WorkflowOneTimeSchedule };
259
+ SCHEDULED_TRIGGER_FREQUENCY_TYPE_QUARTERLY: { quarterly_schedule: WorkflowQuarterlySchedule };
260
+ SCHEDULED_TRIGGER_FREQUENCY_TYPE_ANNUALLY: { annual_schedule: WorkflowAnnualSchedule };
261
+ SCHEDULED_TRIGGER_FREQUENCY_TYPE_CUSTOM: { custom_schedule: WorkflowCustomSchedule };
262
+ SCHEDULED_TRIGGER_FREQUENCY_TYPE_CRON: { cron_schedule: WorkflowCronSchedule };
263
+ };
264
+
265
+ type WorkflowScheduleKey = {
266
+ [F in WorkflowScheduledFrequencyType]: keyof WorkflowScheduledKeys[F];
267
+ }[WorkflowScheduledFrequencyType];
268
+ export type WorkflowScheduledTriggerConfig = {
269
+ [F in WorkflowScheduledFrequencyType]: {
270
+ frequency: F;
271
+ timezone: string;
272
+ supported_role_states?: WorkflowRoleState[];
273
+ } & WorkflowScheduledKeys[F] & {
274
+ [K in Exclude<WorkflowScheduleKey, keyof WorkflowScheduledKeys[F]>]?: never;
275
+ };
276
+ }[WorkflowScheduledFrequencyType];
277
+
278
+ export interface WorkflowTriggerBase {
279
+ automation_event_type: WorkflowAutomationEventType;
280
+ og_module?: string;
281
+ base_model?: string;
282
+ rql_formula?: string;
283
+ is_basic_workflow?: boolean;
284
+ og_category?: string;
285
+ condition_builder_mode?: WorkflowConditionBuilderMode;
286
+ conditions_tree?: string;
287
+ execution_rate_limit?: WorkflowExecutionRateLimit;
288
+ }
289
+
290
+ export interface WorkflowBusinessTrigger extends WorkflowTriggerBase {
291
+ automation_event_type: 'AUTOMATION_EVENT_TYPE_BUSINESS';
292
+ /** Business event to subscribe to. Availability is validated during installation. */
293
+ event_name: BusinessEventKey;
294
+ business_event_trigger?: {
295
+ navigation_path?: string;
296
+ };
297
+ }
298
+
299
+ export type WorkflowBusinessEventKey = BusinessEventKey;
300
+
301
+ export interface WorkflowRecordChangeTrigger extends WorkflowTriggerBase {
302
+ automation_event_type: 'AUTOMATION_EVENT_TYPE_RECORD_CHANGE';
303
+ record_change_operation_type: WorkflowRecordChangeOperationType;
304
+ }
305
+
306
+ export type WorkflowRoleState =
307
+ | 'ROLE_STATE_INIT'
308
+ | 'ROLE_STATE_HIRED'
309
+ | 'ROLE_STATE_ACCEPTED'
310
+ | 'ROLE_STATE_ACTIVE'
311
+ | 'ROLE_STATE_TERMINATED';
312
+
313
+ export type WorkflowDateRelativeBaseField = 'start_date' | 'end_date';
314
+ export interface WorkflowDateRelativeOperand {
315
+ time_delta: { days: number };
316
+ }
317
+
318
+ export type WorkflowDateRelativeTriggerConfig = {
319
+ hour_of_day: number;
320
+ minute_of_hour?: number;
321
+ timezone?: string;
322
+ supported_role_states?: WorkflowRoleState[];
323
+ trigger_on_anniversary?: boolean;
324
+ should_trigger_exactly_once_per_trigger_object?: boolean;
325
+ } & (
326
+ | { base_model_date_field: WorkflowDateRelativeBaseField; base_model_date_field_rql_path?: never }
327
+ | { base_model_date_field?: never; base_model_date_field_rql_path: string }
328
+ ) &
329
+ (
330
+ | {
331
+ date_range_operator?: 'DATE_RELATIVE_TRIGGER_OPERATOR_TYPE_EQ';
332
+ operands: [WorkflowDateRelativeOperand];
333
+ }
334
+ | {
335
+ date_range_operator: 'DATE_RELATIVE_TRIGGER_OPERATOR_TYPE_WITHIN';
336
+ operands: [WorkflowDateRelativeOperand];
337
+ }
338
+ | {
339
+ date_range_operator: 'DATE_RELATIVE_TRIGGER_OPERATOR_TYPE_BETWEEN';
340
+ operands: [WorkflowDateRelativeOperand, WorkflowDateRelativeOperand];
341
+ }
342
+ );
343
+
344
+ export interface WorkflowDateRelativeTrigger extends WorkflowTriggerBase {
345
+ automation_event_type: 'AUTOMATION_EVENT_TYPE_DATE_RELATIVE';
346
+ base_model: string;
347
+ date_relative_trigger: WorkflowDateRelativeTriggerConfig;
348
+ }
349
+
350
+ export interface WorkflowScheduledTrigger extends WorkflowTriggerBase {
351
+ automation_event_type: 'AUTOMATION_EVENT_TYPE_SCHEDULED';
352
+ scheduled_trigger: WorkflowScheduledTriggerConfig;
353
+ }
354
+
355
+ export interface WorkflowManualTrigger extends WorkflowTriggerBase {
356
+ automation_event_type: 'AUTOMATION_EVENT_TYPE_MANUAL';
357
+ }
358
+
359
+ export interface WorkflowIntegrationSenderFilter {
360
+ allowed_supergroup_id?: string;
361
+ }
362
+
363
+ export type WorkflowIntegrationTriggerProvider =
364
+ | 'INTEGRATION_TRIGGER_PROVIDER_SLACK'
365
+ | 'INTEGRATION_TRIGGER_PROVIDER_EMAIL';
366
+
367
+ export type WorkflowSlackTriggerEventType =
368
+ | 'SLACK_TRIGGER_EVENT_TYPE_MESSAGE_POSTED'
369
+ | 'SLACK_TRIGGER_EVENT_TYPE_REACTION_ADDED';
370
+
371
+ export interface WorkflowSlackTriggerConfig {
372
+ event_type: WorkflowSlackTriggerEventType;
373
+ channel_ids: string[];
374
+ allowed_sender_group_id?: string;
375
+ message_text_regex?: string;
376
+ reaction_names?: string[];
377
+ include_thread_replies?: boolean;
378
+ has_attachment?: boolean;
379
+ sender_filter?: WorkflowIntegrationSenderFilter;
380
+ workspace_owner_id?: string;
381
+ }
382
+
383
+ export interface WorkflowEmailTriggerConfig {
384
+ recipient_email: string;
385
+ subject_contains?: string;
386
+ body_contains?: string;
387
+ has_attachment?: boolean;
388
+ sender_filter?: WorkflowIntegrationSenderFilter;
389
+ }
390
+
391
+ export interface WorkflowSlackIntegrationTrigger {
392
+ provider: 'INTEGRATION_TRIGGER_PROVIDER_SLACK';
393
+ slack_trigger: WorkflowSlackTriggerConfig;
394
+ email_trigger?: never;
395
+ }
396
+
397
+ export interface WorkflowEmailIntegrationTrigger {
398
+ provider: 'INTEGRATION_TRIGGER_PROVIDER_EMAIL';
399
+ email_trigger: WorkflowEmailTriggerConfig;
400
+ slack_trigger?: never;
401
+ }
402
+
403
+ export type WorkflowIntegrationTriggerConfig =
404
+ | WorkflowSlackIntegrationTrigger
405
+ | WorkflowEmailIntegrationTrigger;
406
+
407
+ export interface WorkflowIntegrationTrigger extends WorkflowTriggerBase {
408
+ automation_event_type: 'AUTOMATION_EVENT_TYPE_INTEGRATION';
409
+ integration_trigger: WorkflowIntegrationTriggerConfig;
410
+ }
411
+
412
+ export interface WorkflowWebhookConfig {
413
+ allowed_ips?: string[];
414
+ payload_schema?: Record<string, unknown>;
415
+ }
416
+
417
+ export interface WorkflowWebhookDefinition {
418
+ webhook_trigger_id?: string;
419
+ production_webhook_url?: string;
420
+ test_webhook_url?: string;
421
+ config?: WorkflowWebhookConfig;
422
+ auth_token_id?: string;
423
+ }
424
+
425
+ export interface WorkflowWebhookTrigger extends WorkflowTriggerBase {
426
+ automation_event_type: 'AUTOMATION_EVENT_TYPE_WEBHOOK';
427
+ webhook_trigger: WorkflowWebhookDefinition;
428
+ }
429
+
430
+ export type WorkflowTrigger =
431
+ | WorkflowBusinessTrigger
432
+ | WorkflowRecordChangeTrigger
433
+ | WorkflowDateRelativeTrigger
434
+ | WorkflowScheduledTrigger
435
+ | WorkflowManualTrigger
436
+ | WorkflowIntegrationTrigger
437
+ | WorkflowWebhookTrigger;
438
+
439
+ export interface WorkflowDeletionIdentifier {
440
+ definitionId: string;
441
+ }
442
+
443
+ export interface WorkflowProps {
444
+ /**
445
+ * Stable workflow definition id. Auto-generated when omitted.
446
+ */
447
+ definitionId?: string;
448
+ /**
449
+ * Workflow title shown to admins.
450
+ */
451
+ title: string;
452
+ /**
453
+ * Short workflow description shown to admins.
454
+ */
455
+ description?: string;
456
+ /**
457
+ * The function this workflow runs. Pass a live {@link ManifestFunction} reference,
458
+ * or a raw api_name string for functions defined outside this manifest.
459
+ *
460
+ */
461
+ function?: ManifestFunction | string;
462
+ /**
463
+ * Trigger definition in the workflows v2 proto JSON shape.
464
+ */
465
+ trigger: WorkflowTrigger;
466
+ recipeId?: string;
467
+ versionStatus?: WorkflowVersionStatus;
468
+ triggerStatus?: WorkflowTriggerStatus;
469
+ graph?: WorkflowGraph;
470
+ functionsInferredGraph?: WorkflowGraph;
471
+ customVariables?: Record<string, WorkflowCustomVariable>;
472
+ functionApiNamesByOldId?: Record<string, string>;
473
+ categories?: WorkflowCategory[];
474
+ }
475
+
476
+ function assertNonEmptyString(value: unknown, path: string): void {
477
+ if (typeof value !== 'string' || !value.trim()) throw new Error(`${path} is required.`);
478
+ }
479
+
480
+ function assertIntegerInRange(value: unknown, min: number, max: number, path: string): void {
481
+ if (!Number.isInteger(value) || (value as number) < min || (value as number) > max) {
482
+ throw new Error(`${path} must be an integer from ${min} to ${max}.`);
483
+ }
484
+ }
485
+
486
+ function asRecord(value: unknown, path: string): Record<string, unknown> {
487
+ if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error(`${path} is required.`);
488
+ return value as Record<string, unknown>;
489
+ }
490
+
491
+ function assertOnly(
492
+ value: Record<string, unknown>,
493
+ keys: readonly string[],
494
+ selected: string,
495
+ path: string,
496
+ ): void {
497
+ if (value[selected] == null) throw new Error(`${path}.${selected} is required.`);
498
+ for (const key of keys) {
499
+ if (key !== selected && value[key] !== undefined)
500
+ throw new Error(`${path}.${key} is not allowed with ${selected}.`);
501
+ }
502
+ }
503
+
504
+ function validateTime(value: unknown, path: string): void {
505
+ const time = asRecord(value, path);
506
+ assertIntegerInRange(time['hour_of_day'], 0, 23, `${path}.hour_of_day`);
507
+ if (time['minute_of_hour'] !== undefined)
508
+ assertIntegerInRange(time['minute_of_hour'], 0, 59, `${path}.minute_of_hour`);
509
+ }
510
+
511
+ function validateRoleStates(value: unknown, path: string): void {
512
+ if (value === undefined) return;
513
+ const allowed = [
514
+ 'ROLE_STATE_INIT',
515
+ 'ROLE_STATE_HIRED',
516
+ 'ROLE_STATE_ACCEPTED',
517
+ 'ROLE_STATE_ACTIVE',
518
+ 'ROLE_STATE_TERMINATED',
519
+ ];
520
+ if (!Array.isArray(value)) throw new Error(`${path} must be an array of role states.`);
521
+ value.forEach((state: unknown, index: number) => {
522
+ if (typeof state !== 'string' || !allowed.includes(state))
523
+ throw new Error(`${path}[${index}] is invalid.`);
524
+ });
525
+ }
526
+
527
+ const scheduleKeys = {
528
+ SCHEDULED_TRIGGER_FREQUENCY_TYPE_DAILY: 'daily_schedule',
529
+ SCHEDULED_TRIGGER_FREQUENCY_TYPE_WEEKDAY_DAILY: 'weekday_schedule',
530
+ SCHEDULED_TRIGGER_FREQUENCY_TYPE_WEEKLY: 'weekly_schedule',
531
+ SCHEDULED_TRIGGER_FREQUENCY_TYPE_BIWEEKLY: 'bi_weekly_schedule',
532
+ SCHEDULED_TRIGGER_FREQUENCY_TYPE_MONTHLY: 'monthly_schedule',
533
+ SCHEDULED_TRIGGER_FREQUENCY_TYPE_MONTHLY_ON_DAY_OF_WEEK: 'monthly_on_day_of_week_schedule',
534
+ SCHEDULED_TRIGGER_FREQUENCY_TYPE_ONE_TIME: 'one_time_schedule',
535
+ SCHEDULED_TRIGGER_FREQUENCY_TYPE_QUARTERLY: 'quarterly_schedule',
536
+ SCHEDULED_TRIGGER_FREQUENCY_TYPE_ANNUALLY: 'annual_schedule',
537
+ SCHEDULED_TRIGGER_FREQUENCY_TYPE_CUSTOM: 'custom_schedule',
538
+ SCHEDULED_TRIGGER_FREQUENCY_TYPE_CRON: 'cron_schedule',
539
+ } as const;
540
+
541
+ function validateSchedule(input: unknown, path: string): void {
542
+ const config = asRecord(input, path);
543
+ const frequency = config['frequency'];
544
+ const key =
545
+ typeof frequency === 'string' ? scheduleKeys[frequency as keyof typeof scheduleKeys] : undefined;
546
+ if (!key) throw new Error(`${path}.frequency is invalid.`);
547
+ assertNonEmptyString(config['timezone'], `${path}.timezone`);
548
+ try {
549
+ new Intl.DateTimeFormat('en', { timeZone: config['timezone'] as string });
550
+ } catch {
551
+ throw new Error(`${path}.timezone must be an IANA timezone.`);
552
+ }
553
+ assertOnly(config, Object.values(scheduleKeys), key, path);
554
+ validateRoleStates(config['supported_role_states'], `${path}.supported_role_states`);
555
+ const schedule = asRecord(config[key], `${path}.${key}`);
556
+ const p = `${path}.${key}`;
557
+ if (key === 'cron_schedule') {
558
+ if (
559
+ typeof schedule['cron_expression'] !== 'string' ||
560
+ schedule['cron_expression'].trim().split(/\s+/).length !== 5
561
+ ) {
562
+ throw new Error(`${p}.cron_expression must have five fields.`);
563
+ }
564
+ return;
565
+ }
566
+ if (key === 'custom_schedule') {
567
+ const customKeys = ['daily_schedule', 'weekly_schedule', 'monthly_schedule', 'annual_schedule'];
568
+ const unitKey: Record<string, string | undefined> = {
569
+ CUSTOM_SCHEDULE_TRIGGER_FREQUENCY_UNIT_DAYS: 'daily_schedule',
570
+ CUSTOM_SCHEDULE_TRIGGER_FREQUENCY_UNIT_WEEKS: 'weekly_schedule',
571
+ CUSTOM_SCHEDULE_TRIGGER_FREQUENCY_UNIT_MONTHS: 'monthly_schedule',
572
+ CUSTOM_SCHEDULE_TRIGGER_FREQUENCY_UNIT_YEARS: 'annual_schedule',
573
+ CUSTOM_SCHEDULE_TRIGGER_FREQUENCY_UNIT_HOURS: undefined,
574
+ CUSTOM_SCHEDULE_TRIGGER_FREQUENCY_UNIT_MINUTES: undefined,
575
+ };
576
+ const unit = schedule['custom_schedule_frequency'];
577
+ if (typeof unit !== 'string' || !Object.prototype.hasOwnProperty.call(unitKey, unit))
578
+ throw new Error(`${p}.custom_schedule_frequency is invalid.`);
579
+ assertIntegerInRange(
580
+ schedule['custom_schedule_frequency_value'],
581
+ 1,
582
+ 2147483647,
583
+ `${p}.custom_schedule_frequency_value`,
584
+ );
585
+ if (schedule['hour_of_day'] !== undefined)
586
+ assertIntegerInRange(schedule['hour_of_day'], 0, 23, `${p}.hour_of_day`);
587
+ if (schedule['minute_of_hour'] !== undefined)
588
+ assertIntegerInRange(schedule['minute_of_hour'], 0, 59, `${p}.minute_of_hour`);
589
+ const nested = unitKey[unit];
590
+ if (nested) {
591
+ assertOnly(schedule, customKeys, nested, p);
592
+ const inner = asRecord(schedule[nested], `${p}.${nested}`);
593
+ if (schedule['hour_of_day'] !== undefined && schedule['hour_of_day'] !== inner['hour_of_day']) {
594
+ throw new Error(`${p}.hour_of_day must match ${nested}.hour_of_day.`);
595
+ }
596
+ if (
597
+ schedule['minute_of_hour'] !== undefined &&
598
+ schedule['minute_of_hour'] !== (inner['minute_of_hour'] ?? 0)
599
+ ) {
600
+ throw new Error(`${p}.minute_of_hour must match ${nested}.minute_of_hour.`);
601
+ }
602
+ validateScheduleFields(inner, `${p}.${nested}`, nested);
603
+ } else {
604
+ for (const other of customKeys)
605
+ if (schedule[other] !== undefined) throw new Error(`${p}.${other} is not allowed.`);
606
+ if (
607
+ unit === 'CUSTOM_SCHEDULE_TRIGGER_FREQUENCY_UNIT_MINUTES' &&
608
+ ((schedule['custom_schedule_frequency_value'] as number) < 15 ||
609
+ (schedule['custom_schedule_frequency_value'] as number) % 15 !== 0)
610
+ ) {
611
+ throw new Error(`${p}.custom_schedule_frequency_value must be a multiple of 15 and at least 15.`);
612
+ }
613
+ }
614
+ return;
615
+ }
616
+ validateScheduleFields(schedule, p, key);
617
+ }
618
+
619
+ function validateScheduleFields(input: unknown, path: string, key: string): void {
620
+ const schedule = asRecord(input, path);
621
+ validateTime(schedule, path);
622
+ if (key === 'weekly_schedule' || key === 'bi_weekly_schedule') {
623
+ const days = schedule['day_of_week'];
624
+ if (!Array.isArray(days) || !days.length) throw new Error(`${path}.day_of_week is required.`);
625
+ days.forEach((day: unknown, i: number) => assertIntegerInRange(day, 0, 6, `${path}.day_of_week[${i}]`));
626
+ }
627
+ if (key === 'monthly_on_day_of_week_schedule') {
628
+ assertIntegerInRange(schedule['day_of_week'], 0, 6, `${path}.day_of_week`);
629
+ assertIntegerInRange(schedule['day_of_month'], 1, 5, `${path}.day_of_month`);
630
+ } else if (
631
+ ['one_time_schedule', 'monthly_schedule', 'quarterly_schedule', 'annual_schedule'].includes(key)
632
+ ) {
633
+ assertIntegerInRange(schedule['day_of_month'], 1, 31, `${path}.day_of_month`);
634
+ }
635
+ if (['one_time_schedule', 'quarterly_schedule', 'annual_schedule'].includes(key)) {
636
+ assertIntegerInRange(
637
+ schedule['month_of_year'],
638
+ 1,
639
+ key === 'quarterly_schedule' ? 3 : 12,
640
+ `${path}.month_of_year`,
641
+ );
642
+ }
643
+ if (schedule['start_month'] !== undefined)
644
+ assertIntegerInRange(schedule['start_month'], 1, 12, `${path}.start_month`);
645
+ if (key === 'one_time_schedule') assertIntegerInRange(schedule['year'], 1, 9999, `${path}.year`);
646
+ }
647
+
648
+ function validateWorkflowTrigger(trigger: WorkflowTrigger): void {
649
+ const path = 'Workflow.trigger';
650
+ const t = asRecord(trigger, path);
651
+ const variants: Record<string, string | undefined> = {
652
+ AUTOMATION_EVENT_TYPE_BUSINESS: undefined,
653
+ AUTOMATION_EVENT_TYPE_RECORD_CHANGE: 'record_change_operation_type',
654
+ AUTOMATION_EVENT_TYPE_DATE_RELATIVE: 'date_relative_trigger',
655
+ AUTOMATION_EVENT_TYPE_SCHEDULED: 'scheduled_trigger',
656
+ AUTOMATION_EVENT_TYPE_INTEGRATION: 'integration_trigger',
657
+ AUTOMATION_EVENT_TYPE_WEBHOOK: 'webhook_trigger',
658
+ AUTOMATION_EVENT_TYPE_MANUAL: undefined,
659
+ };
660
+ const kind = t['automation_event_type'];
661
+ if (typeof kind !== 'string' || !Object.prototype.hasOwnProperty.call(variants, kind))
662
+ throw new Error(`${path}.automation_event_type is invalid.`);
663
+ const ownFields = [
664
+ 'event_name',
665
+ 'business_event_trigger',
666
+ 'record_change_operation_type',
667
+ 'date_relative_trigger',
668
+ 'scheduled_trigger',
669
+ 'integration_trigger',
670
+ 'webhook_trigger',
671
+ ];
672
+ const selected =
673
+ kind === 'AUTOMATION_EVENT_TYPE_BUSINESS' ? ['event_name', 'business_event_trigger']
674
+ : variants[kind] ? [variants[kind]]
675
+ : [];
676
+ for (const field of ownFields) {
677
+ if (!selected.includes(field) && t[field] !== undefined)
678
+ throw new Error(`${path}.${field} is not allowed with ${kind}.`);
679
+ }
680
+ if (kind === 'AUTOMATION_EVENT_TYPE_BUSINESS') assertNonEmptyString(t['event_name'], `${path}.event_name`);
681
+ else if (variants[kind] && t[variants[kind]] == null)
682
+ throw new Error(`${path}.${variants[kind]} is required.`);
683
+ if (t['execution_rate_limit'] !== undefined) {
684
+ assertIntegerInRange(
685
+ asRecord(t['execution_rate_limit'], `${path}.execution_rate_limit`)['execution_limit'],
686
+ 100000,
687
+ 200000,
688
+ `${path}.execution_rate_limit.execution_limit`,
689
+ );
690
+ }
691
+ if (kind === 'AUTOMATION_EVENT_TYPE_DATE_RELATIVE') {
692
+ const d = asRecord(t['date_relative_trigger'], `${path}.date_relative_trigger`);
693
+ const p = `${path}.date_relative_trigger`;
694
+ assertNonEmptyString(t['base_model'], `${path}.base_model`);
695
+ validateTime(d, p);
696
+ validateRoleStates(d['supported_role_states'], `${p}.supported_role_states`);
697
+ const field = d['base_model_date_field'];
698
+ const rql = d['base_model_date_field_rql_path'];
699
+ if (field === undefined && rql === undefined)
700
+ throw new Error(`${p}.base_model_date_field or ${p}.base_model_date_field_rql_path is required.`);
701
+ if (field !== undefined && rql !== undefined)
702
+ throw new Error(`${p}.base_model_date_field_rql_path conflicts with base_model_date_field.`);
703
+ if (field !== undefined && field !== 'start_date' && field !== 'end_date')
704
+ throw new Error(`${p}.base_model_date_field is invalid.`);
705
+ if (rql !== undefined) assertNonEmptyString(rql, `${p}.base_model_date_field_rql_path`);
706
+ const operator = d['date_range_operator'];
707
+ const between = operator === 'DATE_RELATIVE_TRIGGER_OPERATOR_TYPE_BETWEEN';
708
+ if (
709
+ operator !== undefined &&
710
+ ![
711
+ 'DATE_RELATIVE_TRIGGER_OPERATOR_TYPE_EQ',
712
+ 'DATE_RELATIVE_TRIGGER_OPERATOR_TYPE_WITHIN',
713
+ 'DATE_RELATIVE_TRIGGER_OPERATOR_TYPE_BETWEEN',
714
+ ].includes(operator as string)
715
+ )
716
+ throw new Error(`${p}.date_range_operator is invalid.`);
717
+ if (!Array.isArray(d['operands']) || d['operands'].length !== (between ? 2 : 1))
718
+ throw new Error(`${p}.operands must contain exactly ${between ? 2 : 1} operand${between ? 's' : ''}.`);
719
+ const days = d['operands'].map((operand: unknown, i: number) => {
720
+ const value = asRecord(
721
+ asRecord(operand, `${p}.operands[${i}]`)['time_delta'],
722
+ `${p}.operands[${i}].time_delta`,
723
+ )['days'];
724
+ assertIntegerInRange(value, -2147483648, 2147483647, `${p}.operands[${i}].time_delta.days`);
725
+ return value as number;
726
+ });
727
+ if (between && days[0]! * days[1]! < 0) throw new Error(`${p}.operands must have the same sign.`);
728
+ if (
729
+ d['trigger_on_anniversary'] &&
730
+ operator !== undefined &&
731
+ operator !== 'DATE_RELATIVE_TRIGGER_OPERATOR_TYPE_EQ'
732
+ )
733
+ throw new Error(`${p}.trigger_on_anniversary requires EQ.`);
734
+ if (d['trigger_on_anniversary'] && d['should_trigger_exactly_once_per_trigger_object'])
735
+ throw new Error(
736
+ `${p}.should_trigger_exactly_once_per_trigger_object conflicts with trigger_on_anniversary.`,
737
+ );
738
+ }
739
+ if (kind === 'AUTOMATION_EVENT_TYPE_SCHEDULED')
740
+ validateSchedule(t['scheduled_trigger'], `${path}.scheduled_trigger`);
741
+ if (kind === 'AUTOMATION_EVENT_TYPE_INTEGRATION') {
742
+ const integration = asRecord(t['integration_trigger'], `${path}.integration_trigger`);
743
+ const p = `${path}.integration_trigger`;
744
+ const selectedPayload =
745
+ integration['provider'] === 'INTEGRATION_TRIGGER_PROVIDER_SLACK' ? 'slack_trigger'
746
+ : integration['provider'] === 'INTEGRATION_TRIGGER_PROVIDER_EMAIL' ? 'email_trigger'
747
+ : undefined;
748
+ if (!selectedPayload) throw new Error(`${p}.provider is invalid.`);
749
+ assertOnly(integration, ['slack_trigger', 'email_trigger'], selectedPayload, p);
750
+ const payload = asRecord(integration[selectedPayload], `${p}.${selectedPayload}`);
751
+ const q = `${p}.${selectedPayload}`;
752
+ if (selectedPayload === 'email_trigger')
753
+ assertNonEmptyString(payload['recipient_email'], `${q}.recipient_email`);
754
+ else {
755
+ if (
756
+ !['SLACK_TRIGGER_EVENT_TYPE_MESSAGE_POSTED', 'SLACK_TRIGGER_EVENT_TYPE_REACTION_ADDED'].includes(
757
+ payload['event_type'] as string,
758
+ )
759
+ )
760
+ throw new Error(`${q}.event_type is required.`);
761
+ const channels = payload['channel_ids'];
762
+ if (!Array.isArray(channels) || !channels.length) throw new Error(`${q}.channel_ids is required.`);
763
+ channels.forEach((channel: unknown, i: number) =>
764
+ assertNonEmptyString(channel, `${q}.channel_ids[${i}]`),
765
+ );
766
+ if (typeof payload['message_text_regex'] === 'string' && payload['message_text_regex'].length > 512)
767
+ throw new Error(`${q}.message_text_regex must be at most 512 characters.`);
768
+ if (
769
+ payload['event_type'] === 'SLACK_TRIGGER_EVENT_TYPE_MESSAGE_POSTED' &&
770
+ payload['reaction_names'] !== undefined
771
+ )
772
+ throw new Error(`${q}.reaction_names is not allowed for MESSAGE_POSTED.`);
773
+ if (payload['event_type'] === 'SLACK_TRIGGER_EVENT_TYPE_REACTION_ADDED') {
774
+ if (payload['message_text_regex'] !== undefined)
775
+ throw new Error(`${q}.message_text_regex is not allowed for REACTION_ADDED.`);
776
+ if (payload['include_thread_replies'] === true)
777
+ throw new Error(`${q}.include_thread_replies is not allowed for REACTION_ADDED.`);
778
+ if (payload['has_attachment'] === true)
779
+ throw new Error(`${q}.has_attachment is not allowed for REACTION_ADDED.`);
780
+ if (payload['reaction_names'] !== undefined) {
781
+ if (!Array.isArray(payload['reaction_names']))
782
+ throw new Error(`${q}.reaction_names must be an array.`);
783
+ payload['reaction_names'].forEach((name: unknown, i: number) => {
784
+ assertNonEmptyString(name, `${q}.reaction_names[${i}]`);
785
+ if ((name as string).includes(':'))
786
+ throw new Error(`${q}.reaction_names[${i}] must not contain colons.`);
787
+ });
788
+ }
789
+ }
790
+ }
791
+ }
792
+ }
793
+
794
+ function validateWorkflowGraph(graph: WorkflowGraph, path: string): void {
795
+ if (!graph || !graph.nodes || typeof graph.nodes !== 'object' || Array.isArray(graph.nodes))
796
+ throw new Error(`${path}.nodes is required.`);
797
+ const nodes = graph.nodes;
798
+ const ids = Object.keys(nodes);
799
+ if (!ids.length && graph.root_node_id === '') return;
800
+ assertNonEmptyString(graph.root_node_id, `${path}.root_node_id`);
801
+ if (!Object.prototype.hasOwnProperty.call(nodes, graph.root_node_id))
802
+ throw new Error(`${path}.root_node_id must reference a node.`);
803
+ const visited = new Set<string>();
804
+ const active = new Set<string>();
805
+ const visit = (id: string): void => {
806
+ if (active.has(id)) throw new Error(`${path}.nodes.${id} contains a cycle.`);
807
+ if (visited.has(id)) return;
808
+ active.add(id);
809
+ const node = nodes[id];
810
+ if (!node) throw new Error(`${path}.nodes.${id} is required.`);
811
+ const p = `${path}.nodes.${id}`;
812
+ assertNonEmptyString(id, `${path}.nodes key`);
813
+ assertNonEmptyString(node?.config_id, `${p}.config_id`);
814
+ const logic = (['fork', 'loop', 'group', 'switch'] as const).filter((key) => node[key] !== undefined);
815
+ if (logic.length > 1) throw new Error(`${p}.${logic[1]} conflicts with ${logic[0]}.`);
816
+ if (
817
+ node.processing_context?.function_context !== undefined &&
818
+ node.processing_context?.digest_context !== undefined
819
+ ) {
820
+ throw new Error(`${p}.processing_context contains conflicting contexts.`);
821
+ }
822
+ const edge = (next: unknown, edgePath: string): void => {
823
+ assertNonEmptyString(next, edgePath);
824
+ if (typeof next !== 'string' || !Object.prototype.hasOwnProperty.call(nodes, next))
825
+ throw new Error(`${edgePath} must reference a node.`);
826
+ visit(next);
827
+ };
828
+ if (node.next !== undefined) edge(node.next, `${p}.next`);
829
+ if (node.fork !== undefined) {
830
+ const fork = asRecord(node.fork, `${p}.fork`);
831
+ if (!Array.isArray(fork['branches'])) throw new Error(`${p}.fork.branches is required.`);
832
+ fork['branches'].forEach((branch: unknown, i: number) => edge(branch, `${p}.fork.branches[${i}]`));
833
+ }
834
+ if (node.loop !== undefined) edge(asRecord(node.loop, `${p}.loop`)['inner_node'], `${p}.loop.inner_node`);
835
+ if (node.group !== undefined)
836
+ edge(asRecord(node.group, `${p}.group`)['inner_node'], `${p}.group.inner_node`);
837
+ if (node.switch !== undefined) {
838
+ const sw = asRecord(node.switch, `${p}.switch`);
839
+ if (!Array.isArray(sw['branches'])) throw new Error(`${p}.switch.branches is required.`);
840
+ sw['branches'].forEach((branch: unknown, i: number) => {
841
+ const choice = asRecord(branch, `${p}.switch.branches[${i}]`);
842
+ if (choice['node_id'] !== undefined) edge(choice['node_id'], `${p}.switch.branches[${i}].node_id`);
843
+ });
844
+ if (sw['default_node'] !== undefined) edge(sw['default_node'], `${p}.switch.default_node`);
845
+ }
846
+ active.delete(id);
847
+ visited.add(id);
848
+ };
849
+ visit(graph.root_node_id);
850
+ for (const id of ids)
851
+ if (!visited.has(id)) throw new Error(`${path}.nodes.${id} is unreachable from root_node_id.`);
852
+ }
853
+
854
+ /** Defines a workflow using a trigger, function shorthand, or raw graph. */
855
+ export class Workflow {
856
+ static readonly componentType = 'WORKFLOW_DEFINITION' as const;
857
+
858
+ static toDeletionIdentifier(identifier: WorkflowDeletionIdentifier | string): ManifestComponentDeletion {
859
+ const definitionId = typeof identifier === 'string' ? identifier : identifier.definitionId;
860
+ return {
861
+ type: Workflow.componentType,
862
+ definition_id: definitionId,
863
+ };
864
+ }
865
+
866
+ private readonly _definitionId: string;
867
+ private readonly _title: string;
868
+ private readonly _description: string | undefined;
869
+ private readonly _functionApiName: string | undefined;
870
+ private readonly _trigger: WorkflowTrigger;
871
+ private readonly _props: WorkflowProps;
872
+
873
+ constructor(scope: ManifestScope, props: WorkflowProps) {
874
+ this._definitionId = props.definitionId ?? generateId('workflow');
875
+ this._title = props.title;
876
+ this._description = props.description;
877
+ this._functionApiName =
878
+ typeof props.function === 'string' ? props.function : props.function?.getApiName();
879
+ this._trigger = props.trigger;
880
+ this._props = props;
881
+ this._validate();
882
+ scope._register(this);
883
+ }
884
+
885
+ private _validate(): void {
886
+ assertNonEmptyString(this._title, 'Workflow.workflow_meta.name');
887
+ if (this._props.function !== undefined)
888
+ assertNonEmptyString(this._functionApiName, 'Workflow.function_api_name');
889
+ validateWorkflowTrigger(this._trigger);
890
+ if (this._props.graph !== undefined) validateWorkflowGraph(this._props.graph, 'Workflow.graph');
891
+ if (this._props.functionsInferredGraph !== undefined)
892
+ validateWorkflowGraph(this._props.functionsInferredGraph, 'Workflow.functions_inferred_graph');
893
+ for (const [id, variable] of Object.entries(this._props.customVariables ?? {})) {
894
+ assertNonEmptyString(id, 'Workflow.custom_variables key');
895
+ assertNonEmptyString(variable?.name, `Workflow.custom_variables.${id}.name`);
896
+ }
897
+ for (const [id, apiName] of Object.entries(this._props.functionApiNamesByOldId ?? {})) {
898
+ assertNonEmptyString(id, 'Workflow.function_api_names_by_old_id key');
899
+ assertNonEmptyString(apiName, `Workflow.function_api_names_by_old_id.${id}`);
900
+ }
901
+ if (this._functionApiName !== undefined && this._props.graph !== undefined) {
902
+ const graph = this._props.graph;
903
+ const root = graph.nodes[graph.root_node_id];
904
+ const context = root?.processing_context?.['function_context'];
905
+ if (
906
+ Object.keys(graph.nodes).length !== 1 ||
907
+ root?.config_id !== 'RunFunction' ||
908
+ root.next !== undefined ||
909
+ !context ||
910
+ typeof context !== 'object' ||
911
+ !('function_api_name' in context) ||
912
+ context.function_api_name !== this._functionApiName
913
+ ) {
914
+ throw new Error('Workflow.graph conflicts with function_api_name shorthand.');
915
+ }
916
+ }
917
+ if (
918
+ (this._props.versionStatus ?? 'VERSION_STATUS_DRAFT') === 'VERSION_STATUS_DRAFT' &&
919
+ (this._props.triggerStatus ?? 'TRIGGER_STATUS_INACTIVE') !== 'TRIGGER_STATUS_INACTIVE'
920
+ ) {
921
+ throw new Error('Workflow.trigger_status must be inactive for a draft workflow.');
922
+ }
923
+ }
924
+
925
+ getDefinitionId(): string {
926
+ return this._definitionId;
927
+ }
928
+
929
+ toDict(): Record<string, unknown> {
930
+ this._validate();
931
+ const workflowMeta: Record<string, unknown> = { name: this._title };
932
+ if (this._description !== undefined) workflowMeta['description'] = this._description;
933
+ if (this._props.recipeId !== undefined) workflowMeta['recipe_id'] = this._props.recipeId;
934
+ const component: Record<string, unknown> = {
935
+ type: Workflow.componentType,
936
+ definition_id: this._definitionId,
937
+ version_status: this._props.versionStatus ?? 'VERSION_STATUS_DRAFT',
938
+ trigger_status: this._props.triggerStatus ?? 'TRIGGER_STATUS_INACTIVE',
939
+ trigger: this._trigger,
940
+ workflow_meta: workflowMeta,
941
+ };
942
+ if (this._functionApiName !== undefined) component['function_api_name'] = this._functionApiName;
943
+ if (this._props.graph !== undefined) component['graph'] = this._props.graph;
944
+ if (this._props.functionsInferredGraph !== undefined)
945
+ component['functions_inferred_graph'] = this._props.functionsInferredGraph;
946
+ if (this._props.customVariables !== undefined)
947
+ component['custom_variables'] = this._props.customVariables;
948
+ if (this._props.functionApiNamesByOldId !== undefined)
949
+ component['function_api_names_by_old_id'] = this._props.functionApiNamesByOldId;
950
+ if (this._props.categories !== undefined) component['categories'] = this._props.categories;
951
+ return component;
952
+ }
953
+ }