Skip to content

Commit cbfab1c

Browse files
authored
v0.6.36: new chunkers, sockets state machine, google sheets/drive/calendar triggers, docs updates, integrations/models pages improvements
2 parents 4f40c4c + 1acafe8 commit cbfab1c

File tree

135 files changed

+9227
-2564
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

135 files changed

+9227
-2564
lines changed

.claude/commands/add-trigger.md

Lines changed: 151 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
---
2-
description: Create webhook triggers for a Sim integration using the generic trigger builder
2+
description: Create webhook or polling triggers for a Sim integration
33
argument-hint: <service-name>
44
---
55

66
# Add Trigger
77

8-
You are an expert at creating webhook triggers for Sim. You understand the trigger system, the generic `buildTriggerSubBlocks` helper, and how triggers connect to blocks.
8+
You are an expert at creating webhook and polling triggers for Sim. You understand the trigger system, the generic `buildTriggerSubBlocks` helper, polling infrastructure, and how triggers connect to blocks.
99

1010
## Your Task
1111

12-
1. Research what webhook events the service supports
13-
2. Create the trigger files using the generic builder
14-
3. Create a provider handler if custom auth, formatting, or subscriptions are needed
12+
1. Research what webhook events the service supports — if the service lacks reliable webhooks, use polling
13+
2. Create the trigger files using the generic builder (webhook) or manual config (polling)
14+
3. Create a provider handler (webhook) or polling handler (polling)
1515
4. Register triggers and connect them to the block
1616

1717
## Directory Structure
@@ -146,23 +146,37 @@ export const TRIGGER_REGISTRY: TriggerRegistry = {
146146

147147
### Block file (`apps/sim/blocks/blocks/{service}.ts`)
148148

149+
Wire triggers into the block so the trigger UI appears and `generate-docs.ts` discovers them. Two changes are needed:
150+
151+
1. **Spread trigger subBlocks** at the end of the block's `subBlocks` array
152+
2. **Add `triggers` property** after `outputs` with `enabled: true` and `available: [...]`
153+
149154
```typescript
150155
import { getTrigger } from '@/triggers'
151156

152157
export const {Service}Block: BlockConfig = {
153158
// ...
154-
triggers: {
155-
enabled: true,
156-
available: ['{service}_event_a', '{service}_event_b'],
157-
},
158159
subBlocks: [
159160
// Regular tool subBlocks first...
160161
...getTrigger('{service}_event_a').subBlocks,
161162
...getTrigger('{service}_event_b').subBlocks,
162163
],
164+
// ... tools, inputs, outputs ...
165+
triggers: {
166+
enabled: true,
167+
available: ['{service}_event_a', '{service}_event_b'],
168+
},
163169
}
164170
```
165171

172+
**Versioned blocks (V1 + V2):** Many integrations have a hidden V1 block and a visible V2 block. Where you add the trigger wiring depends on how V2 inherits from V1:
173+
174+
- **V2 uses `...V1Block` spread** (e.g., Google Calendar): Add trigger to V1 — V2 inherits both `subBlocks` and `triggers` automatically.
175+
- **V2 defines its own `subBlocks`** (e.g., Google Sheets): Add trigger to V2 (the visible block). V1 is hidden and doesn't need it.
176+
- **Single block, no V2** (e.g., Google Drive): Add trigger directly.
177+
178+
`generate-docs.ts` deduplicates by base type (first match wins). If V1 is processed first without triggers, the V2 triggers won't appear in `integrations.json`. Always verify by checking the output after running the script.
179+
166180
## Provider Handler
167181

168182
All provider-specific webhook logic lives in a single handler file: `apps/sim/lib/webhooks/providers/{service}.ts`.
@@ -327,6 +341,122 @@ export function buildOutputs(): Record<string, TriggerOutput> {
327341
}
328342
```
329343

344+
## Polling Triggers
345+
346+
Use polling when the service lacks reliable webhooks (e.g., Google Sheets, Google Drive, Google Calendar, Gmail, RSS, IMAP). Polling triggers do NOT use `buildTriggerSubBlocks` — they define subBlocks manually.
347+
348+
### Directory Structure
349+
350+
```
351+
apps/sim/triggers/{service}/
352+
├── index.ts # Barrel export
353+
└── poller.ts # TriggerConfig with polling: true
354+
355+
apps/sim/lib/webhooks/polling/
356+
└── {service}.ts # PollingProviderHandler implementation
357+
```
358+
359+
### Polling Handler (`apps/sim/lib/webhooks/polling/{service}.ts`)
360+
361+
```typescript
362+
import { pollingIdempotency } from '@/lib/core/idempotency/service'
363+
import type { PollingProviderHandler, PollWebhookContext } from '@/lib/webhooks/polling/types'
364+
import { markWebhookFailed, markWebhookSuccess, resolveOAuthCredential, updateWebhookProviderConfig } from '@/lib/webhooks/polling/utils'
365+
import { processPolledWebhookEvent } from '@/lib/webhooks/processor'
366+
367+
export const {service}PollingHandler: PollingProviderHandler = {
368+
provider: '{service}',
369+
label: '{Service}',
370+
371+
async pollWebhook(ctx: PollWebhookContext): Promise<'success' | 'failure'> {
372+
const { webhookData, workflowData, requestId, logger } = ctx
373+
const webhookId = webhookData.id
374+
375+
try {
376+
// For OAuth services:
377+
const accessToken = await resolveOAuthCredential(webhookData, '{service}', requestId, logger)
378+
const config = webhookData.providerConfig as unknown as {Service}WebhookConfig
379+
380+
// First poll: seed state, emit nothing
381+
if (!config.lastCheckedTimestamp) {
382+
await updateWebhookProviderConfig(webhookId, { lastCheckedTimestamp: new Date().toISOString() }, logger)
383+
await markWebhookSuccess(webhookId, logger)
384+
return 'success'
385+
}
386+
387+
// Fetch changes since last poll, process with idempotency
388+
// ...
389+
390+
await markWebhookSuccess(webhookId, logger)
391+
return 'success'
392+
} catch (error) {
393+
logger.error(`[${requestId}] Error processing {service} webhook ${webhookId}:`, error)
394+
await markWebhookFailed(webhookId, logger)
395+
return 'failure'
396+
}
397+
},
398+
}
399+
```
400+
401+
**Key patterns:**
402+
- First poll seeds state and emits nothing (avoids flooding with existing data)
403+
- Use `pollingIdempotency.executeWithIdempotency(provider, key, callback)` for dedup
404+
- Use `processPolledWebhookEvent(webhookData, workflowData, payload, requestId)` to fire the workflow
405+
- Use `updateWebhookProviderConfig(webhookId, partialConfig, logger)` for read-merge-write on state
406+
- Use the latest server-side timestamp from API responses (not wall clock) to avoid clock skew
407+
408+
### Trigger Config (`apps/sim/triggers/{service}/poller.ts`)
409+
410+
```typescript
411+
import { {Service}Icon } from '@/components/icons'
412+
import type { TriggerConfig } from '@/triggers/types'
413+
414+
export const {service}PollingTrigger: TriggerConfig = {
415+
id: '{service}_poller',
416+
name: '{Service} Trigger',
417+
provider: '{service}',
418+
description: 'Triggers when ...',
419+
version: '1.0.0',
420+
icon: {Service}Icon,
421+
polling: true, // REQUIRED — routes to polling infrastructure
422+
423+
subBlocks: [
424+
{ id: 'triggerCredentials', type: 'oauth-input', title: 'Credentials', serviceId: '{service}', requiredScopes: [], required: true, mode: 'trigger', supportsCredentialSets: true },
425+
// ... service-specific config fields (dropdowns, inputs, switches) ...
426+
{ id: 'triggerSave', type: 'trigger-save', title: '', hideFromPreview: true, mode: 'trigger', triggerId: '{service}_poller' },
427+
{ id: 'triggerInstructions', type: 'text', title: 'Setup Instructions', hideFromPreview: true, mode: 'trigger', defaultValue: '...' },
428+
],
429+
430+
outputs: {
431+
// Must match the payload shape from processPolledWebhookEvent
432+
},
433+
}
434+
```
435+
436+
### Registration (3 places)
437+
438+
1. **`apps/sim/triggers/constants.ts`** — add provider to `POLLING_PROVIDERS` Set
439+
2. **`apps/sim/lib/webhooks/polling/registry.ts`** — import handler, add to `POLLING_HANDLERS`
440+
3. **`apps/sim/triggers/registry.ts`** — import trigger config, add to `TRIGGER_REGISTRY`
441+
442+
### Helm Cron Job
443+
444+
Add to `helm/sim/values.yaml` under the existing polling cron jobs:
445+
446+
```yaml
447+
{service}WebhookPoll:
448+
schedule: "*/1 * * * *"
449+
concurrencyPolicy: Forbid
450+
url: "http://sim:3000/api/webhooks/poll/{service}"
451+
```
452+
453+
### Reference Implementations
454+
455+
- Simple: `apps/sim/lib/webhooks/polling/rss.ts` + `apps/sim/triggers/rss/poller.ts`
456+
- Complex (OAuth, attachments): `apps/sim/lib/webhooks/polling/gmail.ts` + `apps/sim/triggers/gmail/poller.ts`
457+
- Cursor-based (changes API): `apps/sim/lib/webhooks/polling/google-drive.ts`
458+
- Timestamp-based: `apps/sim/lib/webhooks/polling/google-calendar.ts`
459+
330460
## Checklist
331461

332462
### Trigger Definition
@@ -352,7 +482,18 @@ export function buildOutputs(): Record<string, TriggerOutput> {
352482
- [ ] NO changes to `route.ts`, `provider-subscriptions.ts`, or `deploy.ts`
353483
- [ ] API key field uses `password: true`
354484

485+
### Polling Trigger (if applicable)
486+
- [ ] Handler implements `PollingProviderHandler` at `lib/webhooks/polling/{service}.ts`
487+
- [ ] Trigger config has `polling: true` and defines subBlocks manually (no `buildTriggerSubBlocks`)
488+
- [ ] Provider string matches across: trigger config, handler, `POLLING_PROVIDERS`, polling registry
489+
- [ ] `triggerSave` subBlock `triggerId` matches trigger config `id`
490+
- [ ] First poll seeds state and emits nothing
491+
- [ ] Added provider to `POLLING_PROVIDERS` in `triggers/constants.ts`
492+
- [ ] Added handler to `POLLING_HANDLERS` in `lib/webhooks/polling/registry.ts`
493+
- [ ] Added cron job to `helm/sim/values.yaml`
494+
- [ ] Payload shape matches trigger `outputs` schema
495+
355496
### Testing
356497
- [ ] `bun run type-check` passes
357-
- [ ] Manually verify `formatInput` output keys match trigger `outputs` keys
498+
- [ ] Manually verify output keys match trigger `outputs` keys
358499
- [ ] Trigger UI shows correctly in the block

0 commit comments

Comments
 (0)