Files
wehub-resource-sync d25d482dc2
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:20:55 +08:00

528 lines
17 KiB
TypeScript

import type { SubBlockConfig } from '@/blocks/types'
import type { TriggerOutput } from '@/triggers/types'
export const attioTriggerOptions = [
{ label: 'Record Created', id: 'attio_record_created' },
{ label: 'Record Updated', id: 'attio_record_updated' },
{ label: 'Record Deleted', id: 'attio_record_deleted' },
{ label: 'Record Merged', id: 'attio_record_merged' },
{ label: 'Note Created', id: 'attio_note_created' },
{ label: 'Note Updated', id: 'attio_note_updated' },
{ label: 'Note Deleted', id: 'attio_note_deleted' },
{ label: 'Task Created', id: 'attio_task_created' },
{ label: 'Task Updated', id: 'attio_task_updated' },
{ label: 'Task Deleted', id: 'attio_task_deleted' },
{ label: 'Comment Created', id: 'attio_comment_created' },
{ label: 'Comment Resolved', id: 'attio_comment_resolved' },
{ label: 'Comment Unresolved', id: 'attio_comment_unresolved' },
{ label: 'Comment Deleted', id: 'attio_comment_deleted' },
{ label: 'List Entry Created', id: 'attio_list_entry_created' },
{ label: 'List Entry Updated', id: 'attio_list_entry_updated' },
{ label: 'List Entry Deleted', id: 'attio_list_entry_deleted' },
{ label: 'List Created', id: 'attio_list_created' },
{ label: 'List Updated', id: 'attio_list_updated' },
{ label: 'List Deleted', id: 'attio_list_deleted' },
{ label: 'Workspace Member Created', id: 'attio_workspace_member_created' },
{ label: 'Generic Webhook (All Events)', id: 'attio_webhook' },
]
export function attioSetupInstructions(): string {
const instructions = [
'<strong>Note:</strong> Webhooks are automatically created in Attio when you deploy this workflow, and deleted when you undeploy. See the <a href="https://docs.attio.com/rest-api/guides/webhooks" target="_blank" rel="noopener noreferrer">Attio webhook documentation</a> for details.',
'Connect your <strong>Attio account</strong> using the credential selector above.',
'<strong>Deploy</strong> the workflow — a webhook will be created automatically in your Attio workspace.',
]
return instructions
.map(
(instruction, index) =>
`<div class="mb-3">${index === 0 ? instruction : `<strong>${index}.</strong> ${instruction}`}</div>`
)
.join('')
}
/**
* Builds subBlocks for an Attio trigger with OAuth credentials and automatic webhook lifecycle.
* Used by both the primary trigger (with dropdown) and secondary triggers.
*/
export function buildAttioTriggerSubBlocks(triggerId: string): SubBlockConfig[] {
return [
{
id: 'triggerCredentials',
title: 'Attio Account',
type: 'oauth-input',
serviceId: 'attio',
mode: 'trigger',
required: true,
condition: { field: 'selectedTriggerId', value: triggerId },
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
hideFromPreview: true,
type: 'text',
defaultValue: attioSetupInstructions(),
mode: 'trigger',
condition: { field: 'selectedTriggerId', value: triggerId },
},
]
}
/**
* Base webhook outputs common to all Attio triggers.
*/
function buildBaseWebhookOutputs(): Record<string, TriggerOutput> {
return {
eventType: {
type: 'string',
description: 'The type of event (e.g. record.created, note.created)',
},
}
}
/**
* Record event outputs for record triggers.
*/
function buildRecordIdOutputs(): Record<string, TriggerOutput> {
return {
workspaceId: { type: 'string', description: 'The workspace ID' },
objectId: { type: 'string', description: 'The object type ID (e.g. people, companies)' },
recordId: { type: 'string', description: 'The record ID' },
}
}
/**
* Record updated event outputs (includes attributeId).
*/
function buildRecordUpdatedIdOutputs(): Record<string, TriggerOutput> {
return {
...buildRecordIdOutputs(),
attributeId: {
type: 'string',
description: 'The ID of the attribute that was updated on the record',
},
}
}
/**
* Record merged event outputs.
* Attio payload: id.record_id (winner), duplicate_object_id, duplicate_record_id (loser).
*/
function buildRecordMergedOutputs(): Record<string, TriggerOutput> {
return {
workspaceId: { type: 'string', description: 'The workspace ID' },
objectId: { type: 'string', description: 'The object type ID of the surviving record' },
recordId: { type: 'string', description: 'The surviving record ID after merge' },
duplicateObjectId: {
type: 'string',
description: 'The object type ID of the merged-away record',
},
duplicateRecordId: { type: 'string', description: 'The record ID that was merged away' },
}
}
/**
* Note event outputs.
*/
function buildNoteIdOutputs(): Record<string, TriggerOutput> {
return {
workspaceId: { type: 'string', description: 'The workspace ID' },
noteId: { type: 'string', description: 'The note ID' },
parentObjectId: { type: 'string', description: 'The parent object type ID' },
parentRecordId: { type: 'string', description: 'The parent record ID' },
}
}
/**
* Task event outputs.
* Attio task webhook payloads only contain workspace_id and task_id.
*/
function buildTaskIdOutputs(): Record<string, TriggerOutput> {
return {
workspaceId: { type: 'string', description: 'The workspace ID' },
taskId: { type: 'string', description: 'The task ID' },
}
}
/**
* Comment event outputs.
* Attio payload uses object_id/record_id (not parent_*), plus list_id/entry_id.
*/
function buildCommentIdOutputs(): Record<string, TriggerOutput> {
return {
workspaceId: { type: 'string', description: 'The workspace ID' },
threadId: { type: 'string', description: 'The thread ID' },
commentId: { type: 'string', description: 'The comment ID' },
objectId: { type: 'string', description: 'The object type ID' },
recordId: { type: 'string', description: 'The record ID' },
listId: { type: 'string', description: 'The list ID (if comment is on a list entry)' },
entryId: { type: 'string', description: 'The list entry ID (if comment is on a list entry)' },
}
}
/**
* List entry event outputs.
*/
function buildListEntryIdOutputs(): Record<string, TriggerOutput> {
return {
workspaceId: { type: 'string', description: 'The workspace ID' },
listId: { type: 'string', description: 'The list ID' },
entryId: { type: 'string', description: 'The list entry ID' },
}
}
/**
* List entry updated event outputs (includes attributeId).
*/
function buildListEntryUpdatedIdOutputs(): Record<string, TriggerOutput> {
return {
...buildListEntryIdOutputs(),
attributeId: {
type: 'string',
description: 'The ID of the attribute that was updated on the list entry',
},
}
}
/** Record created/deleted outputs. */
export function buildRecordOutputs(): Record<string, TriggerOutput> {
return {
...buildBaseWebhookOutputs(),
...buildRecordIdOutputs(),
}
}
/** Record updated outputs (includes attributeId). */
export function buildRecordUpdatedOutputs(): Record<string, TriggerOutput> {
return {
...buildBaseWebhookOutputs(),
...buildRecordUpdatedIdOutputs(),
}
}
/** Record merged outputs. */
export function buildRecordMergedEventOutputs(): Record<string, TriggerOutput> {
return {
...buildBaseWebhookOutputs(),
...buildRecordMergedOutputs(),
}
}
/** Note event outputs. */
export function buildNoteOutputs(): Record<string, TriggerOutput> {
return {
...buildBaseWebhookOutputs(),
...buildNoteIdOutputs(),
}
}
/** Task event outputs. */
export function buildTaskOutputs(): Record<string, TriggerOutput> {
return {
...buildBaseWebhookOutputs(),
...buildTaskIdOutputs(),
}
}
/** Comment event outputs. */
export function buildCommentOutputs(): Record<string, TriggerOutput> {
return {
...buildBaseWebhookOutputs(),
...buildCommentIdOutputs(),
}
}
/**
* List event outputs.
*/
function buildListIdOutputs(): Record<string, TriggerOutput> {
return {
workspaceId: { type: 'string', description: 'The workspace ID' },
listId: { type: 'string', description: 'The list ID' },
}
}
/** List created/updated/deleted outputs. */
export function buildListOutputs(): Record<string, TriggerOutput> {
return {
...buildBaseWebhookOutputs(),
...buildListIdOutputs(),
}
}
/**
* Workspace member event outputs.
*/
function buildWorkspaceMemberIdOutputs(): Record<string, TriggerOutput> {
return {
workspaceId: { type: 'string', description: 'The workspace ID' },
workspaceMemberId: { type: 'string', description: 'The workspace member ID' },
}
}
/** Workspace member outputs. */
export function buildWorkspaceMemberOutputs(): Record<string, TriggerOutput> {
return {
...buildBaseWebhookOutputs(),
...buildWorkspaceMemberIdOutputs(),
}
}
/** List entry created/deleted outputs. */
export function buildListEntryOutputs(): Record<string, TriggerOutput> {
return {
...buildBaseWebhookOutputs(),
...buildListEntryIdOutputs(),
}
}
/** List entry updated outputs (includes attributeId). */
export function buildListEntryUpdatedOutputs(): Record<string, TriggerOutput> {
return {
...buildBaseWebhookOutputs(),
...buildListEntryUpdatedIdOutputs(),
}
}
/** Generic webhook outputs covering all event types. */
export function buildGenericWebhookOutputs(): Record<string, TriggerOutput> {
return {
...buildBaseWebhookOutputs(),
id: { type: 'json', description: 'The event ID object containing resource identifiers' },
parentObjectId: {
type: 'string',
description: 'The parent object type ID (if applicable)',
},
parentRecordId: {
type: 'string',
description: 'The parent record ID (if applicable)',
},
}
}
/**
* Maps trigger IDs to the exact Attio event type strings.
*/
export const TRIGGER_EVENT_MAP: Record<string, string[]> = {
attio_record_created: ['record.created'],
attio_record_updated: ['record.updated'],
attio_record_deleted: ['record.deleted'],
attio_record_merged: ['record.merged'],
attio_note_created: ['note.created'],
attio_note_updated: ['note.updated', 'note-content.updated'],
attio_note_deleted: ['note.deleted'],
attio_task_created: ['task.created'],
attio_task_updated: ['task.updated'],
attio_task_deleted: ['task.deleted'],
attio_comment_created: ['comment.created'],
attio_comment_resolved: ['comment.resolved'],
attio_comment_unresolved: ['comment.unresolved'],
attio_comment_deleted: ['comment.deleted'],
attio_list_entry_created: ['list-entry.created'],
attio_list_entry_updated: ['list-entry.updated'],
attio_list_entry_deleted: ['list-entry.deleted'],
attio_list_created: ['list.created'],
attio_list_updated: ['list.updated'],
attio_list_deleted: ['list.deleted'],
attio_workspace_member_created: ['workspace-member.created'],
}
/**
* Extracts the first event from an Attio webhook payload.
* Attio wraps events in an `events` array: `{ webhook_id, events: [{ event_type, id, ... }] }`.
*/
export function getAttioEvent(body: Record<string, unknown>): Record<string, unknown> | undefined {
const events = body.events as Array<Record<string, unknown>> | undefined
return events?.[0]
}
/**
* Checks if an Attio webhook payload matches a trigger.
*/
export function isAttioPayloadMatch(triggerId: string, body: Record<string, unknown>): boolean {
if (triggerId === 'attio_webhook') {
return true
}
const event = getAttioEvent(body)
const eventType = event?.event_type as string | undefined
if (!eventType) {
return false
}
const acceptedEvents = TRIGGER_EVENT_MAP[triggerId]
return acceptedEvents ? acceptedEvents.includes(eventType) : false
}
/**
* Extracts formatted data from an Attio record event payload.
* Used for record.created, record.deleted triggers.
*/
export function extractAttioRecordData(body: Record<string, unknown>): Record<string, unknown> {
const event = getAttioEvent(body) ?? {}
const id = (event.id as Record<string, unknown>) ?? {}
return {
eventType: event.event_type ?? null,
workspaceId: id.workspace_id ?? null,
objectId: id.object_id ?? null,
recordId: id.record_id ?? null,
}
}
/**
* Extracts formatted data from an Attio record.updated event payload.
*/
export function extractAttioRecordUpdatedData(
body: Record<string, unknown>
): Record<string, unknown> {
const event = getAttioEvent(body) ?? {}
const id = (event.id as Record<string, unknown>) ?? {}
return {
eventType: event.event_type ?? null,
workspaceId: id.workspace_id ?? null,
objectId: id.object_id ?? null,
recordId: id.record_id ?? null,
attributeId: id.attribute_id ?? null,
}
}
/**
* Extracts formatted data from an Attio record.merged event payload.
*/
export function extractAttioRecordMergedData(
body: Record<string, unknown>
): Record<string, unknown> {
const event = getAttioEvent(body) ?? {}
const id = (event.id as Record<string, unknown>) ?? {}
return {
eventType: event.event_type ?? null,
workspaceId: id.workspace_id ?? null,
objectId: id.object_id ?? null,
recordId: id.record_id ?? null,
duplicateObjectId: (event.duplicate_object_id as string) ?? null,
duplicateRecordId: (event.duplicate_record_id as string) ?? null,
}
}
/**
* Extracts formatted data from an Attio note event payload.
*/
export function extractAttioNoteData(body: Record<string, unknown>): Record<string, unknown> {
const event = getAttioEvent(body) ?? {}
const id = (event.id as Record<string, unknown>) ?? {}
return {
eventType: event.event_type ?? null,
workspaceId: id.workspace_id ?? null,
noteId: id.note_id ?? null,
parentObjectId: (event.parent_object_id as string) ?? null,
parentRecordId: (event.parent_record_id as string) ?? null,
}
}
/**
* Extracts formatted data from an Attio task event payload.
*/
export function extractAttioTaskData(body: Record<string, unknown>): Record<string, unknown> {
const event = getAttioEvent(body) ?? {}
const id = (event.id as Record<string, unknown>) ?? {}
return {
eventType: event.event_type ?? null,
workspaceId: id.workspace_id ?? null,
taskId: id.task_id ?? null,
}
}
/**
* Extracts formatted data from an Attio comment event payload.
*/
export function extractAttioCommentData(body: Record<string, unknown>): Record<string, unknown> {
const event = getAttioEvent(body) ?? {}
const id = (event.id as Record<string, unknown>) ?? {}
return {
eventType: event.event_type ?? null,
workspaceId: id.workspace_id ?? null,
threadId: (event.thread_id as string) ?? null,
commentId: id.comment_id ?? null,
objectId: (event.object_id as string) ?? null,
recordId: (event.record_id as string) ?? null,
listId: (event.list_id as string) ?? null,
entryId: (event.entry_id as string) ?? null,
}
}
/**
* Extracts formatted data from an Attio list-entry event payload.
* Used for list-entry.created, list-entry.deleted triggers.
*/
export function extractAttioListEntryData(body: Record<string, unknown>): Record<string, unknown> {
const event = getAttioEvent(body) ?? {}
const id = (event.id as Record<string, unknown>) ?? {}
return {
eventType: event.event_type ?? null,
workspaceId: id.workspace_id ?? null,
listId: id.list_id ?? null,
entryId: id.entry_id ?? null,
}
}
/**
* Extracts formatted data from an Attio list-entry.updated event payload.
*/
export function extractAttioListEntryUpdatedData(
body: Record<string, unknown>
): Record<string, unknown> {
const event = getAttioEvent(body) ?? {}
const id = (event.id as Record<string, unknown>) ?? {}
return {
eventType: event.event_type ?? null,
workspaceId: id.workspace_id ?? null,
listId: id.list_id ?? null,
entryId: id.entry_id ?? null,
attributeId: id.attribute_id ?? null,
}
}
/**
* Extracts formatted data from an Attio list event payload.
* Used for list.created, list.updated, list.deleted triggers.
*/
export function extractAttioListData(body: Record<string, unknown>): Record<string, unknown> {
const event = getAttioEvent(body) ?? {}
const id = (event.id as Record<string, unknown>) ?? {}
return {
eventType: event.event_type ?? null,
workspaceId: id.workspace_id ?? null,
listId: id.list_id ?? null,
}
}
/**
* Extracts formatted data from an Attio workspace-member.created event payload.
*/
export function extractAttioWorkspaceMemberData(
body: Record<string, unknown>
): Record<string, unknown> {
const event = getAttioEvent(body) ?? {}
const id = (event.id as Record<string, unknown>) ?? {}
return {
eventType: event.event_type ?? null,
workspaceId: id.workspace_id ?? null,
workspaceMemberId: id.workspace_member_id ?? null,
}
}
/**
* Extracts formatted data from a generic Attio webhook payload.
* Passes through the first event with camelCase field mapping.
*/
export function extractAttioGenericData(body: Record<string, unknown>): Record<string, unknown> {
const event = getAttioEvent(body) ?? {}
const id = (event.id as Record<string, unknown>) ?? {}
return {
eventType: event.event_type ?? null,
id,
parentObjectId: (event.parent_object_id as string) ?? null,
parentRecordId: (event.parent_record_id as string) ?? null,
}
}