@notionhq/apps 0.0.12 → 0.0.14

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.
Files changed (137) hide show
  1. package/README.md +32 -0
  2. package/dist/box.generated.d.ts +164 -0
  3. package/dist/box.generated.d.ts.map +1 -0
  4. package/dist/box.generated.js +24 -0
  5. package/dist/calendar.generated.d.ts +1036 -0
  6. package/dist/calendar.generated.d.ts.map +1 -0
  7. package/dist/calendar.generated.js +42 -0
  8. package/dist/cli/build.d.ts.map +1 -1
  9. package/dist/cli/build.js +41 -10
  10. package/dist/cli/codegen.d.ts +2 -0
  11. package/dist/cli/codegen.d.ts.map +1 -1
  12. package/dist/cli/codegen.js +13 -2
  13. package/dist/cli/discover.d.ts +2 -0
  14. package/dist/cli/discover.d.ts.map +1 -1
  15. package/dist/cli/discover.js +4 -2
  16. package/dist/cli/emit-manifest.d.ts +3 -1
  17. package/dist/cli/emit-manifest.d.ts.map +1 -1
  18. package/dist/cli/emit-manifest.js +10 -4
  19. package/dist/cli/index.js +33 -1
  20. package/dist/confluence.generated.d.ts +98 -0
  21. package/dist/confluence.generated.d.ts.map +1 -0
  22. package/dist/confluence.generated.js +18 -0
  23. package/dist/connection-actions.d.ts +2 -0
  24. package/dist/connection-actions.d.ts.map +1 -0
  25. package/dist/connection-actions.js +0 -0
  26. package/dist/connection-trigger-definitions.generated.d.ts +34 -0
  27. package/dist/connection-trigger-definitions.generated.d.ts.map +1 -0
  28. package/dist/connection-trigger-definitions.generated.js +45 -0
  29. package/dist/connections.d.ts +15 -0
  30. package/dist/connections.d.ts.map +1 -0
  31. package/dist/connections.js +79 -0
  32. package/dist/connections.test.d.ts +2 -0
  33. package/dist/connections.test.d.ts.map +1 -0
  34. package/dist/cursor.generated.d.ts +154 -0
  35. package/dist/cursor.generated.d.ts.map +1 -0
  36. package/dist/cursor.generated.js +18 -0
  37. package/dist/custom-block.d.ts +54 -0
  38. package/dist/custom-block.d.ts.map +1 -0
  39. package/dist/custom-block.js +30 -0
  40. package/dist/custom-block.test.d.ts +2 -0
  41. package/dist/custom-block.test.d.ts.map +1 -0
  42. package/dist/custom-blocks.d.ts +2 -0
  43. package/dist/custom-blocks.d.ts.map +1 -0
  44. package/dist/custom-blocks.js +1 -0
  45. package/dist/discord.generated.d.ts +279 -0
  46. package/dist/discord.generated.d.ts.map +1 -0
  47. package/dist/discord.generated.js +33 -0
  48. package/dist/gmail.generated.d.ts +184 -0
  49. package/dist/gmail.generated.d.ts.map +1 -0
  50. package/dist/gmail.generated.js +21 -0
  51. package/dist/googleCalendar.generated.d.ts +83 -0
  52. package/dist/googleCalendar.generated.d.ts.map +1 -0
  53. package/dist/googleCalendar.generated.js +15 -0
  54. package/dist/googleDrive.generated.d.ts +210 -0
  55. package/dist/googleDrive.generated.d.ts.map +1 -0
  56. package/dist/googleDrive.generated.js +27 -0
  57. package/dist/googleDriveOauth.generated.d.ts +851 -0
  58. package/dist/googleDriveOauth.generated.d.ts.map +1 -0
  59. package/dist/googleDriveOauth.generated.js +84 -0
  60. package/dist/manifest.d.ts +1 -1
  61. package/dist/manifest.d.ts.map +1 -1
  62. package/dist/nds.css +1 -0
  63. package/dist/notion-as-code/database.d.ts +4 -3
  64. package/dist/notion-as-code/database.d.ts.map +1 -1
  65. package/dist/notion-as-code/database.js +11 -2
  66. package/dist/notion-as-code/intents.d.ts +4 -1
  67. package/dist/notion-as-code/intents.d.ts.map +1 -1
  68. package/dist/notion-as-code/page.d.ts +4 -4
  69. package/dist/notion-as-code/page.d.ts.map +1 -1
  70. package/dist/notion-as-code/page.js +11 -2
  71. package/dist/outlook.generated.d.ts +224 -0
  72. package/dist/outlook.generated.d.ts.map +1 -0
  73. package/dist/outlook.generated.js +24 -0
  74. package/dist/providers.generated.d.ts +117 -0
  75. package/dist/providers.generated.d.ts.map +1 -0
  76. package/dist/providers.generated.js +108 -0
  77. package/dist/react.d.ts +2 -0
  78. package/dist/react.d.ts.map +1 -0
  79. package/dist/react.js +1 -0
  80. package/dist/salesforce.generated.d.ts +151 -0
  81. package/dist/salesforce.generated.d.ts.map +1 -0
  82. package/dist/salesforce.generated.js +24 -0
  83. package/dist/slack.generated.d.ts +290 -0
  84. package/dist/slack.generated.d.ts.map +1 -0
  85. package/dist/slack.generated.js +30 -0
  86. package/dist/triggers.generated.d.ts +107 -32
  87. package/dist/triggers.generated.d.ts.map +1 -1
  88. package/dist/triggers.generated.js +36 -24
  89. package/dist/workflow-connections-types.test.d.ts +2 -0
  90. package/dist/workflow-connections-types.test.d.ts.map +1 -0
  91. package/dist/workflow-types.test.d.ts +2 -0
  92. package/dist/workflow-types.test.d.ts.map +1 -0
  93. package/dist/workflow.d.ts +20 -16
  94. package/dist/workflow.d.ts.map +1 -1
  95. package/dist/workflow.js +14 -17
  96. package/docs/BUILD.md +83 -14
  97. package/docs/CONNECTIONS.md +129 -0
  98. package/package.json +16 -2
  99. package/src/box.generated.ts +187 -0
  100. package/src/calendar.generated.ts +1117 -0
  101. package/src/cli/build.test.ts +87 -0
  102. package/src/cli/build.ts +47 -9
  103. package/src/cli/codegen.test.ts +2 -0
  104. package/src/cli/codegen.ts +25 -7
  105. package/src/cli/discover.test.ts +12 -0
  106. package/src/cli/discover.ts +6 -2
  107. package/src/cli/emit-manifest.ts +13 -3
  108. package/src/cli/index.ts +33 -1
  109. package/src/confluence.generated.ts +109 -0
  110. package/src/connection-actions.ts +2 -0
  111. package/src/connection-trigger-definitions.generated.ts +48 -0
  112. package/src/connections.test.ts +157 -0
  113. package/src/connections.ts +126 -0
  114. package/src/cursor.generated.ts +167 -0
  115. package/src/custom-block.test.ts +19 -0
  116. package/src/custom-block.ts +41 -0
  117. package/src/custom-blocks.ts +1 -0
  118. package/src/discord.generated.ts +318 -0
  119. package/src/gmail.generated.ts +205 -0
  120. package/src/googleCalendar.generated.ts +95 -0
  121. package/src/googleDrive.generated.ts +235 -0
  122. package/src/googleDriveOauth.generated.ts +980 -0
  123. package/src/manifest.ts +1 -1
  124. package/src/nds.css +1 -0
  125. package/src/notion-as-code/database.ts +19 -11
  126. package/src/notion-as-code/intents.ts +2 -1
  127. package/src/notion-as-code/page.ts +17 -11
  128. package/src/outlook.generated.ts +249 -0
  129. package/src/providers.generated.ts +207 -0
  130. package/src/react.ts +1 -0
  131. package/src/salesforce.generated.ts +170 -0
  132. package/src/slack.generated.ts +330 -0
  133. package/src/triggers.generated.ts +112 -32
  134. package/src/workflow-connections-types.test.ts +59 -0
  135. package/src/workflow-types.test.ts +144 -0
  136. package/src/workflow.test.ts +112 -2
  137. package/src/workflow.ts +58 -29
@@ -1,6 +1,8 @@
1
1
  // Generated by scripts/generate-triggers.ts — do not edit by hand.
2
2
  // Regenerate with: pnpm run generate
3
3
 
4
+ import type { WorkflowConnection, WorkflowConnectionKeys } from "./connections.js";
5
+
4
6
  import type {
5
7
  CalendarEventCanceledEvent,
6
8
  CalendarEventCreatedEvent,
@@ -72,24 +74,32 @@ export type {
72
74
  WebhookEvent,
73
75
  } from "./triggers.js";
74
76
 
75
- /** A message was posted in a subscribed Slack channel. */
76
- export type SlackMessageTrigger = {
77
+ /** Triggers immediately after a new message is posted in Slack (includes new threads and replies) */
78
+ export type SlackMessageTrigger<TKey extends string = string> = {
77
79
  type: "slack.message";
80
+ /** Key of the declared slack connection. */
81
+ connectionKey?: TKey;
78
82
  };
79
83
 
80
- /** A reaction was added to a Slack message. */
81
- export type SlackReactionAddedTrigger = {
84
+ /** Triggers immediately after a reaction is added to a message in Slack */
85
+ export type SlackReactionAddedTrigger<TKey extends string = string> = {
82
86
  type: "slack.reaction.added";
87
+ /** Key of the declared slack connection. */
88
+ connectionKey?: TKey;
83
89
  };
84
90
 
85
- /** The Notion app was mentioned in a Slack message. */
86
- export type SlackAppMentionTrigger = {
91
+ /** Triggers when the agent is mentioned in a Slack channel or direct message */
92
+ export type SlackAppMentionTrigger<TKey extends string = string> = {
87
93
  type: "slack.app.mention";
94
+ /** Key of the declared slack connection. */
95
+ connectionKey?: TKey;
88
96
  };
89
97
 
90
- /** A Discord interaction was received. */
91
- export type DiscordInteractionTrigger = {
98
+ /** Triggers when a user invokes the /ai slash command in a configured Discord channel */
99
+ export type DiscordInteractionTrigger<TKey extends string = string> = {
92
100
  type: "discord.interaction";
101
+ /** Key of the declared discord connection. */
102
+ connectionKey?: TKey;
93
103
  };
94
104
 
95
105
  /** An email was received. */
@@ -107,19 +117,25 @@ export type MailLabelAppliedTrigger = {
107
117
  type: "mail.label.applied";
108
118
  };
109
119
 
110
- /** A calendar event was created. */
111
- export type CalendarEventCreatedTrigger = {
120
+ /** Triggers immediately when a calendar event is created */
121
+ export type CalendarEventCreatedTrigger<TKey extends string = string> = {
112
122
  type: "calendar.event.created";
123
+ /** Key of the declared calendar connection. */
124
+ connectionKey?: TKey;
113
125
  };
114
126
 
115
- /** A calendar event was updated. */
116
- export type CalendarEventUpdatedTrigger = {
127
+ /** Triggers immediately when a calendar event is updated */
128
+ export type CalendarEventUpdatedTrigger<TKey extends string = string> = {
117
129
  type: "calendar.event.updated";
130
+ /** Key of the declared calendar connection. */
131
+ connectionKey?: TKey;
118
132
  };
119
133
 
120
- /** A calendar event was canceled. */
121
- export type CalendarEventCanceledTrigger = {
134
+ /** Triggers immediately when a calendar event is canceled */
135
+ export type CalendarEventCanceledTrigger<TKey extends string = string> = {
122
136
  type: "calendar.event.canceled";
137
+ /** Key of the declared calendar connection. */
138
+ connectionKey?: TKey;
123
139
  };
124
140
 
125
141
  /** A page has been added to a database. */
@@ -167,9 +183,11 @@ export type WebhookTrigger = {
167
183
  type: "webhooks.webhook";
168
184
  };
169
185
 
170
- /** Files changed in a watched Google Drive folder or drive. */
171
- export type GoogleDriveOauthFilesChangedTrigger = {
186
+ /** Watches for file changes in a specific Google Drive folder (direct children only) or shared drive. The My Drive root is not supported. Fires on a schedule and provides change tokens for fetching the list of changes. */
187
+ export type GoogleDriveOauthFilesChangedTrigger<TKey extends string = string> = {
172
188
  type: "googleDriveOauth.filesChanged";
189
+ /** Key of the declared googleDriveOauth connection. */
190
+ connectionKey?: TKey;
173
191
  };
174
192
 
175
193
  /** A recurring schedule fired. */
@@ -182,44 +200,48 @@ export const triggers = {
182
200
  /**
183
201
  * Declare that a workflow can run on `slack.message` events.
184
202
  *
185
- * A message was posted in a subscribed Slack channel.
203
+ * Triggers immediately after a new message is posted in Slack (includes new threads and replies)
186
204
  */
187
- slackMessage(): SlackMessageTrigger {
205
+ slackMessage<const TKey extends string = never>(options: { connectionKey: TKey } | undefined = undefined): SlackMessageTrigger<TKey> {
188
206
  return {
189
207
  type: "slack.message",
208
+ ...(options === undefined ? {} : { connectionKey: options.connectionKey }),
190
209
  };
191
210
  },
192
211
 
193
212
  /**
194
213
  * Declare that a workflow can run on `slack.reaction.added` events.
195
214
  *
196
- * A reaction was added to a Slack message.
215
+ * Triggers immediately after a reaction is added to a message in Slack
197
216
  */
198
- slackReactionAdded(): SlackReactionAddedTrigger {
217
+ slackReactionAdded<const TKey extends string = never>(options: { connectionKey: TKey } | undefined = undefined): SlackReactionAddedTrigger<TKey> {
199
218
  return {
200
219
  type: "slack.reaction.added",
220
+ ...(options === undefined ? {} : { connectionKey: options.connectionKey }),
201
221
  };
202
222
  },
203
223
 
204
224
  /**
205
225
  * Declare that a workflow can run on `slack.app.mention` events.
206
226
  *
207
- * The Notion app was mentioned in a Slack message.
227
+ * Triggers when the agent is mentioned in a Slack channel or direct message
208
228
  */
209
- slackAppMention(): SlackAppMentionTrigger {
229
+ slackAppMention<const TKey extends string = never>(options: { connectionKey: TKey } | undefined = undefined): SlackAppMentionTrigger<TKey> {
210
230
  return {
211
231
  type: "slack.app.mention",
232
+ ...(options === undefined ? {} : { connectionKey: options.connectionKey }),
212
233
  };
213
234
  },
214
235
 
215
236
  /**
216
237
  * Declare that a workflow can run on `discord.interaction` events.
217
238
  *
218
- * A Discord interaction was received.
239
+ * Triggers when a user invokes the /ai slash command in a configured Discord channel
219
240
  */
220
- discordInteraction(): DiscordInteractionTrigger {
241
+ discordInteraction<const TKey extends string = never>(options: { connectionKey: TKey } | undefined = undefined): DiscordInteractionTrigger<TKey> {
221
242
  return {
222
243
  type: "discord.interaction",
244
+ ...(options === undefined ? {} : { connectionKey: options.connectionKey }),
223
245
  };
224
246
  },
225
247
 
@@ -259,33 +281,36 @@ export const triggers = {
259
281
  /**
260
282
  * Declare that a workflow can run on `calendar.event.created` events.
261
283
  *
262
- * A calendar event was created.
284
+ * Triggers immediately when a calendar event is created
263
285
  */
264
- calendarEventCreated(): CalendarEventCreatedTrigger {
286
+ calendarEventCreated<const TKey extends string = never>(options: { connectionKey: TKey } | undefined = undefined): CalendarEventCreatedTrigger<TKey> {
265
287
  return {
266
288
  type: "calendar.event.created",
289
+ ...(options === undefined ? {} : { connectionKey: options.connectionKey }),
267
290
  };
268
291
  },
269
292
 
270
293
  /**
271
294
  * Declare that a workflow can run on `calendar.event.updated` events.
272
295
  *
273
- * A calendar event was updated.
296
+ * Triggers immediately when a calendar event is updated
274
297
  */
275
- calendarEventUpdated(): CalendarEventUpdatedTrigger {
298
+ calendarEventUpdated<const TKey extends string = never>(options: { connectionKey: TKey } | undefined = undefined): CalendarEventUpdatedTrigger<TKey> {
276
299
  return {
277
300
  type: "calendar.event.updated",
301
+ ...(options === undefined ? {} : { connectionKey: options.connectionKey }),
278
302
  };
279
303
  },
280
304
 
281
305
  /**
282
306
  * Declare that a workflow can run on `calendar.event.canceled` events.
283
307
  *
284
- * A calendar event was canceled.
308
+ * Triggers immediately when a calendar event is canceled
285
309
  */
286
- calendarEventCanceled(): CalendarEventCanceledTrigger {
310
+ calendarEventCanceled<const TKey extends string = never>(options: { connectionKey: TKey } | undefined = undefined): CalendarEventCanceledTrigger<TKey> {
287
311
  return {
288
312
  type: "calendar.event.canceled",
313
+ ...(options === undefined ? {} : { connectionKey: options.connectionKey }),
289
314
  };
290
315
  },
291
316
 
@@ -391,11 +416,12 @@ export const triggers = {
391
416
  /**
392
417
  * Declare that a workflow can run on `googleDriveOauth.filesChanged` events.
393
418
  *
394
- * Files changed in a watched Google Drive folder or drive.
419
+ * Watches for file changes in a specific Google Drive folder (direct children only) or shared drive. The My Drive root is not supported. Fires on a schedule and provides change tokens for fetching the list of changes.
395
420
  */
396
- googleDriveOauthFilesChanged(): GoogleDriveOauthFilesChangedTrigger {
421
+ googleDriveOauthFilesChanged<const TKey extends string = never>(options: { connectionKey: TKey } | undefined = undefined): GoogleDriveOauthFilesChangedTrigger<TKey> {
397
422
  return {
398
423
  type: "googleDriveOauth.filesChanged",
424
+ ...(options === undefined ? {} : { connectionKey: options.connectionKey }),
399
425
  };
400
426
  },
401
427
 
@@ -411,6 +437,60 @@ export const triggers = {
411
437
  },
412
438
  } as const;
413
439
 
440
+ /** Trigger creators restricted to the workflow's declared provider keys. */
441
+ export type WorkflowTriggerCreators<TConnections extends readonly WorkflowConnection[]> = {
442
+ slackMessage(options?: { connectionKey: WorkflowConnectionKeys<TConnections, "slack"> }): SlackMessageTrigger<WorkflowConnectionKeys<TConnections, "slack">>;
443
+ slackReactionAdded(options?: { connectionKey: WorkflowConnectionKeys<TConnections, "slack"> }): SlackReactionAddedTrigger<WorkflowConnectionKeys<TConnections, "slack">>;
444
+ slackAppMention(options?: { connectionKey: WorkflowConnectionKeys<TConnections, "slack"> }): SlackAppMentionTrigger<WorkflowConnectionKeys<TConnections, "slack">>;
445
+ discordInteraction(options?: { connectionKey: WorkflowConnectionKeys<TConnections, "discord"> }): DiscordInteractionTrigger<WorkflowConnectionKeys<TConnections, "discord">>;
446
+ mailEmailReceived(): MailEmailReceivedTrigger;
447
+ mailEmailSent(): MailEmailSentTrigger;
448
+ mailLabelApplied(): MailLabelAppliedTrigger;
449
+ calendarEventCreated(options?: { connectionKey: WorkflowConnectionKeys<TConnections, "calendar"> }): CalendarEventCreatedTrigger<WorkflowConnectionKeys<TConnections, "calendar">>;
450
+ calendarEventUpdated(options?: { connectionKey: WorkflowConnectionKeys<TConnections, "calendar"> }): CalendarEventUpdatedTrigger<WorkflowConnectionKeys<TConnections, "calendar">>;
451
+ calendarEventCanceled(options?: { connectionKey: WorkflowConnectionKeys<TConnections, "calendar"> }): CalendarEventCanceledTrigger<WorkflowConnectionKeys<TConnections, "calendar">>;
452
+ notionPageCreated(): NotionPageCreatedTrigger;
453
+ notionPageUpdated(): NotionPageUpdatedTrigger;
454
+ notionPageDeleted(): NotionPageDeletedTrigger;
455
+ notionMeetingNoteSummaryCompleted(): NotionMeetingNoteSummaryCompletedTrigger;
456
+ notionCommentAdded(): NotionCommentAddedTrigger;
457
+ notionAgentMentioned(): NotionAgentMentionedTrigger;
458
+ notionButtonPressed(): NotionButtonPressedTrigger;
459
+ notionDatabaseAgentUpdated(): NotionDatabaseAgentUpdatedTrigger;
460
+ webhook(): WebhookTrigger;
461
+ googleDriveOauthFilesChanged(options?: { connectionKey: WorkflowConnectionKeys<TConnections, "googleDriveOauth"> }): GoogleDriveOauthFilesChangedTrigger<WorkflowConnectionKeys<TConnections, "googleDriveOauth">>;
462
+ scheduled(): RecurrenceTrigger;
463
+ };
464
+
465
+ /** Triggers whose connection keys belong to the declared provider. */
466
+ export type WorkflowTriggerForConnections<TConnections extends readonly WorkflowConnection[]> =
467
+ | SlackMessageTrigger<WorkflowConnectionKeys<TConnections, "slack">>
468
+ | SlackReactionAddedTrigger<WorkflowConnectionKeys<TConnections, "slack">>
469
+ | SlackAppMentionTrigger<WorkflowConnectionKeys<TConnections, "slack">>
470
+ | DiscordInteractionTrigger<WorkflowConnectionKeys<TConnections, "discord">>
471
+ | MailEmailReceivedTrigger
472
+ | MailEmailSentTrigger
473
+ | MailLabelAppliedTrigger
474
+ | CalendarEventCreatedTrigger<WorkflowConnectionKeys<TConnections, "calendar">>
475
+ | CalendarEventUpdatedTrigger<WorkflowConnectionKeys<TConnections, "calendar">>
476
+ | CalendarEventCanceledTrigger<WorkflowConnectionKeys<TConnections, "calendar">>
477
+ | NotionPageCreatedTrigger
478
+ | NotionPageUpdatedTrigger
479
+ | NotionPageDeletedTrigger
480
+ | NotionMeetingNoteSummaryCompletedTrigger
481
+ | NotionCommentAddedTrigger
482
+ | NotionAgentMentionedTrigger
483
+ | NotionButtonPressedTrigger
484
+ | NotionDatabaseAgentUpdatedTrigger
485
+ | WebhookTrigger
486
+ | GoogleDriveOauthFilesChangedTrigger<WorkflowConnectionKeys<TConnections, "googleDriveOauth">>
487
+ | RecurrenceTrigger;
488
+
489
+ /** @internal */
490
+ export function createWorkflowTriggers<TConnections extends readonly WorkflowConnection[]>(): WorkflowTriggerCreators<TConnections> {
491
+ return triggers;
492
+ }
493
+
414
494
  /** Event that can trigger a workflow. */
415
495
  export type WorkflowTrigger =
416
496
  | SlackMessageTrigger
@@ -0,0 +1,59 @@
1
+ import { expectTypeOf, it } from "vitest";
2
+ import { triggers } from "./triggers.generated.js";
3
+ import { connections, createWorkflow, type WorkflowConnection } from "./workflow.js";
4
+
5
+ it("infers only the declared provider clients", () => {
6
+ createWorkflow({
7
+ name: "Calendar",
8
+ description: "Calendar only",
9
+ triggers: [triggers.notionPageCreated()],
10
+ connections: [connections.calendar({ key: "work" })],
11
+ handler(_event, context) {
12
+ expectTypeOf<keyof typeof context.connections>().toEqualTypeOf<"calendar">();
13
+ expectTypeOf(context.connections.calendar).toBeFunction();
14
+ },
15
+ });
16
+ const requirements = [connections.calendar(), connections.slack({ key: "support" })];
17
+ createWorkflow({
18
+ name: "Multiple providers",
19
+ description: "Preserves providers in a separately declared array",
20
+ triggers: [triggers.notionPageCreated()],
21
+ connections: requirements,
22
+ handler(_event, context) {
23
+ expectTypeOf<keyof typeof context.connections>().toEqualTypeOf<"calendar" | "slack">();
24
+ },
25
+ });
26
+ });
27
+
28
+ it("exposes no providers for omitted or empty connections", () => {
29
+ createWorkflow({
30
+ name: "No connections",
31
+ description: "Omitted",
32
+ triggers: [triggers.notionPageCreated()],
33
+ handler(_event, context) {
34
+ expectTypeOf<keyof typeof context.connections>().toEqualTypeOf<never>();
35
+ },
36
+ });
37
+ createWorkflow({
38
+ name: "No connections",
39
+ description: "Empty",
40
+ triggers: [triggers.notionPageCreated()],
41
+ connections: [],
42
+ handler(_event, context) {
43
+ expectTypeOf<keyof typeof context.connections>().toEqualTypeOf<never>();
44
+ },
45
+ });
46
+ });
47
+
48
+ it("preserves literal declarations checked with satisfies", () => {
49
+ const requirements = [connections.slack()] satisfies readonly WorkflowConnection[];
50
+ createWorkflow({
51
+ name: "Slack",
52
+ description: "Slack only",
53
+ triggers: [triggers.notionPageCreated()],
54
+ connections: requirements,
55
+ handler(_event, context) {
56
+ expectTypeOf<keyof typeof context.connections>().toEqualTypeOf<"slack">();
57
+ },
58
+ });
59
+ });
@@ -0,0 +1,144 @@
1
+ import { resolve } from "node:path";
2
+ import ts from "typescript";
3
+ import { expect, it } from "vitest";
4
+
5
+ it("checks provider keys on callback arguments and returned triggers", () => {
6
+ const cases = [
7
+ {
8
+ name: "valid",
9
+ valid: true,
10
+ connections: '[connections.slack({ key: "support" }), connections.calendar()]',
11
+ trigger:
12
+ 'triggers.slackMessage({ connectionKey: "support" }), triggers.calendarEventCreated({ connectionKey: "calendar" })',
13
+ },
14
+ {
15
+ name: "default",
16
+ valid: true,
17
+ connections: "[connections.slack()]",
18
+ trigger: 'triggers.slackMessage({ connectionKey: "slack" })',
19
+ },
20
+ {
21
+ name: "unbound",
22
+ valid: true,
23
+ connections: "[]",
24
+ trigger: "triggers.slackMessage(), triggers.notionPageCreated()",
25
+ },
26
+ {
27
+ name: "global_valid",
28
+ valid: true,
29
+ connections: '[connections.slack({ key: "support" })]',
30
+ trigger: 'globalTriggers.slackMessage({ connectionKey: "support" })',
31
+ },
32
+ {
33
+ name: "undeclared",
34
+ valid: false,
35
+ connections: '[connections.slack({ key: "support" })]',
36
+ trigger: 'triggers.slackMessage({ connectionKey: "typo" })',
37
+ },
38
+ {
39
+ name: "wrong_provider",
40
+ valid: false,
41
+ connections: '[connections.calendar({ key: "support" })]',
42
+ trigger: 'triggers.slackMessage({ connectionKey: "support" })',
43
+ },
44
+ {
45
+ name: "custom_not_default",
46
+ valid: false,
47
+ connections: '[connections.slack({ key: "support" })]',
48
+ trigger: 'triggers.slackMessage({ connectionKey: "slack" })',
49
+ },
50
+ {
51
+ name: "missing",
52
+ valid: false,
53
+ connections: undefined,
54
+ trigger: 'triggers.slackMessage({ connectionKey: "slack" })',
55
+ },
56
+ {
57
+ name: "global_invalid",
58
+ valid: false,
59
+ connections: '[connections.calendar({ key: "support" })]',
60
+ trigger: 'globalTriggers.slackMessage({ connectionKey: "support" })',
61
+ },
62
+ {
63
+ name: "literal_invalid",
64
+ valid: false,
65
+ connections: '[connections.calendar({ key: "support" })]',
66
+ trigger: '{ type: "slack.message", connectionKey: "support" }',
67
+ },
68
+ {
69
+ name: "generic_without_key",
70
+ valid: false,
71
+ connections: '[connections.slack<"support">()]',
72
+ trigger: 'triggers.slackMessage({ connectionKey: "support" })',
73
+ },
74
+ {
75
+ name: "literal_default",
76
+ valid: true,
77
+ connections: '[{ type: "slack" }]',
78
+ trigger: 'triggers.slackMessage({ connectionKey: "slack" })',
79
+ },
80
+ {
81
+ name: "saved",
82
+ valid: true,
83
+ connections: "[support]",
84
+ trigger: 'triggers.slackMessage({ connectionKey: "support" })',
85
+ },
86
+ {
87
+ name: "saved_invalid",
88
+ valid: false,
89
+ connections: "[support]",
90
+ trigger: 'triggers.slackMessage({ connectionKey: "slack" })',
91
+ },
92
+ ];
93
+ const fixtures = new Map(
94
+ cases.map((testCase) => [
95
+ resolve("src", `__workflow_types_${testCase.name}.ts`),
96
+ `import { connections, createWorkflow } from "./workflow.js";
97
+ import { triggers as globalTriggers } from "./triggers.generated.js";
98
+ const support = connections.slack({ key: "support" });
99
+ createWorkflow({
100
+ name: "Type check", description: "Type check",
101
+ ${testCase.connections === undefined ? "" : `connections: ${testCase.connections},`}
102
+ triggers: ({ triggers }) => [${testCase.trigger}],
103
+ handler: () => {},
104
+ });`,
105
+ ]),
106
+ );
107
+ const config = ts.readConfigFile(resolve("tsconfig.json"), ts.sys.readFile);
108
+ expect(config.error).toBeUndefined();
109
+ const parsed = ts.parseJsonConfigFileContent(config.config, ts.sys, process.cwd());
110
+ expect(parsed.errors).toEqual([]);
111
+ const host = ts.createCompilerHost(parsed.options);
112
+ const getSourceFile = host.getSourceFile.bind(host);
113
+ host.getSourceFile = (fileName, languageVersion, onError, shouldCreateNewSourceFile) => {
114
+ const fixture = fixtures.get(fileName);
115
+ return fixture === undefined
116
+ ? getSourceFile(fileName, languageVersion, onError, shouldCreateNewSourceFile)
117
+ : ts.createSourceFile(fileName, fixture, languageVersion, true);
118
+ };
119
+ const program = ts.createProgram(
120
+ [...fixtures.keys()],
121
+ { ...parsed.options, noEmit: true },
122
+ host,
123
+ );
124
+ const diagnostics = ts.getPreEmitDiagnostics(program);
125
+ const unrelated = diagnostics.filter(
126
+ (diagnostic) => !diagnostic.file || !fixtures.has(diagnostic.file.fileName),
127
+ );
128
+ expect(
129
+ unrelated.map((diagnostic) =>
130
+ ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n"),
131
+ ),
132
+ ).toEqual([]);
133
+ for (const testCase of cases) {
134
+ const errors = diagnostics.filter(
135
+ (diagnostic) =>
136
+ diagnostic.file?.fileName ===
137
+ resolve("src", `__workflow_types_${testCase.name}.ts`),
138
+ );
139
+ expect(
140
+ errors.length === 0,
141
+ `${testCase.name}: ${errors.map((diagnostic) => ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n")).join("\n")}`,
142
+ ).toBe(testCase.valid);
143
+ }
144
+ }, 30_000);
@@ -37,8 +37,8 @@ afterEach(async () => {
37
37
 
38
38
  const event = { type: "notion.page.created" } as NotionPageCreatedEvent;
39
39
 
40
- async function createWorkflowContext(): Promise<WorkflowContext> {
41
- let context: WorkflowContext | undefined;
40
+ async function createWorkflowContext(): Promise<WorkflowContext<readonly []>> {
41
+ let context: WorkflowContext<readonly []> | undefined;
42
42
  const workflow = createWorkflow({
43
43
  name: "Test workflow",
44
44
  description: "Captures its workflow context",
@@ -832,3 +832,113 @@ describe("createWorkflow", () => {
832
832
  );
833
833
  });
834
834
  });
835
+
836
+ describe("connection-bound triggers", () => {
837
+ it("resolves typed trigger callbacks once and preserves event inference", () => {
838
+ let calls = 0;
839
+ const workflow = createWorkflow({
840
+ name: "Typed triggers",
841
+ description: "Uses only declared provider keys",
842
+ connections: [connections.slack({ key: "support" }), connections.calendar()],
843
+ triggers: ({ triggers }) => {
844
+ calls += 1;
845
+ expectTypeOf(triggers.slackMessage)
846
+ .parameter(0)
847
+ .toEqualTypeOf<{ connectionKey: "support" } | undefined>();
848
+ expectTypeOf(triggers.calendarEventCreated)
849
+ .parameter(0)
850
+ .toEqualTypeOf<{ connectionKey: "calendar" } | undefined>();
851
+ expectTypeOf(triggers.discordInteraction)
852
+ .parameter(0)
853
+ .toEqualTypeOf<{ connectionKey: never } | undefined>();
854
+ return [
855
+ triggers.slackMessage({ connectionKey: "support" }),
856
+ triggers.calendarEventCreated({ connectionKey: "calendar" }),
857
+ ];
858
+ },
859
+ handler: (event, context) => {
860
+ expectTypeOf(event.type).toEqualTypeOf<
861
+ "slack.message" | "calendar.event.created"
862
+ >();
863
+ expectTypeOf<keyof typeof context.connections>().toEqualTypeOf<
864
+ "slack" | "calendar"
865
+ >();
866
+ },
867
+ });
868
+ expect(calls).toBe(1);
869
+ expect(workflow.config.triggers).toEqual([
870
+ { type: "slack.message", connectionKey: "support" },
871
+ { type: "calendar.event.created", connectionKey: "calendar" },
872
+ ]);
873
+ });
874
+
875
+ it("preserves the binding in the manifest and infers the event", () => {
876
+ const workflow = createWorkflow({
877
+ name: "Support",
878
+ description: "Watch support messages",
879
+ connections: [connections.slack({ key: "support" })],
880
+ triggers: [triggers.slackMessage({ connectionKey: "support" })],
881
+ handler(event) {
882
+ expectTypeOf(event.type).toEqualTypeOf<"slack.message">();
883
+ },
884
+ });
885
+ expect(workflow.config.triggers).toEqual([
886
+ { type: "slack.message", connectionKey: "support" },
887
+ ]);
888
+ });
889
+ it.each([
890
+ { requirements: [] },
891
+ { requirements: [connections.calendar({ key: "support" })] },
892
+ { requirements: [connections.slack({ key: "other" })] },
893
+ ])("rejects missing or incompatible declarations: %j", ({ requirements }) => {
894
+ expect(() =>
895
+ createWorkflow({
896
+ name: "Support",
897
+ description: "",
898
+ connections: requirements,
899
+ triggers: [triggers.slackMessage({ connectionKey: "support" })],
900
+ handler() {},
901
+ }),
902
+ ).toThrow("requires a declared slack connection");
903
+ });
904
+ it("rejects duplicate bindings while allowing distinct connections", () => {
905
+ const configuration = {
906
+ name: "Support",
907
+ description: "",
908
+ connections: [
909
+ connections.slack({ key: "support" }),
910
+ connections.slack({ key: "internal" }),
911
+ ],
912
+ handler() {},
913
+ };
914
+ expect(() =>
915
+ createWorkflow({
916
+ ...configuration,
917
+ triggers: [
918
+ triggers.slackMessage({ connectionKey: "support" }),
919
+ triggers.slackMessage({ connectionKey: "support" }),
920
+ ],
921
+ }),
922
+ ).toThrow("Duplicate");
923
+ expect(() =>
924
+ createWorkflow({
925
+ ...configuration,
926
+ triggers: [
927
+ triggers.slackMessage({ connectionKey: "support" }),
928
+ triggers.slackMessage({ connectionKey: "internal" }),
929
+ ],
930
+ }),
931
+ ).not.toThrow();
932
+ });
933
+ it("preserves existing unkeyed helpers", () => {
934
+ expect(triggers.slackMessage()).toEqual({ type: "slack.message" });
935
+ expect(() =>
936
+ createWorkflow({
937
+ name: "Existing",
938
+ description: "",
939
+ triggers: [triggers.slackMessage()],
940
+ handler() {},
941
+ }),
942
+ ).not.toThrow();
943
+ });
944
+ });