@notionhq/apps 0.0.41 → 0.0.44

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.
@@ -13,15 +13,59 @@ afterEach(() => {
13
13
  });
14
14
 
15
15
  describe("connection requirements", () => {
16
+ it("rejects Calendar declarations that omit explicit access", () => {
17
+ expect(() => normalizeConnectionRequirements({ work: { type: "calendar" } })).toThrow(
18
+ "scope and readTeammatesCalendars",
19
+ );
20
+ });
21
+ it.each([
22
+ { scope: "read" },
23
+ { readTeammatesCalendars: false },
24
+ { scope: "write", readTeammatesCalendars: false },
25
+ { scope: "read", readTeammatesCalendars: "false" },
26
+ ])("rejects invalid Calendar options: %j", (calendar) => {
27
+ expect(() =>
28
+ Reflect.apply(normalizeConnectionRequirements, undefined, [
29
+ { work: { type: "calendar", calendar } },
30
+ ]),
31
+ ).toThrow("scope and readTeammatesCalendars");
32
+ });
33
+ it("rejects Calendar options on other providers", () => {
34
+ expect(() =>
35
+ normalizeConnectionRequirements({
36
+ work: { type: "mail", calendar: { scope: "read", readTeammatesCalendars: false } },
37
+ }),
38
+ ).toThrow("only valid on Calendar");
39
+ });
40
+ it("preserves explicit write and teammate choices in the manifest", () => {
41
+ const connection = connections.calendar({
42
+ scope: "read_write",
43
+ readTeammatesCalendars: true,
44
+ });
45
+ const normalized = normalizeConnectionRequirements({ work: connection });
46
+ if (!connection.calendar) throw new Error("Missing Calendar options");
47
+ connection.calendar.readTeammatesCalendars = false;
48
+ expect(normalized).toEqual([
49
+ {
50
+ type: "calendar",
51
+ key: "work",
52
+ calendar: { scope: "read_write", readTeammatesCalendars: true },
53
+ },
54
+ ]);
55
+ });
16
56
  it("normalizes object keys into manifest requirements", () => {
17
57
  expect(
18
58
  normalizeConnectionRequirements({
19
- work: connections.calendar(),
59
+ work: connections.calendar({ scope: "read", readTeammatesCalendars: false }),
20
60
  support: connections.slack(),
21
61
  team: connections.slack(),
22
62
  }),
23
63
  ).toEqual([
24
- { type: "calendar", key: "work" },
64
+ {
65
+ type: "calendar",
66
+ key: "work",
67
+ calendar: { scope: "read", readTeammatesCalendars: false },
68
+ },
25
69
  { type: "slack", key: "support" },
26
70
  { type: "slack", key: "team" },
27
71
  ]);
@@ -44,10 +88,14 @@ describe("connection requirements", () => {
44
88
  Reflect.apply(normalizeConnectionRequirements, undefined, [[connections.slack()]]),
45
89
  ).toThrow("must be an object");
46
90
  const inherited = { support: connections.slack() };
47
- Object.setPrototypeOf(inherited, { hidden: connections.calendar() });
91
+ Object.setPrototypeOf(inherited, {
92
+ hidden: connections.calendar({ scope: "read", readTeammatesCalendars: false }),
93
+ });
48
94
  expect(() => normalizeConnectionRequirements(inherited)).toThrow("only own");
49
95
  const hidden = { support: connections.slack() };
50
- Object.defineProperty(hidden, "hidden", { value: connections.calendar() });
96
+ Object.defineProperty(hidden, "hidden", {
97
+ value: connections.calendar({ scope: "read", readTeammatesCalendars: false }),
98
+ });
51
99
  expect(() => normalizeConnectionRequirements(hidden)).toThrow("enumerable strings");
52
100
  const symbolic = { [Symbol("support")]: connections.slack() };
53
101
  expect(() => normalizeConnectionRequirements(symbolic)).toThrow("enumerable strings");
@@ -120,7 +168,7 @@ describe("workflow connection runtime", () => {
120
168
  const declarations = { support: connections.slack(), team: connections.slack() };
121
169
  const runtime = createWorkflowConnections(notion, declarations);
122
170
  Object.assign(declarations, {
123
- support: connections.calendar(),
171
+ support: connections.calendar({ scope: "read", readTeammatesCalendars: false }),
124
172
  injected: connections.slack(),
125
173
  });
126
174
  await runtime.support.findUserByEmail({ email: "support@example.com" });
@@ -194,6 +242,52 @@ describe("workflow connection runtime", () => {
194
242
  ]);
195
243
  });
196
244
 
245
+ it("routes Slack channel writes through their configured connection", async () => {
246
+ vi.stubEnv(
247
+ "NOTION_CONNECTION_BINDINGS",
248
+ '[{"key":"support","type":"slack","id":"support-id"}]',
249
+ );
250
+ const notion = new Client({ auth: "test" });
251
+ const request = vi.spyOn(notion, "request").mockResolvedValue({ result: {} });
252
+ const slack = createWorkflowConnections(notion, {
253
+ support: connections.slack(),
254
+ }).support;
255
+
256
+ await slack.createThreadInChannel({ channelId: "C123", message: "New update" });
257
+ await slack.replyInThread({
258
+ prefixedThreadUri: "slackMessage://example.slack.com/C123/123/123",
259
+ message: "Reply",
260
+ });
261
+ await slack.updateMessage({
262
+ prefixedMessageUri: "slackMessage://example.slack.com/C123/123/123",
263
+ message: "Edited update",
264
+ });
265
+
266
+ expect(request.mock.calls.map(([input]) => input.body)).toEqual([
267
+ {
268
+ connection: "support-id",
269
+ action: "createThreadInChannel",
270
+ args: { channelId: "C123", message: "New update" },
271
+ },
272
+ {
273
+ connection: "support-id",
274
+ action: "replyInThread",
275
+ args: {
276
+ prefixedThreadUri: "slackMessage://example.slack.com/C123/123/123",
277
+ message: "Reply",
278
+ },
279
+ },
280
+ {
281
+ connection: "support-id",
282
+ action: "updateMessage",
283
+ args: {
284
+ prefixedMessageUri: "slackMessage://example.slack.com/C123/123/123",
285
+ message: "Edited update",
286
+ },
287
+ },
288
+ ]);
289
+ });
290
+
197
291
  it("does not parse missing or malformed bindings until a declared client is used", () => {
198
292
  vi.stubEnv("NOTION_CONNECTION_BINDINGS", "not json");
199
293
  const runtime = createWorkflowConnections(new Client(), { support: connections.slack() });
@@ -263,7 +357,7 @@ describe("workflow connection runtime", () => {
263
357
  const notion = new Client({ auth: "test" });
264
358
  const request = vi.spyOn(notion, "request").mockResolvedValue({ result: { accounts: [] } });
265
359
  const result = await createWorkflowConnections(notion, {
266
- calendar: connections.calendar(),
360
+ calendar: connections.calendar({ scope: "read", readTeammatesCalendars: false }),
267
361
  }).calendar.listCalendars({});
268
362
  expect(result.accounts).toEqual([]);
269
363
  expect(request).toHaveBeenCalledWith({
@@ -13,6 +13,12 @@ import type { ConnectionActionInvoker } from "./connection-actions.js";
13
13
  export type WorkflowConnection<TProvider extends string = string> = {
14
14
  type: TProvider;
15
15
  oauth?: OAuthConfiguration;
16
+ calendar?: CalendarConnectionOptions;
17
+ };
18
+
19
+ export type CalendarConnectionOptions = {
20
+ scope: "read" | "read_write";
21
+ readTeammatesCalendars: boolean;
16
22
  };
17
23
 
18
24
  export type WorkflowConnectionDeclarations = Readonly<Record<string, WorkflowConnection>>;
@@ -94,6 +100,19 @@ export function normalizeConnectionRequirements(
94
100
  "Declare a connection key as its workflow object property, not in the provider options.",
95
101
  );
96
102
 
103
+ if (requirement.type === "calendar") {
104
+ const options = requirement.calendar;
105
+ if (
106
+ !options ||
107
+ !["read", "read_write"].includes(options.scope) ||
108
+ typeof options.readTeammatesCalendars !== "boolean"
109
+ ) {
110
+ throw new Error("Calendar connections require scope and readTeammatesCalendars.");
111
+ }
112
+ } else if (requirement.calendar !== undefined) {
113
+ throw new Error("Calendar options are only valid on Calendar connections.");
114
+ }
115
+
97
116
  if (requirement.type === "oauth") {
98
117
  if (!requirement.oauth)
99
118
  throw new Error("OAuth connections require provider configuration.");
@@ -105,6 +124,9 @@ export function normalizeConnectionRequirements(
105
124
  return {
106
125
  type: requirement.type,
107
126
  key,
127
+ ...(requirement.calendar === undefined
128
+ ? {}
129
+ : { calendar: { ...requirement.calendar } }),
108
130
  ...(requirement.oauth === undefined
109
131
  ? {}
110
132
  : { oauth: structuredClone(requirement.oauth) }),
@@ -1,6 +1,9 @@
1
1
  // Generated by notion tool-core codegen-script-types --connections. Do not edit.
2
2
  import type { ConnectionActionInvoker } from "./connection-actions.js"
3
- import type { WorkflowConnection } from "./connections.js"
3
+ import type {
4
+ WorkflowConnection,
5
+ CalendarConnectionOptions,
6
+ } from "./connections.js"
4
7
  import { CalendarConnectionClient } from "./calendar.generated.js"
5
8
  import { MailConnectionClient } from "./mail.generated.js"
6
9
  import { SlackConnectionClient } from "./slack.generated.js"
@@ -10,7 +13,6 @@ import { CursorConnectionClient } from "./cursor.generated.js"
10
13
  import { BoxConnectionClient } from "./box.generated.js"
11
14
  import { ConfluenceConnectionClient } from "./confluence.generated.js"
12
15
  import { GoogleDriveConnectionClient } from "./googleDrive.generated.js"
13
- import { OutlookConnectionClient } from "./outlook.generated.js"
14
16
  import { SalesforceConnectionClient } from "./salesforce.generated.js"
15
17
  export const connections = {
16
18
  calendar,
@@ -22,7 +24,6 @@ export const connections = {
22
24
  box,
23
25
  confluence,
24
26
  googleDrive,
25
- outlook,
26
27
  salesforce,
27
28
  }
28
29
  export type ConnectionClients = {
@@ -35,7 +36,6 @@ export type ConnectionClients = {
35
36
  box: BoxConnectionClient
36
37
  confluence: ConfluenceConnectionClient
37
38
  googleDrive: GoogleDriveConnectionClient
38
- outlook: OutlookConnectionClient
39
39
  salesforce: SalesforceConnectionClient
40
40
  }
41
41
  export function createConnectionClient(
@@ -61,16 +61,16 @@ export function createConnectionClient(
61
61
  return new ConfluenceConnectionClient(invoker)
62
62
  case "googleDrive":
63
63
  return new GoogleDriveConnectionClient(invoker)
64
- case "outlook":
65
- return new OutlookConnectionClient(invoker)
66
64
  case "salesforce":
67
65
  return new SalesforceConnectionClient(invoker)
68
66
  default:
69
67
  throw new Error("Unsupported connection provider: " + provider)
70
68
  }
71
69
  }
72
- function calendar(): WorkflowConnection<"calendar"> {
73
- return { type: "calendar" }
70
+ function calendar(
71
+ options: CalendarConnectionOptions,
72
+ ): WorkflowConnection<"calendar"> {
73
+ return { type: "calendar", calendar: { ...options } }
74
74
  }
75
75
  function mail(): WorkflowConnection<"mail"> {
76
76
  return { type: "mail" }
@@ -96,9 +96,6 @@ function confluence(): WorkflowConnection<"confluence"> {
96
96
  function googleDrive(): WorkflowConnection<"googleDrive"> {
97
97
  return { type: "googleDrive" }
98
98
  }
99
- function outlook(): WorkflowConnection<"outlook"> {
100
- return { type: "outlook" }
101
- }
102
99
  function salesforce(): WorkflowConnection<"salesforce"> {
103
100
  return { type: "salesforce" }
104
101
  }
@@ -27,6 +27,96 @@ export type AddReactionToMessageScriptOutput = {
27
27
  message?: string
28
28
  }
29
29
 
30
+ /**
31
+ * Script input for the "Create a new thread in a Slack channel" effect (`slack.createThreadInChannel`),
32
+ * derived from its Tool Core definition's call_function input projection.
33
+ *
34
+ * Create a new thread in a Slack channel
35
+ */
36
+ export type CreateThreadInChannelScriptInput =
37
+ | {
38
+ /**
39
+ * The Slack ID of the channel to send the message in.
40
+ */
41
+ channelId: string
42
+ /**
43
+ * The file IDs of any files attached to the message.
44
+ */
45
+ attachedFileIds?: Array<string>
46
+ /**
47
+ * Raw Slack user ID that should be the only user able to see the message. Omit for a normal channel message.
48
+ */
49
+ ephemeralUserId?: string
50
+ /**
51
+ * Message body in standard Markdown. Never use Notion rich-text XML such as <mention>; write Notion references as Markdown links ([label](https://example.com)). Use Slack mention tokens only when a verified Slack ID is available.
52
+ */
53
+ message: string
54
+ /**
55
+ * Whether to unfurl links in Slack. Defaults to true.
56
+ */
57
+ unfurlLinks?: boolean
58
+ /**
59
+ * A confirmation token for sending messages with URLs that require user approval. Do NOT provide unless specifically instructed or unless an error provides the token.
60
+ */
61
+ userConfirmationToken?: string
62
+ }
63
+ | {
64
+ /**
65
+ * The name of the channel without the # symbol. For example, "engineering".
66
+ */
67
+ channelName: string
68
+ /**
69
+ * The file IDs of any files attached to the message.
70
+ */
71
+ attachedFileIds?: Array<string>
72
+ /**
73
+ * Raw Slack user ID that should be the only user able to see the message. Omit for a normal channel message.
74
+ */
75
+ ephemeralUserId?: string
76
+ /**
77
+ * Message body in standard Markdown. Never use Notion rich-text XML such as <mention>; write Notion references as Markdown links ([label](https://example.com)). Use Slack mention tokens only when a verified Slack ID is available.
78
+ */
79
+ message: string
80
+ /**
81
+ * Whether to unfurl links in Slack. Defaults to true.
82
+ */
83
+ unfurlLinks?: boolean
84
+ /**
85
+ * A confirmation token for sending messages with URLs that require user approval. Do NOT provide unless specifically instructed or unless an error provides the token.
86
+ */
87
+ userConfirmationToken?: string
88
+ }
89
+
90
+ /**
91
+ * Canonical Tool Core output for the "Create a new thread in a Slack channel" effect (`slack.createThreadInChannel`).
92
+ * This is the definition's output schema; the effect's script surface may
93
+ * re-encode it before returning.
94
+ */
95
+ export type CreateThreadInChannelScriptOutput = {
96
+ channelId: string
97
+ channelUri: string
98
+ from:
99
+ | {
100
+ handle: string
101
+ id: string
102
+ type: "user"
103
+ uri: string
104
+ }
105
+ | {
106
+ type: "bot"
107
+ }
108
+ id: string
109
+ message: string
110
+ threadId: string
111
+ timestamp: string
112
+ uri: string
113
+ url: string
114
+ urls: Array<string>
115
+ channel?: string
116
+ fileIds?: Array<string>
117
+ files?: Array<string>
118
+ }
119
+
30
120
  /**
31
121
  * Script input for the "Find a Slack user by email" effect (`slack.findUserByEmail`),
32
122
  * derived from its Tool Core definition's call_function input projection.
@@ -428,6 +518,52 @@ export type RemoveReactionFromMessageScriptOutput = {
428
518
  message?: string
429
519
  }
430
520
 
521
+ /**
522
+ * Script input for the "Reply to Slack thread" effect (`slack.replyInThread`),
523
+ * derived from its Tool Core definition's call_function input projection.
524
+ *
525
+ * Reply in a Slack thread
526
+ */
527
+ export type ReplyInThreadScriptInput = {
528
+ /**
529
+ * The file IDs of any files attached to the message.
530
+ */
531
+ attachedFileIds?: Array<string>
532
+ /**
533
+ * Raw Slack user ID that should be the only user able to see the reply. Omit for a normal thread reply.
534
+ */
535
+ ephemeralUserId?: string
536
+ /**
537
+ * The thread URI to reply to. If there is a Slack message trigger, use the thread-id attribute from the trigger message.
538
+ */
539
+ prefixedThreadUri: string
540
+ /**
541
+ * The Slack markdown message to post in the thread.
542
+ */
543
+ message: string
544
+ /**
545
+ * Whether to unfurl links in Slack. Defaults to true.
546
+ */
547
+ unfurlLinks?: boolean
548
+ /**
549
+ * A confirmation token for sending messages with URLs that require user approval. Do NOT provide unless specifically instructed or unless an error provides the token.
550
+ */
551
+ userConfirmationToken?: string
552
+ }
553
+
554
+ /**
555
+ * Canonical Tool Core output for the "Reply to Slack thread" effect (`slack.replyInThread`).
556
+ * This is the definition's output schema; the effect's script surface may
557
+ * re-encode it before returning.
558
+ */
559
+ export type ReplyInThreadScriptOutput = {
560
+ message: string
561
+ url: string
562
+ channelId: string
563
+ threadId: string
564
+ slackMessageId: string
565
+ }
566
+
431
567
  /**
432
568
  * Script input for the "Search Slack messages" effect (`slack.search`),
433
569
  * derived from its Tool Core definition's call_function input projection.
@@ -478,6 +614,57 @@ export type SearchScriptOutput = {
478
614
  }>
479
615
  }
480
616
 
617
+ /**
618
+ * Script input for the "Update a Slack message" effect (`slack.updateMessage`),
619
+ * derived from its Tool Core definition's call_function input projection.
620
+ *
621
+ * Update a Slack message
622
+ */
623
+ export type UpdateMessageScriptInput = {
624
+ /**
625
+ * The URI of the Slack message
626
+ */
627
+ prefixedMessageUri: string
628
+ /**
629
+ * Message body in standard Markdown. Never use Notion rich-text XML such as <mention>; write Notion references as Markdown links ([label](https://example.com)). Use Slack mention tokens only when a verified Slack ID is available.
630
+ */
631
+ message: string
632
+ /**
633
+ * Whether to unfurl links in Slack. Defaults to true.
634
+ */
635
+ unfurlLinks?: boolean
636
+ }
637
+
638
+ /**
639
+ * Canonical Tool Core output for the "Update a Slack message" effect (`slack.updateMessage`).
640
+ * This is the definition's output schema; the effect's script surface may
641
+ * re-encode it before returning.
642
+ */
643
+ export type UpdateMessageScriptOutput = {
644
+ channelId: string
645
+ channelUri: string
646
+ from:
647
+ | {
648
+ handle: string
649
+ id: string
650
+ type: "user"
651
+ uri: string
652
+ }
653
+ | {
654
+ type: "bot"
655
+ }
656
+ id: string
657
+ message: string
658
+ threadId: string
659
+ timestamp: string
660
+ uri: string
661
+ url: string
662
+ urls: Array<string>
663
+ channel?: string
664
+ fileIds?: Array<string>
665
+ files?: Array<string>
666
+ }
667
+
481
668
  /**
482
669
  * Script input for the "View Slack file URL" effect (`slack.viewFileUrl`),
483
670
  * derived from its Tool Core definition's call_function input projection.
@@ -512,6 +699,14 @@ export class SlackConnectionClient {
512
699
  args,
513
700
  )
514
701
  }
702
+ createThreadInChannel(
703
+ args: CreateThreadInChannelScriptInput,
704
+ ): Promise<CreateThreadInChannelScriptOutput> {
705
+ return this.invoke<CreateThreadInChannelScriptOutput>(
706
+ "createThreadInChannel",
707
+ args,
708
+ )
709
+ }
515
710
  findUserByEmail(
516
711
  args: FindUserByEmailScriptInput,
517
712
  ): Promise<FindUserByEmailScriptOutput> {
@@ -557,9 +752,19 @@ export class SlackConnectionClient {
557
752
  args,
558
753
  )
559
754
  }
755
+ replyInThread(
756
+ args: ReplyInThreadScriptInput,
757
+ ): Promise<ReplyInThreadScriptOutput> {
758
+ return this.invoke<ReplyInThreadScriptOutput>("replyInThread", args)
759
+ }
560
760
  search(args: SearchScriptInput): Promise<SearchScriptOutput> {
561
761
  return this.invoke<SearchScriptOutput>("search", args)
562
762
  }
763
+ updateMessage(
764
+ args: UpdateMessageScriptInput,
765
+ ): Promise<UpdateMessageScriptOutput> {
766
+ return this.invoke<UpdateMessageScriptOutput>("updateMessage", args)
767
+ }
563
768
  viewFileUrl(args: ViewFileUrlScriptInput): Promise<ViewFileUrlScriptOutput> {
564
769
  return this.invoke<ViewFileUrlScriptOutput>("viewFileUrl", args)
565
770
  }
@@ -20,7 +20,9 @@ it("infers only the declared provider clients", () => {
20
20
  name: "Calendar",
21
21
  description: "Calendar only",
22
22
  triggers: [events.notionPageCreated()],
23
- connections: { work: connections.calendar() },
23
+ connections: {
24
+ work: connections.calendar({ scope: "read", readTeammatesCalendars: false }),
25
+ },
24
26
  handler(_event, context) {
25
27
  expectTypeOf<keyof typeof context.connections>().toEqualTypeOf<"work">();
26
28
  expectTypeOf(context.connections.work.listCalendars).toBeFunction();
@@ -30,7 +32,7 @@ it("infers only the declared provider clients", () => {
30
32
  },
31
33
  });
32
34
  const requirements = {
33
- calendar: connections.calendar(),
35
+ calendar: connections.calendar({ scope: "read", readTeammatesCalendars: false }),
34
36
  support: connections.slack(),
35
37
  team: connections.slack(),
36
38
  };
@@ -71,7 +71,8 @@ it("checks provider keys on callback arguments and returned triggers", () => {
71
71
  {
72
72
  name: "valid",
73
73
  valid: true,
74
- connections: "{ support: connections.slack(), calendar: connections.calendar() }",
74
+ connections:
75
+ "{ support: connections.slack(), calendar: connections.calendar({ scope: 'read', readTeammatesCalendars: false }) }",
75
76
  trigger:
76
77
  'events.slackMessage({ connectionKey: "support" }), events.calendarEventCreated({ connectionKey: "calendar" })',
77
78
  },
@@ -102,7 +103,8 @@ it("checks provider keys on callback arguments and returned triggers", () => {
102
103
  {
103
104
  name: "wrong_provider",
104
105
  valid: false,
105
- connections: "{ support: connections.calendar() }",
106
+ connections:
107
+ "{ support: connections.calendar({ scope: 'read', readTeammatesCalendars: false }) }",
106
108
  trigger: 'events.slackMessage({ connectionKey: "support" })',
107
109
  },
108
110
  {
@@ -120,13 +122,15 @@ it("checks provider keys on callback arguments and returned triggers", () => {
120
122
  {
121
123
  name: "global_invalid",
122
124
  valid: false,
123
- connections: "{ support: connections.calendar() }",
125
+ connections:
126
+ "{ support: connections.calendar({ scope: 'read', readTeammatesCalendars: false }) }",
124
127
  trigger: 'globalTriggers.slackMessage({ connectionKey: "support" })',
125
128
  },
126
129
  {
127
130
  name: "literal_invalid",
128
131
  valid: false,
129
- connections: "{ support: connections.calendar() }",
132
+ connections:
133
+ "{ support: connections.calendar({ scope: 'read', readTeammatesCalendars: false }) }",
130
134
  trigger: '{ type: "slack.message", connectionKey: "support" }',
131
135
  },
132
136
  {
@@ -170,7 +174,7 @@ it("checks provider keys on callback arguments and returned triggers", () => {
170
174
  name: "keyed_clients",
171
175
  valid: true,
172
176
  connections:
173
- "{ support: connections.slack(), team: connections.slack(), work: connections.calendar() }",
177
+ "{ support: connections.slack(), team: connections.slack(), work: connections.calendar({ scope: 'read', readTeammatesCalendars: false }) }",
174
178
  trigger: "events.notionPageCreated()",
175
179
  handler:
176
180
  '(_event, ctx) => { ctx.connections.support.findUserByEmail({ email: "a@example.com" }); ctx.connections.team.findUserByEmail({ email: "b@example.com" }); ctx.connections.work.listCalendars({}); }',
@@ -1702,11 +1702,19 @@ describe("workflow", () => {
1702
1702
  name: "Schedule Follow-up",
1703
1703
  description: "Schedules a follow-up after a page is created",
1704
1704
  triggers: [events.notionPageCreated()],
1705
- connections: { calendar: connections.calendar() },
1705
+ connections: {
1706
+ calendar: connections.calendar({ scope: "read", readTeammatesCalendars: false }),
1707
+ },
1706
1708
  handler: () => {},
1707
1709
  });
1708
1710
 
1709
- expect(definition.config.connections).toEqual([{ type: "calendar", key: "calendar" }]);
1711
+ expect(definition.config.connections).toEqual([
1712
+ {
1713
+ type: "calendar",
1714
+ key: "calendar",
1715
+ calendar: { scope: "read", readTeammatesCalendars: false },
1716
+ },
1717
+ ]);
1710
1718
  });
1711
1719
 
1712
1720
  it("keeps declaration keys stable after the input object changes", async () => {
@@ -1721,14 +1729,19 @@ describe("workflow", () => {
1721
1729
  seen.push(Object.keys(context.connections));
1722
1730
  },
1723
1731
  });
1724
- Object.assign(declarations, { support: connections.calendar(), team: connections.slack() });
1732
+ Object.assign(declarations, {
1733
+ support: connections.calendar({ scope: "read", readTeammatesCalendars: false }),
1734
+ team: connections.slack(),
1735
+ });
1725
1736
  expect(definition.config.connections).toEqual([{ type: "slack", key: "support" }]);
1726
1737
  await definition.handler(event, { concreteOutput: true });
1727
1738
  expect(seen).toEqual([["support"]]);
1728
1739
  });
1729
1740
  it("rejects inherited declaration objects before cloning", () => {
1730
1741
  const declarations = { support: connections.slack() };
1731
- Object.setPrototypeOf(declarations, { extra: connections.calendar() });
1742
+ Object.setPrototypeOf(declarations, {
1743
+ extra: connections.calendar({ scope: "read", readTeammatesCalendars: false }),
1744
+ });
1732
1745
  expect(() =>
1733
1746
  workflow({
1734
1747
  name: "Inherited keys",
@@ -1746,7 +1759,12 @@ describe("workflow", () => {
1746
1759
  name: "Schedule Follow-up",
1747
1760
  description: "Schedules a follow-up after a page is created",
1748
1761
  triggers: [events.notionPageCreated()],
1749
- connections: { "bad/key": connections.calendar() },
1762
+ connections: {
1763
+ "bad/key": connections.calendar({
1764
+ scope: "read",
1765
+ readTeammatesCalendars: false,
1766
+ }),
1767
+ },
1750
1768
  handler: () => {},
1751
1769
  }),
1752
1770
  ).toThrowError("Invalid workflow connection key: bad/key");
@@ -2011,7 +2029,10 @@ describe("connection-bound triggers", () => {
2011
2029
  const definition = workflow({
2012
2030
  name: "Typed triggers",
2013
2031
  description: "Uses only declared provider keys",
2014
- connections: { support: connections.slack(), calendar: connections.calendar() },
2032
+ connections: {
2033
+ support: connections.slack(),
2034
+ calendar: connections.calendar({ scope: "read", readTeammatesCalendars: false }),
2035
+ },
2015
2036
  triggers: ({ events }) => {
2016
2037
  calls += 1;
2017
2038
  expectTypeOf(events.slackMessage)
@@ -2060,7 +2081,11 @@ describe("connection-bound triggers", () => {
2060
2081
  });
2061
2082
  it.each([
2062
2083
  { requirements: {} },
2063
- { requirements: { support: connections.calendar() } },
2084
+ {
2085
+ requirements: {
2086
+ support: connections.calendar({ scope: "read", readTeammatesCalendars: false }),
2087
+ },
2088
+ },
2064
2089
  { requirements: { other: connections.slack() } },
2065
2090
  ])("rejects missing or incompatible declarations: %j", ({ requirements }) => {
2066
2091
  expect(() =>