Files
simstudioai--sim/apps/sim/triggers/index.ts
T
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

241 lines
7.4 KiB
TypeScript

import { generateMockPayloadFromOutputsDefinition } from '@/lib/workflows/triggers/trigger-utils'
import type { SubBlockConfig } from '@/blocks/types'
import { TRIGGER_REGISTRY } from '@/triggers/registry'
import type { TriggerConfig } from '@/triggers/types'
/**
* IDs that should NOT be namespaced because they are shared across triggers
* or are the control mechanism for trigger selection
*/
const SHARED_SUBBLOCK_IDS = new Set(['selectedTriggerId'])
/**
* Checks if a subBlock is display-only (not user-editable).
* Display-only subBlocks should be namespaced to avoid conflicts when
* multiple triggers show different content for the same conceptual field.
*/
function isDisplayOnlySubBlock(subBlock: SubBlockConfig): boolean {
// Text type is always display-only
if (subBlock.type === 'text') {
return true
}
// ReadOnly inputs are display-only
if (subBlock.readOnly === true) {
return true
}
return false
}
/**
* Namespaces a subBlock ID with the trigger ID to avoid conflicts when
* multiple triggers are merged into a single block.
*
* Only namespaces display-only subBlocks (readOnly or text type) that have
* a condition on selectedTriggerId. User-input fields are NOT namespaced
* so their values persist when switching between triggers.
*/
function namespaceSubBlockId(subBlock: SubBlockConfig, triggerId: string): SubBlockConfig {
// Don't namespace shared IDs
if (SHARED_SUBBLOCK_IDS.has(subBlock.id)) {
return subBlock
}
// Only namespace display-only subBlocks to avoid content conflicts
// User-input fields should remain shared so values persist across trigger switches
if (!isDisplayOnlySubBlock(subBlock)) {
return subBlock
}
// Only namespace if the subBlock has a condition on selectedTriggerId
// These are the ones that are trigger-specific and will conflict when merged
const condition =
typeof subBlock.condition === 'function' ? subBlock.condition() : subBlock.condition
if (condition?.field === 'selectedTriggerId') {
return {
...subBlock,
id: `${subBlock.id}_${triggerId}`,
}
}
return subBlock
}
/**
* Gets a trigger config and injects samplePayload subblock with condition.
* Also namespaces subBlock IDs to avoid conflicts when multiple triggers
* are merged into a single block (e.g., ...getTrigger('a').subBlocks, ...getTrigger('b').subBlocks).
*/
export function getTrigger(triggerId: string): TriggerConfig {
const trigger = TRIGGER_REGISTRY[triggerId]
if (!trigger) {
throw new Error(`Trigger not found: ${triggerId}`)
}
// Filter out deprecated trigger-save subblocks from legacy stored data
const subBlocks = trigger.subBlocks
.filter(
(subBlock) => subBlock.id !== 'triggerSave' && (subBlock.type as string) !== 'trigger-save'
)
.map((subBlock) => namespaceSubBlockId(subBlock, triggerId))
const clonedTrigger = { ...trigger, subBlocks }
// Inject samplePayload for webhooks/pollers with condition
if (trigger.webhook || trigger.id.includes('webhook') || trigger.id.includes('poller')) {
const samplePayloadExists = clonedTrigger.subBlocks.some(
(sb) => sb.id === 'samplePayload' || sb.id === `samplePayload_${triggerId}`
)
if (!samplePayloadExists && trigger.outputs) {
const mockPayload = generateMockPayloadFromOutputsDefinition(trigger.outputs)
const generatedPayload = JSON.stringify(mockPayload, null, 2)
const samplePayloadSubBlock: SubBlockConfig = {
id: `samplePayload_${triggerId}`,
title: 'Event Payload Example',
type: 'code',
language: 'json',
defaultValue: generatedPayload,
readOnly: true,
collapsible: true,
defaultCollapsed: true,
hideFromPreview: true,
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: trigger.id,
},
}
clonedTrigger.subBlocks.push(samplePayloadSubBlock)
}
}
return clonedTrigger
}
export function getTriggersByProvider(provider: string): TriggerConfig[] {
return Object.values(TRIGGER_REGISTRY)
.filter((trigger) => trigger.provider === provider)
.map((trigger) => getTrigger(trigger.id))
}
export function getAllTriggers(): TriggerConfig[] {
return Object.keys(TRIGGER_REGISTRY).map((triggerId) => getTrigger(triggerId))
}
export function getTriggerIds(): string[] {
return Object.keys(TRIGGER_REGISTRY)
}
export function isTriggerValid(triggerId: string): boolean {
return triggerId in TRIGGER_REGISTRY
}
export type { TriggerConfig } from '@/triggers/types'
/**
* Options for building trigger subBlocks
*/
export interface BuildTriggerSubBlocksOptions {
/** The trigger ID (e.g., 'lemlist_email_replied') */
triggerId: string
/** Dropdown options for selecting trigger type */
triggerOptions: Array<{ label: string; id: string }>
/** Whether to include the trigger type dropdown (only for primary trigger) */
includeDropdown?: boolean
/** HTML setup instructions to display */
setupInstructions: string
/** Additional fields to insert before the save button (e.g., campaign filters) */
extraFields?: SubBlockConfig[]
/** Webhook URL placeholder text */
webhookPlaceholder?: string
}
/**
* Generic builder for trigger subBlocks.
* Creates a consistent structure: [dropdown?] -> webhookUrl -> extraFields -> instructions
*
* Usage:
* - Primary trigger: `buildTriggerSubBlocks({ ...options, includeDropdown: true })`
* - Secondary triggers: `buildTriggerSubBlocks({ ...options })` (no dropdown)
*
* @example
* ```typescript
* // Primary trigger (with dropdown)
* subBlocks: buildTriggerSubBlocks({
* triggerId: 'service_event_a',
* triggerOptions: serviceTriggerOptions,
* includeDropdown: true,
* setupInstructions: serviceSetupInstructions('eventA'),
* })
*
* // Secondary trigger (no dropdown)
* subBlocks: buildTriggerSubBlocks({
* triggerId: 'service_event_b',
* triggerOptions: serviceTriggerOptions,
* setupInstructions: serviceSetupInstructions('eventB'),
* })
* ```
*/
export function buildTriggerSubBlocks(options: BuildTriggerSubBlocksOptions): SubBlockConfig[] {
const {
triggerId,
triggerOptions,
includeDropdown = false,
setupInstructions,
extraFields = [],
webhookPlaceholder = 'Webhook URL will be generated',
} = options
const blocks: SubBlockConfig[] = []
// Only the primary trigger includes the dropdown
if (includeDropdown) {
blocks.push({
id: 'selectedTriggerId',
title: 'Trigger Type',
type: 'dropdown',
mode: 'trigger',
options: triggerOptions,
value: () => triggerId,
required: true,
})
}
// Webhook URL display (common to all triggers)
// ID will be namespaced by getTrigger() when merged into blocks
blocks.push({
id: 'webhookUrlDisplay',
title: 'Webhook URL',
type: 'short-input',
readOnly: true,
showCopyButton: true,
useWebhookUrl: true,
placeholder: webhookPlaceholder,
mode: 'trigger',
condition: { field: 'selectedTriggerId', value: triggerId },
})
// Insert any extra fields (campaign filters, event types, etc.)
if (extraFields.length > 0) {
blocks.push(...extraFields)
}
// Setup instructions
// ID will be namespaced by getTrigger() when merged into blocks
blocks.push({
id: 'triggerInstructions',
title: 'Setup Instructions',
hideFromPreview: true,
type: 'text',
defaultValue: setupInstructions,
mode: 'trigger',
condition: { field: 'selectedTriggerId', value: triggerId },
})
return blocks
}