@automate.ax/integration-contracts 0.87.7 → 0.88.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,755 @@
1
+ import * as z from "zod"
2
+ import { CLOSE_OBJECT_SCHEMA } from "./api"
3
+ import {
4
+ CLOSE_CALL_SCHEMA,
5
+ CLOSE_COMMENT_SCHEMA,
6
+ CLOSE_DATE_SCHEMA,
7
+ CLOSE_CONTACT_SCHEMA,
8
+ CLOSE_EMAIL_SCHEMA,
9
+ CLOSE_LEAD_SCHEMA,
10
+ CLOSE_MEETING_SCHEMA,
11
+ CLOSE_OPPORTUNITY_SCHEMA,
12
+ CLOSE_SEQUENCE_SUBSCRIPTION_SCHEMA,
13
+ CLOSE_SMS_SCHEMA,
14
+ CLOSE_TASK_SCHEMA,
15
+ } from "./schemas"
16
+
17
+ export const CLOSE_WEBHOOK_EVENT_SELECTIONS = [
18
+ ["lead", "created"],
19
+ ["lead", "updated"],
20
+ ["lead", "deleted"],
21
+ ["lead", "merged"],
22
+ ["contact", "created"],
23
+ ["contact", "updated"],
24
+ ["contact", "deleted"],
25
+ ["opportunity", "created"],
26
+ ["opportunity", "updated"],
27
+ ["opportunity", "deleted"],
28
+ ["task.lead", "created"],
29
+ ["task.lead", "updated"],
30
+ ["task.lead", "deleted"],
31
+ ["task.lead", "completed"],
32
+ ["task.incoming_email", "created"],
33
+ ["task.incoming_email", "updated"],
34
+ ["task.incoming_email", "deleted"],
35
+ ["task.incoming_email", "completed"],
36
+ ["task.email_followup", "created"],
37
+ ["task.email_followup", "updated"],
38
+ ["task.email_followup", "deleted"],
39
+ ["task.email_followup", "completed"],
40
+ ["task.missed_call", "created"],
41
+ ["task.missed_call", "updated"],
42
+ ["task.missed_call", "deleted"],
43
+ ["task.missed_call", "completed"],
44
+ ["task.answered_detached_call", "created"],
45
+ ["task.answered_detached_call", "updated"],
46
+ ["task.answered_detached_call", "deleted"],
47
+ ["task.answered_detached_call", "completed"],
48
+ ["task.voicemail", "created"],
49
+ ["task.voicemail", "updated"],
50
+ ["task.voicemail", "deleted"],
51
+ ["task.voicemail", "completed"],
52
+ ["task.opportunity_due", "created"],
53
+ ["task.opportunity_due", "updated"],
54
+ ["task.opportunity_due", "deleted"],
55
+ ["task.opportunity_due", "completed"],
56
+ ["task.incoming_sms", "created"],
57
+ ["task.incoming_sms", "updated"],
58
+ ["task.incoming_sms", "deleted"],
59
+ ["task.incoming_sms", "completed"],
60
+ ["task.outbound_call", "created"],
61
+ ["task.outbound_call", "updated"],
62
+ ["task.outbound_call", "deleted"],
63
+ ["task.outbound_call", "completed"],
64
+ ["activity.email", "created"],
65
+ ["activity.email", "updated"],
66
+ ["activity.email", "deleted"],
67
+ ["activity.email", "sent"],
68
+ ["activity.email_thread", "created"],
69
+ ["activity.email_thread", "updated"],
70
+ ["activity.email_thread", "deleted"],
71
+ ["unsubscribed_email", "created"],
72
+ ["unsubscribed_email", "deleted"],
73
+ ["activity.call", "created"],
74
+ ["activity.call", "updated"],
75
+ ["activity.call", "deleted"],
76
+ ["activity.call", "answered"],
77
+ ["activity.call", "completed"],
78
+ ["activity.sms", "created"],
79
+ ["activity.sms", "updated"],
80
+ ["activity.sms", "deleted"],
81
+ ["activity.sms", "sent"],
82
+ ["activity.whatsapp_message", "created"],
83
+ ["activity.whatsapp_message", "updated"],
84
+ ["activity.whatsapp_message", "deleted"],
85
+ ["activity.note", "created"],
86
+ ["activity.note", "updated"],
87
+ ["activity.note", "deleted"],
88
+ ["activity.meeting", "created"],
89
+ ["activity.meeting", "updated"],
90
+ ["activity.meeting", "deleted"],
91
+ ["activity.meeting", "scheduled"],
92
+ ["activity.meeting", "started"],
93
+ ["activity.meeting", "completed"],
94
+ ["activity.meeting", "canceled"],
95
+ ["activity.form_submission", "created"],
96
+ ["activity.form_submission", "updated"],
97
+ ["activity.form_submission", "deleted"],
98
+ ["activity.lead_status_change", "created"],
99
+ ["activity.lead_status_change", "updated"],
100
+ ["activity.lead_status_change", "deleted"],
101
+ ["activity.opportunity_status_change", "created"],
102
+ ["activity.opportunity_status_change", "updated"],
103
+ ["activity.opportunity_status_change", "deleted"],
104
+ ["activity.task_completed", "created"],
105
+ ["activity.task_completed", "deleted"],
106
+ ["activity.lead_merge", "created"],
107
+ ["activity.lead_merge", "updated"],
108
+ ["activity.lead_merge", "deleted"],
109
+ ["import", "created"],
110
+ ["import", "updated"],
111
+ ["import", "completed"],
112
+ ["import", "reverting"],
113
+ ["import", "reverted"],
114
+ ["export.lead", "created"],
115
+ ["export.lead", "updated"],
116
+ ["export.lead", "completed"],
117
+ ["export.opportunity", "created"],
118
+ ["export.opportunity", "updated"],
119
+ ["export.opportunity", "completed"],
120
+ ["bulk_action.delete", "created"],
121
+ ["bulk_action.delete", "updated"],
122
+ ["bulk_action.delete", "completed"],
123
+ ["bulk_action.delete", "paused"],
124
+ ["bulk_action.edit", "created"],
125
+ ["bulk_action.edit", "updated"],
126
+ ["bulk_action.edit", "completed"],
127
+ ["bulk_action.edit", "paused"],
128
+ ["bulk_action.email", "created"],
129
+ ["bulk_action.email", "updated"],
130
+ ["bulk_action.email", "completed"],
131
+ ["bulk_action.email", "paused"],
132
+ ["bulk_action.sequence_subscription", "created"],
133
+ ["bulk_action.sequence_subscription", "updated"],
134
+ ["bulk_action.sequence_subscription", "completed"],
135
+ ["bulk_action.sequence_subscription", "paused"],
136
+ ["custom_fields.lead", "created"],
137
+ ["custom_fields.lead", "updated"],
138
+ ["custom_fields.lead", "deleted"],
139
+ ["custom_fields.contact", "created"],
140
+ ["custom_fields.contact", "updated"],
141
+ ["custom_fields.contact", "deleted"],
142
+ ["custom_fields.opportunity", "created"],
143
+ ["custom_fields.opportunity", "updated"],
144
+ ["custom_fields.opportunity", "deleted"],
145
+ ["custom_fields.activity", "created"],
146
+ ["custom_fields.activity", "updated"],
147
+ ["custom_fields.activity", "deleted"],
148
+ ["custom_fields.custom_object", "created"],
149
+ ["custom_fields.custom_object", "updated"],
150
+ ["custom_fields.custom_object", "deleted"],
151
+ ["custom_fields.shared", "created"],
152
+ ["custom_fields.shared", "updated"],
153
+ ["custom_fields.shared", "deleted"],
154
+ ["custom_activity_type", "created"],
155
+ ["custom_activity_type", "updated"],
156
+ ["custom_activity_type", "deleted"],
157
+ ["activity.custom_activity", "created"],
158
+ ["activity.custom_activity", "updated"],
159
+ ["activity.custom_activity", "deleted"],
160
+ ["custom_object_type", "created"],
161
+ ["custom_object_type", "updated"],
162
+ ["custom_object_type", "deleted"],
163
+ ["custom_object", "created"],
164
+ ["custom_object", "updated"],
165
+ ["custom_object", "deleted"],
166
+ ["status.lead", "created"],
167
+ ["status.lead", "updated"],
168
+ ["status.lead", "deleted"],
169
+ ["status.opportunity", "created"],
170
+ ["status.opportunity", "updated"],
171
+ ["status.opportunity", "deleted"],
172
+ ["membership", "activated"],
173
+ ["membership", "deactivated"],
174
+ ["group", "created"],
175
+ ["group", "updated"],
176
+ ["group", "deleted"],
177
+ ["saved_search", "created"],
178
+ ["saved_search", "updated"],
179
+ ["phone_number", "created"],
180
+ ["phone_number", "updated"],
181
+ ["phone_number", "deleted"],
182
+ ["email_template", "created"],
183
+ ["email_template", "updated"],
184
+ ["email_template", "deleted"],
185
+ ["sms_template", "created"],
186
+ ["sms_template", "updated"],
187
+ ["sms_template", "deleted"],
188
+ ["sequence", "created"],
189
+ ["sequence", "updated"],
190
+ ["sequence", "deleted"],
191
+ ["sequence_subscription", "created"],
192
+ ["sequence_subscription", "updated"],
193
+ ["sequence_subscription", "deleted"],
194
+ ["comment", "created"],
195
+ ["comment", "updated"],
196
+ ["comment", "deleted"],
197
+ ["comment_thread", "created"],
198
+ ["comment_thread", "updated"],
199
+ ["comment_thread", "deleted"],
200
+ ] as const
201
+
202
+ export type CloseWebhookEventSelection =
203
+ (typeof CLOSE_WEBHOOK_EVENT_SELECTIONS)[number]
204
+
205
+ export const CLOSE_EVENT_SCHEMA = z.object({
206
+ action: z.string(),
207
+ apiKeyId: z.string().nullable().optional(),
208
+ changedFields: z.string().array().prefault([]),
209
+ createdAt: CLOSE_DATE_SCHEMA,
210
+ data: CLOSE_OBJECT_SCHEMA.nullable(),
211
+ id: z.string(),
212
+ leadId: z.string().nullable(),
213
+ meta: CLOSE_OBJECT_SCHEMA.prefault({}),
214
+ objectId: z.string(),
215
+ objectType: z.string(),
216
+ organizationId: z.string(),
217
+ previousData: CLOSE_OBJECT_SCHEMA.nullable(),
218
+ requestId: z.string().nullable().optional(),
219
+ updatedAt: CLOSE_DATE_SCHEMA,
220
+ userId: z.string().nullable(),
221
+ })
222
+
223
+ export const CLOSE_WEBHOOK_PAYLOAD_SCHEMA = z.object({
224
+ event: CLOSE_EVENT_SCHEMA,
225
+ subscriptionId: z.string(),
226
+ })
227
+
228
+ export const CLOSE_TRIGGER_CONFIG_SCHEMA = z.object({})
229
+
230
+ // Event snapshots can omit fields that are required by the current REST
231
+ // resource. Close's documented opportunity-won webhook is one such payload.
232
+ const CLOSE_CALL_EVENT_DATA_SCHEMA = CLOSE_CALL_SCHEMA.partial()
233
+ const CLOSE_COMMENT_EVENT_DATA_SCHEMA = CLOSE_COMMENT_SCHEMA.partial()
234
+ const CLOSE_CONTACT_EVENT_DATA_SCHEMA = CLOSE_CONTACT_SCHEMA.partial()
235
+ const CLOSE_EMAIL_EVENT_DATA_SCHEMA = CLOSE_EMAIL_SCHEMA.partial()
236
+ const CLOSE_LEAD_EVENT_DATA_SCHEMA = CLOSE_LEAD_SCHEMA.partial()
237
+ const CLOSE_MEETING_EVENT_DATA_SCHEMA = CLOSE_MEETING_SCHEMA.partial()
238
+ const CLOSE_OPPORTUNITY_EVENT_DATA_SCHEMA = CLOSE_OPPORTUNITY_SCHEMA.partial()
239
+ const CLOSE_SEQUENCE_SUBSCRIPTION_EVENT_DATA_SCHEMA =
240
+ CLOSE_SEQUENCE_SUBSCRIPTION_SCHEMA.partial()
241
+ const CLOSE_SMS_EVENT_DATA_SCHEMA = CLOSE_SMS_SCHEMA.partial()
242
+ const CLOSE_TASK_EVENT_DATA_SCHEMA = z.union([
243
+ CLOSE_TASK_SCHEMA.options[0].partial(),
244
+ CLOSE_TASK_SCHEMA.options[1].partial(),
245
+ CLOSE_TASK_SCHEMA.options[2].partial(),
246
+ CLOSE_TASK_SCHEMA.options[3].partial(),
247
+ CLOSE_TASK_SCHEMA.options[4].partial(),
248
+ CLOSE_TASK_SCHEMA.options[5].partial(),
249
+ CLOSE_TASK_SCHEMA.options[6].partial(),
250
+ CLOSE_TASK_SCHEMA.options[7].partial(),
251
+ CLOSE_TASK_SCHEMA.options[8].partial(),
252
+ ])
253
+
254
+ const SEMANTIC_EVENTS = {
255
+ "close.call.answered": [
256
+ "activity.call",
257
+ "answered",
258
+ CLOSE_CALL_EVENT_DATA_SCHEMA,
259
+ ],
260
+ "close.call.completed": [
261
+ "activity.call",
262
+ "completed",
263
+ CLOSE_CALL_EVENT_DATA_SCHEMA,
264
+ ],
265
+ "close.comment.created": [
266
+ "comment",
267
+ "created",
268
+ CLOSE_COMMENT_EVENT_DATA_SCHEMA,
269
+ ],
270
+ "close.comment.deleted": ["comment", "deleted", null],
271
+ "close.comment.updated": [
272
+ "comment",
273
+ "updated",
274
+ CLOSE_COMMENT_EVENT_DATA_SCHEMA,
275
+ ],
276
+ "close.contact.created": [
277
+ "contact",
278
+ "created",
279
+ CLOSE_CONTACT_EVENT_DATA_SCHEMA,
280
+ ],
281
+ "close.contact.deleted": ["contact", "deleted", null],
282
+ "close.contact.updated": [
283
+ "contact",
284
+ "updated",
285
+ CLOSE_CONTACT_EVENT_DATA_SCHEMA,
286
+ ],
287
+ "close.email.received": [
288
+ "activity.email",
289
+ "created",
290
+ CLOSE_EMAIL_EVENT_DATA_SCHEMA,
291
+ ],
292
+ "close.email.sent": ["activity.email", "sent", CLOSE_EMAIL_EVENT_DATA_SCHEMA],
293
+ "close.lead.created": ["lead", "created", CLOSE_LEAD_EVENT_DATA_SCHEMA],
294
+ "close.lead.deleted": ["lead", "deleted", null],
295
+ "close.lead.merged": ["lead", "merged", CLOSE_LEAD_EVENT_DATA_SCHEMA],
296
+ "close.lead.updated": ["lead", "updated", CLOSE_LEAD_EVENT_DATA_SCHEMA],
297
+ "close.meeting.canceled": [
298
+ "activity.meeting",
299
+ "canceled",
300
+ CLOSE_MEETING_EVENT_DATA_SCHEMA,
301
+ ],
302
+ "close.meeting.completed": [
303
+ "activity.meeting",
304
+ "completed",
305
+ CLOSE_MEETING_EVENT_DATA_SCHEMA,
306
+ ],
307
+ "close.meeting.scheduled": [
308
+ "activity.meeting",
309
+ "scheduled",
310
+ CLOSE_MEETING_EVENT_DATA_SCHEMA,
311
+ ],
312
+ "close.meeting.started": [
313
+ "activity.meeting",
314
+ "started",
315
+ CLOSE_MEETING_EVENT_DATA_SCHEMA,
316
+ ],
317
+ "close.opportunity.created": [
318
+ "opportunity",
319
+ "created",
320
+ CLOSE_OPPORTUNITY_EVENT_DATA_SCHEMA,
321
+ ],
322
+ "close.opportunity.deleted": ["opportunity", "deleted", null],
323
+ "close.opportunity.lost": [
324
+ "opportunity",
325
+ "updated",
326
+ CLOSE_OPPORTUNITY_EVENT_DATA_SCHEMA,
327
+ ],
328
+ "close.opportunity.updated": [
329
+ "opportunity",
330
+ "updated",
331
+ CLOSE_OPPORTUNITY_EVENT_DATA_SCHEMA,
332
+ ],
333
+ "close.opportunity.won": [
334
+ "opportunity",
335
+ "updated",
336
+ CLOSE_OPPORTUNITY_EVENT_DATA_SCHEMA,
337
+ ],
338
+ "close.sequenceSubscription.created": [
339
+ "sequence_subscription",
340
+ "created",
341
+ CLOSE_SEQUENCE_SUBSCRIPTION_EVENT_DATA_SCHEMA,
342
+ ],
343
+ "close.sequenceSubscription.deleted": [
344
+ "sequence_subscription",
345
+ "deleted",
346
+ null,
347
+ ],
348
+ "close.sequenceSubscription.errored": [
349
+ "sequence_subscription",
350
+ "updated",
351
+ CLOSE_SEQUENCE_SUBSCRIPTION_EVENT_DATA_SCHEMA,
352
+ ],
353
+ "close.sequenceSubscription.finished": [
354
+ "sequence_subscription",
355
+ "updated",
356
+ CLOSE_SEQUENCE_SUBSCRIPTION_EVENT_DATA_SCHEMA,
357
+ ],
358
+ "close.sequenceSubscription.goalReached": [
359
+ "sequence_subscription",
360
+ "updated",
361
+ CLOSE_SEQUENCE_SUBSCRIPTION_EVENT_DATA_SCHEMA,
362
+ ],
363
+ "close.sequenceSubscription.paused": [
364
+ "sequence_subscription",
365
+ "updated",
366
+ CLOSE_SEQUENCE_SUBSCRIPTION_EVENT_DATA_SCHEMA,
367
+ ],
368
+ "close.sequenceSubscription.resumed": [
369
+ "sequence_subscription",
370
+ "updated",
371
+ CLOSE_SEQUENCE_SUBSCRIPTION_EVENT_DATA_SCHEMA,
372
+ ],
373
+ "close.sequenceSubscription.updated": [
374
+ "sequence_subscription",
375
+ "updated",
376
+ CLOSE_SEQUENCE_SUBSCRIPTION_EVENT_DATA_SCHEMA,
377
+ ],
378
+ "close.sms.received": [
379
+ "activity.sms",
380
+ "created",
381
+ CLOSE_SMS_EVENT_DATA_SCHEMA,
382
+ ],
383
+ "close.sms.sent": ["activity.sms", "sent", CLOSE_SMS_EVENT_DATA_SCHEMA],
384
+ "close.task.completed": ["task.*", "completed", CLOSE_TASK_EVENT_DATA_SCHEMA],
385
+ "close.task.created": ["task.*", "created", CLOSE_TASK_EVENT_DATA_SCHEMA],
386
+ "close.task.deleted": ["task.*", "deleted", null],
387
+ "close.task.updated": ["task.*", "updated", CLOSE_TASK_EVENT_DATA_SCHEMA],
388
+ } as const
389
+
390
+ export const CLOSE_SEMANTIC_EVENT_TYPES = [
391
+ "close.call.answered",
392
+ "close.call.completed",
393
+ "close.comment.created",
394
+ "close.comment.deleted",
395
+ "close.comment.updated",
396
+ "close.contact.created",
397
+ "close.contact.deleted",
398
+ "close.contact.updated",
399
+ "close.email.received",
400
+ "close.email.sent",
401
+ "close.lead.created",
402
+ "close.lead.deleted",
403
+ "close.lead.merged",
404
+ "close.lead.updated",
405
+ "close.meeting.canceled",
406
+ "close.meeting.completed",
407
+ "close.meeting.scheduled",
408
+ "close.meeting.started",
409
+ "close.opportunity.created",
410
+ "close.opportunity.deleted",
411
+ "close.opportunity.lost",
412
+ "close.opportunity.updated",
413
+ "close.opportunity.won",
414
+ "close.sequenceSubscription.created",
415
+ "close.sequenceSubscription.deleted",
416
+ "close.sequenceSubscription.errored",
417
+ "close.sequenceSubscription.finished",
418
+ "close.sequenceSubscription.goalReached",
419
+ "close.sequenceSubscription.paused",
420
+ "close.sequenceSubscription.resumed",
421
+ "close.sequenceSubscription.updated",
422
+ "close.sms.received",
423
+ "close.sms.sent",
424
+ "close.task.completed",
425
+ "close.task.created",
426
+ "close.task.deleted",
427
+ "close.task.updated",
428
+ ] as const satisfies readonly (keyof typeof SEMANTIC_EVENTS)[]
429
+
430
+ export const closeTriggerContracts = {
431
+ "close.event": {
432
+ configSchema: CLOSE_TRIGGER_CONFIG_SCHEMA,
433
+ eventSchema: CLOSE_EVENT_SCHEMA,
434
+ },
435
+ "close.call.answered": semanticContract(
436
+ "activity.call",
437
+ "answered",
438
+ CLOSE_CALL_EVENT_DATA_SCHEMA,
439
+ ),
440
+ "close.call.completed": semanticContract(
441
+ "activity.call",
442
+ "completed",
443
+ CLOSE_CALL_EVENT_DATA_SCHEMA,
444
+ ),
445
+ "close.comment.created": semanticContract(
446
+ "comment",
447
+ "created",
448
+ CLOSE_COMMENT_EVENT_DATA_SCHEMA,
449
+ ),
450
+ "close.comment.deleted": semanticContract("comment", "deleted", z.null()),
451
+ "close.comment.updated": semanticContract(
452
+ "comment",
453
+ "updated",
454
+ CLOSE_COMMENT_EVENT_DATA_SCHEMA,
455
+ ),
456
+ "close.contact.created": semanticContract(
457
+ "contact",
458
+ "created",
459
+ CLOSE_CONTACT_EVENT_DATA_SCHEMA,
460
+ ),
461
+ "close.contact.deleted": semanticContract("contact", "deleted", z.null()),
462
+ "close.contact.updated": semanticContract(
463
+ "contact",
464
+ "updated",
465
+ CLOSE_CONTACT_EVENT_DATA_SCHEMA,
466
+ ),
467
+ "close.email.received": semanticContract(
468
+ "activity.email",
469
+ "created",
470
+ CLOSE_EMAIL_EVENT_DATA_SCHEMA,
471
+ ),
472
+ "close.email.sent": semanticContract(
473
+ "activity.email",
474
+ "sent",
475
+ CLOSE_EMAIL_EVENT_DATA_SCHEMA,
476
+ ),
477
+ "close.lead.created": semanticContract(
478
+ "lead",
479
+ "created",
480
+ CLOSE_LEAD_EVENT_DATA_SCHEMA,
481
+ ),
482
+ "close.lead.deleted": semanticContract("lead", "deleted", z.null()),
483
+ "close.lead.merged": semanticContract(
484
+ "lead",
485
+ "merged",
486
+ CLOSE_LEAD_EVENT_DATA_SCHEMA,
487
+ ),
488
+ "close.lead.updated": semanticContract(
489
+ "lead",
490
+ "updated",
491
+ CLOSE_LEAD_EVENT_DATA_SCHEMA,
492
+ ),
493
+ "close.meeting.canceled": semanticContract(
494
+ "activity.meeting",
495
+ "canceled",
496
+ CLOSE_MEETING_EVENT_DATA_SCHEMA,
497
+ ),
498
+ "close.meeting.completed": semanticContract(
499
+ "activity.meeting",
500
+ "completed",
501
+ CLOSE_MEETING_EVENT_DATA_SCHEMA,
502
+ ),
503
+ "close.meeting.scheduled": semanticContract(
504
+ "activity.meeting",
505
+ "scheduled",
506
+ CLOSE_MEETING_EVENT_DATA_SCHEMA,
507
+ ),
508
+ "close.meeting.started": semanticContract(
509
+ "activity.meeting",
510
+ "started",
511
+ CLOSE_MEETING_EVENT_DATA_SCHEMA,
512
+ ),
513
+ "close.opportunity.created": semanticContract(
514
+ "opportunity",
515
+ "created",
516
+ CLOSE_OPPORTUNITY_EVENT_DATA_SCHEMA,
517
+ ),
518
+ "close.opportunity.deleted": semanticContract(
519
+ "opportunity",
520
+ "deleted",
521
+ z.null(),
522
+ ),
523
+ "close.opportunity.lost": semanticContract(
524
+ "opportunity",
525
+ "updated",
526
+ CLOSE_OPPORTUNITY_EVENT_DATA_SCHEMA,
527
+ ),
528
+ "close.opportunity.updated": semanticContract(
529
+ "opportunity",
530
+ "updated",
531
+ CLOSE_OPPORTUNITY_EVENT_DATA_SCHEMA,
532
+ ),
533
+ "close.opportunity.won": semanticContract(
534
+ "opportunity",
535
+ "updated",
536
+ CLOSE_OPPORTUNITY_EVENT_DATA_SCHEMA,
537
+ ),
538
+ "close.sequenceSubscription.created": semanticContract(
539
+ "sequence_subscription",
540
+ "created",
541
+ CLOSE_SEQUENCE_SUBSCRIPTION_EVENT_DATA_SCHEMA,
542
+ ),
543
+ "close.sequenceSubscription.deleted": semanticContract(
544
+ "sequence_subscription",
545
+ "deleted",
546
+ z.null(),
547
+ ),
548
+ "close.sequenceSubscription.errored": semanticContract(
549
+ "sequence_subscription",
550
+ "updated",
551
+ CLOSE_SEQUENCE_SUBSCRIPTION_EVENT_DATA_SCHEMA,
552
+ ),
553
+ "close.sequenceSubscription.finished": semanticContract(
554
+ "sequence_subscription",
555
+ "updated",
556
+ CLOSE_SEQUENCE_SUBSCRIPTION_EVENT_DATA_SCHEMA,
557
+ ),
558
+ "close.sequenceSubscription.goalReached": semanticContract(
559
+ "sequence_subscription",
560
+ "updated",
561
+ CLOSE_SEQUENCE_SUBSCRIPTION_EVENT_DATA_SCHEMA,
562
+ ),
563
+ "close.sequenceSubscription.paused": semanticContract(
564
+ "sequence_subscription",
565
+ "updated",
566
+ CLOSE_SEQUENCE_SUBSCRIPTION_EVENT_DATA_SCHEMA,
567
+ ),
568
+ "close.sequenceSubscription.resumed": semanticContract(
569
+ "sequence_subscription",
570
+ "updated",
571
+ CLOSE_SEQUENCE_SUBSCRIPTION_EVENT_DATA_SCHEMA,
572
+ ),
573
+ "close.sequenceSubscription.updated": semanticContract(
574
+ "sequence_subscription",
575
+ "updated",
576
+ CLOSE_SEQUENCE_SUBSCRIPTION_EVENT_DATA_SCHEMA,
577
+ ),
578
+ "close.sms.received": semanticContract(
579
+ "activity.sms",
580
+ "created",
581
+ CLOSE_SMS_EVENT_DATA_SCHEMA,
582
+ ),
583
+ "close.sms.sent": semanticContract(
584
+ "activity.sms",
585
+ "sent",
586
+ CLOSE_SMS_EVENT_DATA_SCHEMA,
587
+ ),
588
+ "close.task.completed": taskContract(
589
+ "completed",
590
+ CLOSE_TASK_EVENT_DATA_SCHEMA,
591
+ ),
592
+ "close.task.created": taskContract("created", CLOSE_TASK_EVENT_DATA_SCHEMA),
593
+ "close.task.deleted": taskContract("deleted", z.null()),
594
+ "close.task.updated": taskContract("updated", CLOSE_TASK_EVENT_DATA_SCHEMA),
595
+ } as const satisfies Record<
596
+ string,
597
+ {
598
+ configSchema: typeof CLOSE_TRIGGER_CONFIG_SCHEMA
599
+ eventSchema: z.ZodType
600
+ }
601
+ >
602
+
603
+ /**
604
+ * Classifies one normalized Close event into semantic trigger types.
605
+ *
606
+ * @param event - Normalized Close webhook event.
607
+ */
608
+ export function getCloseSemanticEventTypes(
609
+ event: z.output<typeof CLOSE_EVENT_SCHEMA>,
610
+ ) {
611
+ const exact = CLOSE_SEMANTIC_EVENT_TYPES.filter((type) => {
612
+ const [objectType, action] = SEMANTIC_EVENTS[type]
613
+ return (
614
+ type !== "close.opportunity.won" &&
615
+ type !== "close.opportunity.lost" &&
616
+ type !== "close.email.received" &&
617
+ type !== "close.sms.received" &&
618
+ type !== "close.sequenceSubscription.errored" &&
619
+ type !== "close.sequenceSubscription.finished" &&
620
+ type !== "close.sequenceSubscription.goalReached" &&
621
+ type !== "close.sequenceSubscription.paused" &&
622
+ type !== "close.sequenceSubscription.resumed" &&
623
+ action === event.action &&
624
+ (objectType === "task.*"
625
+ ? event.objectType.startsWith("task.")
626
+ : objectType === event.objectType)
627
+ )
628
+ })
629
+
630
+ if (
631
+ event.objectType === "opportunity" &&
632
+ event.action === "updated" &&
633
+ event.data &&
634
+ event.previousData &&
635
+ event.changedFields.includes("status_type")
636
+ ) {
637
+ const statusType = event.data.statusType
638
+ if (statusType !== event.previousData.statusType) {
639
+ if (statusType === "won") exact.push("close.opportunity.won")
640
+ if (statusType === "lost") exact.push("close.opportunity.lost")
641
+ }
642
+ }
643
+ if (
644
+ event.action === "created" &&
645
+ event.data &&
646
+ ((event.objectType === "activity.email" &&
647
+ event.data.direction === "incoming") ||
648
+ (event.objectType === "activity.sms" &&
649
+ event.data.direction === "inbound"))
650
+ ) {
651
+ exact.push(
652
+ event.objectType === "activity.email"
653
+ ? "close.email.received"
654
+ : "close.sms.received",
655
+ )
656
+ }
657
+ if (
658
+ event.objectType === "sequence_subscription" &&
659
+ event.action === "updated" &&
660
+ event.data &&
661
+ event.previousData &&
662
+ event.changedFields.includes("status") &&
663
+ event.data.status !== event.previousData.status
664
+ ) {
665
+ const status = event.data.status
666
+ if (status === "active") {
667
+ exact.push("close.sequenceSubscription.resumed")
668
+ } else if (status === "error") {
669
+ exact.push("close.sequenceSubscription.errored")
670
+ } else if (status === "finished") {
671
+ exact.push("close.sequenceSubscription.finished")
672
+ } else if (status === "goal") {
673
+ exact.push("close.sequenceSubscription.goalReached")
674
+ } else if (status === "paused") {
675
+ exact.push("close.sequenceSubscription.paused")
676
+ }
677
+ }
678
+ return [...new Set(exact)]
679
+ }
680
+
681
+ /**
682
+ * Resolves the provider event selections needed by a Close trigger type.
683
+ *
684
+ * @param type - Registered Close trigger type.
685
+ */
686
+ export function getCloseProviderEventSelection(
687
+ type: string,
688
+ ): readonly CloseWebhookEventSelection[] {
689
+ if (type === "close.event") return CLOSE_WEBHOOK_EVENT_SELECTIONS
690
+ if (!isCloseSemanticEventType(type)) return []
691
+ const selection = SEMANTIC_EVENTS[type]
692
+ if (selection[0] === "task.*") {
693
+ return CLOSE_WEBHOOK_EVENT_SELECTIONS.filter(
694
+ ([objectType, action]) =>
695
+ objectType.startsWith("task.") && action === selection[1],
696
+ )
697
+ }
698
+ return CLOSE_WEBHOOK_EVENT_SELECTIONS.filter(
699
+ ([objectType, action]) =>
700
+ objectType === selection[0] && action === selection[1],
701
+ )
702
+ }
703
+
704
+ /**
705
+ * Creates a semantic Close trigger contract.
706
+ *
707
+ * @param objectType - Exact provider object type.
708
+ * @param action - Exact provider action.
709
+ * @param dataSchema - Schema for the event object snapshot.
710
+ */
711
+ function semanticContract<
712
+ const TObjectType extends string,
713
+ const TAction extends string,
714
+ TDataSchema extends z.ZodType,
715
+ >(objectType: TObjectType, action: TAction, dataSchema: TDataSchema) {
716
+ return {
717
+ configSchema: CLOSE_TRIGGER_CONFIG_SCHEMA,
718
+ eventSchema: CLOSE_EVENT_SCHEMA.extend({
719
+ action: z.literal(action),
720
+ data: dataSchema,
721
+ objectType: z.literal(objectType),
722
+ }),
723
+ }
724
+ }
725
+
726
+ /**
727
+ * Creates a task-family Close trigger contract.
728
+ *
729
+ * @param action - Exact provider action.
730
+ * @param dataSchema - Schema for the task snapshot.
731
+ */
732
+ function taskContract<
733
+ const TAction extends string,
734
+ TDataSchema extends z.ZodType,
735
+ >(action: TAction, dataSchema: TDataSchema) {
736
+ return {
737
+ configSchema: CLOSE_TRIGGER_CONFIG_SCHEMA,
738
+ eventSchema: CLOSE_EVENT_SCHEMA.extend({
739
+ action: z.literal(action),
740
+ data: dataSchema,
741
+ objectType: z.string().startsWith("task."),
742
+ }),
743
+ }
744
+ }
745
+
746
+ /**
747
+ * Checks whether a trigger type has a semantic Close definition.
748
+ *
749
+ * @param type - Candidate trigger type.
750
+ */
751
+ function isCloseSemanticEventType(
752
+ type: string,
753
+ ): type is (typeof CLOSE_SEMANTIC_EVENT_TYPES)[number] {
754
+ return CLOSE_SEMANTIC_EVENT_TYPES.some((candidate) => candidate === type)
755
+ }