From 727d51571fd7b51fb2cb331073e39c25a76a03c4 Mon Sep 17 00:00:00 2001 From: Chris Cowan Date: Wed, 1 Apr 2026 15:33:02 -0700 Subject: [PATCH 1/3] Fix type for conversation_part.tag.* events --- integrations/intercom/runtime.ts | 52 +++++++++++++++++++-- integrations/intercom/runtime.types.test.ts | 29 +++++++++++- mod.ts | 6 ++- 3 files changed, 79 insertions(+), 8 deletions(-) diff --git a/integrations/intercom/runtime.ts b/integrations/intercom/runtime.ts index fb2fbd7..b02cf30 100644 --- a/integrations/intercom/runtime.ts +++ b/integrations/intercom/runtime.ts @@ -19,6 +19,13 @@ export interface IntercomTriggerOptions extends CommonTriggerOptions { workspaceId?: string; } +export interface IntercomConversationPartTag { + type: "conversation_part_tag"; + created_at: number; + conversation: IntercomTypes.Conversation; + tag: IntercomTypes.Tag; +} + interface IntercomWebhookTypeMap { admin: IntercomTypes.Admin; article: IntercomTypes.Article; @@ -36,6 +43,45 @@ interface IntercomWebhookTypeMap { visitor: IntercomTypes.Visitor; } +/** Contains webhook topics that contain one period. Needs to be separate for + * the type inference to work simply. */ +interface IntercomWebhookSubtopicTypeMap { + "conversation_part.tag": IntercomConversationPartTag; +} + +/** Extracts the longest prefix of TTopic that matches a key in + * IntercomWebhookSubtopicTypeMap or IntercomWebhookTypeMap. */ +type IntercomWebhookPrefix = TTopic extends + `${infer Prefix}.${infer Subtopic}.${string}` + ? (`${Prefix}.${Subtopic}` extends keyof IntercomWebhookSubtopicTypeMap ? `${Prefix}.${Subtopic}` + : Prefix extends keyof IntercomWebhookTypeMap ? Prefix + : undefined) + : (TTopic extends `${infer Prefix}.${string}` + ? Prefix extends keyof IntercomWebhookTypeMap ? Prefix + : undefined + : undefined); + +/** + * Add {type: TPrefix} to the payload type unless it has a type property that + * TPrefix can't be cast to. This fixes the types in the type map that are + * missing the type property or have it set to something overly generic like + * `string` or `string | undefined`, but allows more specific types to remain + * intact. + */ +type IntercomWebhookPayload = TMap[TPrefix] extends + { type: infer TType } + ? (TPrefix extends TType ? TMap[TPrefix] & { type: TPrefix } : TMap[TPrefix]) + : TMap[TPrefix] & { type: TPrefix }; + +type IntercomWebhookItem = IntercomWebhookPrefix extends + infer TPrefix extends string + ? TPrefix extends keyof IntercomWebhookSubtopicTypeMap + ? IntercomWebhookPayload + : TPrefix extends keyof IntercomWebhookTypeMap + ? IntercomWebhookPayload + : unknown + : unknown; + export type IntercomEvent = { [K in TTopic]: { type: "notification_event"; @@ -46,11 +92,7 @@ export type IntercomEvent = { delivery_attempts: number; first_sent_at: number; data: { - item: K extends `${infer Prefix}.${string}` - ? Prefix extends keyof IntercomWebhookTypeMap - ? IntercomWebhookTypeMap[Prefix] & { type: Prefix } - : unknown - : unknown; + item: IntercomWebhookItem; }; }; }[TTopic]; diff --git a/integrations/intercom/runtime.types.test.ts b/integrations/intercom/runtime.types.test.ts index 1c129dc..a3c2ebd 100644 --- a/integrations/intercom/runtime.types.test.ts +++ b/integrations/intercom/runtime.types.test.ts @@ -1,6 +1,6 @@ import { assert } from "@std/assert"; import type { Intercom as IntercomTypes } from "intercom-client"; -import type { Intercom, IntercomEvent } from "./runtime.ts"; +import type { Intercom, IntercomConversationPartTag, IntercomEvent } from "./runtime.ts"; function expectType(_value: T): void {} @@ -14,13 +14,19 @@ function verifyTopicBasedTyping(intercom: Intercom): void { expectType(event.delivery_attempts); }); - intercom.onEvent(["contact.created", "conversation.admin.closed"], (event) => { + intercom.onEvent(["contact.created", "conversation.admin.closed", "job.completed"], (event) => { switch (event.topic) { case "contact.created": expectType(event.data.item); + expectType<"contact">(event.data.item.type); break; case "conversation.admin.closed": expectType(event.data.item); + expectType<"conversation">(event.data.item.type); + break; + case "job.completed": + expectType(event.data.item); + expectType<"job">(event.data.item.type); break; } }); @@ -29,6 +35,24 @@ function verifyTopicBasedTyping(intercom: Intercom): void { expectType<"conversation_part">(event.data.item.type); expectType(event.data.item); }); + + intercom.onEvent(["conversation_part.tag.added"], (event) => { + expectType(event.data.item); + expectType<"conversation_part_tag">(event.data.item.type); + }); + + intercom.onEvent(["conversation_part.tag.added", "conversation_part.replied"], (event) => { + switch (event.topic) { + case "conversation_part.tag.added": + expectType(event.data.item); + expectType<"conversation_part_tag">(event.data.item.type); + break; + case "conversation_part.replied": + expectType(event.data.item); + expectType<"conversation_part">(event.data.item.type); + break; + } + }); } function verifyCustomTopicsRemainSupported(intercom: Intercom): void { @@ -41,6 +65,7 @@ function verifyCustomTopicsRemainSupported(intercom: Intercom): void { function verifyEnvelopeType(event: IntercomEvent<"ticket.created">): void { expectType<"notification_event">(event.type); expectType<"ticket.created">(event.topic); + expectType<"ticket">(event.data.item.type); expectType(event.id); expectType(event.first_sent_at); } diff --git a/mod.ts b/mod.ts index 89ce6a2..e0f7689 100644 --- a/mod.ts +++ b/mod.ts @@ -51,7 +51,11 @@ export type { StreakTriggerOptions, } from "./integrations/streak/runtime.ts"; export type { StripeEvent, StripeTriggerOptions } from "./integrations/stripe/runtime.ts"; -export type { IntercomEvent, IntercomTriggerOptions } from "./integrations/intercom/runtime.ts"; +export type { + IntercomConversationPartTag, + IntercomEvent, + IntercomTriggerOptions, +} from "./integrations/intercom/runtime.ts"; export type { SlackCredentialFetcherOptions, SlackEventWebhook, From 73ea7899250c2cb77986d16f42413a1fb1f7c6da Mon Sep 17 00:00:00 2001 From: Chris Cowan Date: Wed, 1 Apr 2026 16:14:52 -0700 Subject: [PATCH 2/3] update intercom docs link --- integrations/intercom/runtime.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integrations/intercom/runtime.ts b/integrations/intercom/runtime.ts index b02cf30..562df09 100644 --- a/integrations/intercom/runtime.ts +++ b/integrations/intercom/runtime.ts @@ -186,7 +186,7 @@ export class Intercom { * }); * ``` * - * @see https://developers.intercom.com/intercom-api-reference/reference/webhook-topics - Full list of event types + * @see https://developers.intercom.com/docs/references/webhooks/webhook-models - Full list of event types */ onEvent( events: T[], From 0907f0930649e53e1f96a9deaad8f2ce1d657beb Mon Sep 17 00:00:00 2001 From: Chris Cowan Date: Wed, 1 Apr 2026 16:25:12 -0700 Subject: [PATCH 3/3] simplify types further --- integrations/intercom/runtime.ts | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/integrations/intercom/runtime.ts b/integrations/intercom/runtime.ts index 562df09..5c284c9 100644 --- a/integrations/intercom/runtime.ts +++ b/integrations/intercom/runtime.ts @@ -33,6 +33,7 @@ interface IntercomWebhookTypeMap { contact: IntercomTypes.Contact; conversation: IntercomTypes.Conversation; conversation_part: IntercomTypes.ConversationPart; + "conversation_part.tag": IntercomConversationPartTag; event: IntercomTypes.DataEvent; job: IntercomTypes.Jobs; note: IntercomTypes.Note; @@ -43,17 +44,11 @@ interface IntercomWebhookTypeMap { visitor: IntercomTypes.Visitor; } -/** Contains webhook topics that contain one period. Needs to be separate for - * the type inference to work simply. */ -interface IntercomWebhookSubtopicTypeMap { - "conversation_part.tag": IntercomConversationPartTag; -} - /** Extracts the longest prefix of TTopic that matches a key in - * IntercomWebhookSubtopicTypeMap or IntercomWebhookTypeMap. */ + * IntercomWebhookTypeMap. */ type IntercomWebhookPrefix = TTopic extends `${infer Prefix}.${infer Subtopic}.${string}` - ? (`${Prefix}.${Subtopic}` extends keyof IntercomWebhookSubtopicTypeMap ? `${Prefix}.${Subtopic}` + ? (`${Prefix}.${Subtopic}` extends keyof IntercomWebhookTypeMap ? `${Prefix}.${Subtopic}` : Prefix extends keyof IntercomWebhookTypeMap ? Prefix : undefined) : (TTopic extends `${infer Prefix}.${string}` @@ -68,18 +63,14 @@ type IntercomWebhookPrefix = TTopic extends * `string` or `string | undefined`, but allows more specific types to remain * intact. */ -type IntercomWebhookPayload = TMap[TPrefix] extends - { type: infer TType } - ? (TPrefix extends TType ? TMap[TPrefix] & { type: TPrefix } : TMap[TPrefix]) - : TMap[TPrefix] & { type: TPrefix }; +type IntercomWebhookPayload = + IntercomWebhookTypeMap[TPrefix] extends { type: infer TType } + ? (TPrefix extends TType ? IntercomWebhookTypeMap[TPrefix] & { type: TPrefix } + : IntercomWebhookTypeMap[TPrefix]) + : IntercomWebhookTypeMap[TPrefix] & { type: TPrefix }; type IntercomWebhookItem = IntercomWebhookPrefix extends - infer TPrefix extends string - ? TPrefix extends keyof IntercomWebhookSubtopicTypeMap - ? IntercomWebhookPayload - : TPrefix extends keyof IntercomWebhookTypeMap - ? IntercomWebhookPayload - : unknown + infer TPrefix extends keyof IntercomWebhookTypeMap ? IntercomWebhookPayload : unknown; export type IntercomEvent = {