@notionhq/apps 0.0.33 → 0.0.35

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 (60) hide show
  1. package/README.md +59 -5
  2. package/dist/configured-events.d.ts +45 -0
  3. package/dist/configured-events.d.ts.map +1 -0
  4. package/dist/configured-events.js +38 -0
  5. package/dist/configured-events.test.d.ts +2 -0
  6. package/dist/configured-events.test.d.ts.map +1 -0
  7. package/dist/custom-block.d.ts +1 -1
  8. package/dist/events-types.test.d.ts +2 -0
  9. package/dist/events-types.test.d.ts.map +1 -0
  10. package/dist/{triggers.d.ts → events.d.ts} +1 -1
  11. package/dist/events.d.ts.map +1 -0
  12. package/dist/{triggers.generated.d.ts → events.generated.d.ts} +20 -38
  13. package/dist/events.generated.d.ts.map +1 -0
  14. package/dist/{triggers.generated.js → events.generated.js} +9 -35
  15. package/dist/index.d.ts +1 -0
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +2 -0
  18. package/dist/notion-as-code/custom-agent.d.ts +15 -60
  19. package/dist/notion-as-code/custom-agent.d.ts.map +1 -1
  20. package/dist/notion-as-code/custom-agent.js +15 -1
  21. package/dist/notion-as-code/database.d.ts +11 -0
  22. package/dist/notion-as-code/database.d.ts.map +1 -1
  23. package/dist/notion-as-code/database.js +12 -0
  24. package/dist/notion-as-code/index.d.ts +1 -1
  25. package/dist/notion-as-code/index.d.ts.map +1 -1
  26. package/dist/notion-as-code/intents.d.ts +7 -1
  27. package/dist/notion-as-code/intents.d.ts.map +1 -1
  28. package/dist/notion-as-code/recurrence.d.ts +49 -0
  29. package/dist/notion-as-code/recurrence.d.ts.map +1 -0
  30. package/dist/notion-as-code/recurrence.js +0 -0
  31. package/dist/workflow.d.ts +4 -4
  32. package/dist/workflow.d.ts.map +1 -1
  33. package/dist/workflow.js +2 -2
  34. package/package.json +7 -7
  35. package/skills/connections/SKILL.md +3 -3
  36. package/skills/notion-as-code/SKILL.md +11 -7
  37. package/skills/workflow/SKILL.md +25 -5
  38. package/src/cli/build.test.ts +30 -0
  39. package/src/configured-events.test.ts +90 -0
  40. package/src/configured-events.ts +101 -0
  41. package/src/connections.ts +1 -1
  42. package/src/events-types.test.ts +110 -0
  43. package/src/{triggers.generated.ts → events.generated.ts} +21 -55
  44. package/src/index.ts +1 -0
  45. package/src/notion-as-code/custom-agent.test.ts +82 -2
  46. package/src/notion-as-code/custom-agent.ts +46 -74
  47. package/src/notion-as-code/database.ts +27 -0
  48. package/src/notion-as-code/index.ts +2 -0
  49. package/src/notion-as-code/intents.ts +2 -1
  50. package/src/notion-as-code/notion-as-code-types.test.ts +4 -1
  51. package/src/notion-as-code/recurrence.ts +58 -0
  52. package/src/workflow-access-types.test.ts +4 -4
  53. package/src/workflow-connections-types.test.ts +7 -7
  54. package/src/workflow-types.test.ts +23 -23
  55. package/src/workflow.test.ts +39 -39
  56. package/src/workflow.ts +7 -7
  57. package/dist/triggers.d.ts.map +0 -1
  58. package/dist/triggers.generated.d.ts.map +0 -1
  59. /package/dist/{triggers.js → events.js} +0 -0
  60. /package/src/{triggers.ts → events.ts} +0 -0
@@ -0,0 +1,90 @@
1
+ import { describe, expect, expectTypeOf, it } from "vitest";
2
+
3
+ import { events } from "./events.generated.js";
4
+ import { database } from "./notion-as-code/database.js";
5
+ import { finishMetadataRecording, startMetadataRecording } from "./notion-as-code/recorder.js";
6
+ import type { DataSourceHandle } from "./notion-as-code/database.js";
7
+
8
+ function tasks() {
9
+ const recorder = startMetadataRecording();
10
+ const handle = database("tasks-db", {
11
+ dataSourceResourceId: "tasks",
12
+ name: "Tasks",
13
+ schema: {
14
+ Name: { resourceId: "name", type: "title" },
15
+ Status: { resourceId: "status", type: "select", options: [] },
16
+ },
17
+ });
18
+ finishMetadataRecording(recorder);
19
+ return handle.dataSource;
20
+ }
21
+
22
+ describe("configured Apps events", () => {
23
+ it("serializes property handles without including declaration objects", () => {
24
+ const dataSource = tasks();
25
+ expectTypeOf(dataSource.properties.Status.resourceId).toEqualTypeOf<"status">();
26
+ expectTypeOf(dataSource.properties.Status.propertyType).toEqualTypeOf<"select">();
27
+ expect(
28
+ events.notionPageUpdated({ dataSource, properties: [dataSource.properties.Status] }),
29
+ ).toEqual({
30
+ type: "notion.page.updated",
31
+ dataSourceResourceId: "tasks",
32
+ propertyResourceIds: ["status"],
33
+ includePageContent: false,
34
+ });
35
+ });
36
+ it("serializes a page-created data source", () => {
37
+ expect(events.notionPageCreated({ dataSource: tasks() })).toEqual({
38
+ type: "notion.page.created",
39
+ dataSourceResourceId: "tasks",
40
+ });
41
+ });
42
+ it("supports page-content-only watches", () => {
43
+ expect(events.notionPageUpdated({ dataSource: tasks(), includePageContent: true })).toEqual(
44
+ {
45
+ type: "notion.page.updated",
46
+ dataSourceResourceId: "tasks",
47
+ includePageContent: true,
48
+ },
49
+ );
50
+ });
51
+ it("checks property ownership even when source types have been widened", () => {
52
+ const original = tasks();
53
+ const dataSource: DataSourceHandle = original;
54
+ const status = original.properties.Status;
55
+ if (!status) throw new Error("Missing Status property");
56
+ const property = { ...status, dataSourceResourceId: "other-source" };
57
+ expect(() => events.notionPageUpdated({ dataSource, properties: [property] })).toThrow(
58
+ "does not belong",
59
+ );
60
+ });
61
+ it("serializes weekly schedule details", () => {
62
+ expect(
63
+ events.scheduled({
64
+ frequency: "week",
65
+ interval: 1,
66
+ weekdays: { MO: true, FR: true },
67
+ start: "2026-09-23T09:00:00",
68
+ timeZone: "America/Los_Angeles",
69
+ }),
70
+ ).toEqual({
71
+ type: "recurrence",
72
+ frequency: "week",
73
+ interval: 1,
74
+ weekdays: { MO: true, FR: true },
75
+ start: "2026-09-23T09:00:00",
76
+ timeZone: "America/Los_Angeles",
77
+ });
78
+ });
79
+ it("retains unconfigured events for setup in Notion", () => {
80
+ expect([
81
+ events.scheduled(),
82
+ events.notionPageCreated(),
83
+ events.notionPageUpdated(),
84
+ ]).toEqual([
85
+ { type: "recurrence" },
86
+ { type: "notion.page.created" },
87
+ { type: "notion.page.updated" },
88
+ ]);
89
+ });
90
+ });
@@ -0,0 +1,101 @@
1
+ import type { DataSourceHandle, PropertyHandle } from "./notion-as-code/database.js";
2
+ import type { NotionAsCodeSchema } from "./notion-as-code/intents.js";
3
+ import type { RecurrenceSchedule } from "./notion-as-code/recurrence.js";
4
+
5
+ export type RecurrenceTrigger = { type: "recurrence" } & (
6
+ | RecurrenceSchedule
7
+ | { frequency?: never }
8
+ );
9
+ export type NotionPageCreatedTrigger = {
10
+ type: "notion.page.created";
11
+ dataSourceResourceId?: string;
12
+ };
13
+ export type NotionPageUpdatedTrigger = {
14
+ type: "notion.page.updated";
15
+ dataSourceResourceId?: string;
16
+ propertyResourceIds?: string[];
17
+ includePageContent?: boolean;
18
+ };
19
+
20
+ type WatchablePropertyType =
21
+ | "title"
22
+ | "text"
23
+ | "number"
24
+ | "select"
25
+ | "multi_select"
26
+ | "status"
27
+ | "date"
28
+ | "person"
29
+ | "checkbox"
30
+ | "url"
31
+ | "email"
32
+ | "phone_number"
33
+ | "relation";
34
+
35
+ type WatchableProperty<Property extends NotionAsCodeSchema[string]> = Property extends {
36
+ type: WatchablePropertyType;
37
+ }
38
+ ? PropertyHandle<Property>
39
+ : never;
40
+
41
+ type SourceProperty<Schema extends NotionAsCodeSchema> = WatchableProperty<Schema[keyof Schema]>;
42
+ export type PageUpdatedOptions<Schema extends NotionAsCodeSchema> = {
43
+ dataSource: DataSourceHandle<Schema>;
44
+ } & (
45
+ | {
46
+ properties: readonly [
47
+ SourceProperty<NoInfer<Schema>>,
48
+ ...SourceProperty<NoInfer<Schema>>[],
49
+ ];
50
+ includePageContent?: boolean;
51
+ }
52
+ | { properties?: readonly SourceProperty<NoInfer<Schema>>[]; includePageContent: true }
53
+ );
54
+
55
+ /** Configure a recurrence, or omit options to set it up in Notion. */
56
+ export function scheduled(options?: RecurrenceSchedule): RecurrenceTrigger {
57
+ return { type: "recurrence", ...options };
58
+ }
59
+
60
+ /** Watch new rows in a declared data source, or configure the source in Notion. */
61
+ export function notionPageCreated(options?: {
62
+ dataSource: DataSourceHandle;
63
+ }): NotionPageCreatedTrigger {
64
+ return {
65
+ type: "notion.page.created",
66
+ ...(options ? { dataSourceResourceId: options.dataSource.resourceId } : {}),
67
+ };
68
+ }
69
+
70
+ /** Watch typed properties and/or page content in a declared data source. */
71
+ export function notionPageUpdated<const Schema extends NotionAsCodeSchema>(
72
+ options?: PageUpdatedOptions<Schema>,
73
+ ): NotionPageUpdatedTrigger {
74
+ if (!options) return { type: "notion.page.updated" };
75
+ const { dataSource, properties = [], includePageContent = false } = options;
76
+ if (properties.length === 0 && !includePageContent) {
77
+ throw new Error("Page-updated events must watch at least one property or page content");
78
+ }
79
+ for (const property of properties) {
80
+ if (
81
+ property.dataSourceResourceId !== dataSource.resourceId ||
82
+ dataSource.properties[property.name]?.resourceId !== property.resourceId
83
+ ) {
84
+ throw new Error(
85
+ `Property "${property.name}" does not belong to data source "${dataSource.resourceId}"`,
86
+ );
87
+ }
88
+ }
89
+ return {
90
+ type: "notion.page.updated",
91
+ dataSourceResourceId: dataSource.resourceId,
92
+ ...(properties.length
93
+ ? {
94
+ propertyResourceIds: [
95
+ ...new Set(properties.map((property) => property.resourceId)),
96
+ ],
97
+ }
98
+ : {}),
99
+ includePageContent,
100
+ };
101
+ }
@@ -1,6 +1,6 @@
1
1
  import { validateOAuthConfiguration, type OAuthConfiguration } from "./oauth.js";
2
2
  import { connectionTriggerDefinitions } from "./connection-trigger-definitions.generated.js";
3
- import type { WorkflowTrigger } from "./triggers.generated.js";
3
+ import type { WorkflowTrigger } from "./events.generated.js";
4
4
  import type { Client } from "@notionhq/client";
5
5
  import {
6
6
  connections as providerConnections,
@@ -0,0 +1,110 @@
1
+ import { resolve } from "node:path";
2
+ import ts from "typescript";
3
+ import { expect, it } from "vitest";
4
+
5
+ it("checks property references and schedule variants in user code", () => {
6
+ const cases = [
7
+ {
8
+ name: "property",
9
+ valid: true,
10
+ expression:
11
+ "events.notionPageUpdated({ dataSource: tasks.dataSource, properties: [tasks.dataSource.properties.Status] })",
12
+ },
13
+ {
14
+ name: "content",
15
+ valid: true,
16
+ expression:
17
+ "events.notionPageUpdated({ dataSource: tasks.dataSource, includePageContent: true })",
18
+ },
19
+ {
20
+ name: "typo",
21
+ valid: false,
22
+ expression:
23
+ "events.notionPageUpdated({ dataSource: tasks.dataSource, properties: [tasks.dataSource.properties.Statuz] })",
24
+ },
25
+ {
26
+ name: "string",
27
+ valid: false,
28
+ expression:
29
+ 'events.notionPageUpdated({ dataSource: tasks.dataSource, properties: ["Status"] })',
30
+ },
31
+ {
32
+ name: "other_source",
33
+ valid: false,
34
+ expression:
35
+ "events.notionPageUpdated({ dataSource: tasks.dataSource, properties: [other.dataSource.properties.Name] })",
36
+ },
37
+ {
38
+ name: "unsupported_property",
39
+ valid: false,
40
+ expression:
41
+ "events.notionPageUpdated({ dataSource: tasks.dataSource, properties: [tasks.dataSource.properties.Files] })",
42
+ },
43
+ {
44
+ name: "empty",
45
+ valid: false,
46
+ expression:
47
+ "events.notionPageUpdated({ dataSource: tasks.dataSource, properties: [] })",
48
+ },
49
+ {
50
+ name: "no_watch",
51
+ valid: false,
52
+ expression: "events.notionPageUpdated({ dataSource: tasks.dataSource })",
53
+ },
54
+ {
55
+ name: "weekly",
56
+ valid: true,
57
+ expression:
58
+ 'events.scheduled({ frequency: "week", interval: 1, weekdays: { MO: true }, start: "2026-09-23", timeZone: "UTC" })',
59
+ },
60
+ {
61
+ name: "no_weekday",
62
+ valid: false,
63
+ expression:
64
+ 'events.scheduled({ frequency: "week", interval: 1, weekdays: {}, start: "2026-09-23", timeZone: "UTC" })',
65
+ },
66
+ ];
67
+ const fixtures = new Map(
68
+ cases.map((testCase) => [
69
+ resolve("src", `__events_${testCase.name}.ts`),
70
+ `
71
+ import { database, events } from "./index.js";
72
+ const tasks = database("tasks", { name: "Tasks", dataSourceResourceId: "tasks-source", schema: {
73
+ Name: { type: "title", resourceId: "name" }, Status: { type: "select", resourceId: "status", options: [] }, Files: { type: "file", resourceId: "files" }
74
+ } });
75
+ const other = database("other", { name: "Other", dataSourceResourceId: "other-source", schema: {
76
+ Name: { type: "title", resourceId: "other-name" }
77
+ } });
78
+ ${testCase.expression};
79
+ `,
80
+ ]),
81
+ );
82
+ const config = ts.readConfigFile(resolve("tsconfig.json"), ts.sys.readFile);
83
+ const parsed = ts.parseJsonConfigFileContent(config.config, ts.sys, process.cwd());
84
+ const host = ts.createCompilerHost(parsed.options);
85
+ const getSourceFile = host.getSourceFile.bind(host);
86
+ host.getSourceFile = (fileName, languageVersion, onError, shouldCreateNewSourceFile) => {
87
+ const fixture = fixtures.get(fileName);
88
+ return fixture === undefined
89
+ ? getSourceFile(fileName, languageVersion, onError, shouldCreateNewSourceFile)
90
+ : ts.createSourceFile(fileName, fixture, languageVersion, true);
91
+ };
92
+ const program = ts.createProgram(
93
+ [...fixtures.keys()],
94
+ { ...parsed.options, noEmit: true },
95
+ host,
96
+ );
97
+ const diagnostics = ts.getPreEmitDiagnostics(program);
98
+ expect(
99
+ diagnostics.filter(
100
+ (diagnostic) => !diagnostic.file || !fixtures.has(diagnostic.file.fileName),
101
+ ),
102
+ ).toEqual([]);
103
+ for (const testCase of cases) {
104
+ const errors = diagnostics.filter(
105
+ (diagnostic) =>
106
+ diagnostic.file?.fileName === resolve("src", `__events_${testCase.name}.ts`),
107
+ );
108
+ expect(errors.length === 0, testCase.name).toBe(testCase.valid);
109
+ }
110
+ });
@@ -1,7 +1,12 @@
1
- // Generated by scripts/generate-triggers.ts — do not edit by hand.
1
+ // Generated by scripts/generate-events.ts — do not edit by hand.
2
2
  // Regenerate with: pnpm run generate
3
3
 
4
4
  import type { WorkflowConnectionDeclarations, WorkflowConnectionKeys } from "./connections.js";
5
+ import { scheduled, notionPageCreated, notionPageUpdated } from "./configured-events.js";
6
+ import type { NotionPageCreatedTrigger, NotionPageUpdatedTrigger, PageUpdatedOptions, RecurrenceTrigger } from "./configured-events.js";
7
+ import type { DataSourceHandle } from "./notion-as-code/database.js";
8
+ import type { NotionAsCodeSchema } from "./notion-as-code/intents.js";
9
+ import type { RecurrenceSchedule } from "./notion-as-code/recurrence.js";
5
10
 
6
11
  import type {
7
12
  CalendarEventCanceledEvent,
@@ -25,7 +30,7 @@ import type {
25
30
  SlackMessageEvent,
26
31
  SlackReactionAddedEvent,
27
32
  WebhookEvent,
28
- } from "./triggers.js";
33
+ } from "./events.js";
29
34
 
30
35
  export type {
31
36
  Actor,
@@ -72,7 +77,10 @@ export type {
72
77
  VendorActor,
73
78
  VendorExtensions,
74
79
  WebhookEvent,
75
- } from "./triggers.js";
80
+ } from "./events.js";
81
+
82
+ export type { NotionPageCreatedTrigger, NotionPageUpdatedTrigger, PageUpdatedOptions, RecurrenceTrigger } from "./configured-events.js";
83
+ export type { RecurrenceSchedule } from "./notion-as-code/recurrence.js";
76
84
 
77
85
  /** Triggers immediately after a new message is posted in Slack (includes new threads and replies) */
78
86
  export type SlackMessageTrigger<TKey extends string = string> = {
@@ -144,16 +152,6 @@ export type CalendarEventCanceledTrigger<TKey extends string = string> = {
144
152
  connectionKey?: TKey;
145
153
  };
146
154
 
147
- /** A page has been added to a database. */
148
- export type NotionPageCreatedTrigger = {
149
- type: "notion.page.created";
150
- };
151
-
152
- /** A page was updated. */
153
- export type NotionPageUpdatedTrigger = {
154
- type: "notion.page.updated";
155
- };
156
-
157
155
  /** A page was deleted. */
158
156
  export type NotionPageDeletedTrigger = {
159
157
  type: "notion.page.deleted";
@@ -196,13 +194,8 @@ export type GoogleDriveOauthFilesChangedTrigger<TKey extends string = string> =
196
194
  connectionKey?: TKey;
197
195
  };
198
196
 
199
- /** A recurring schedule fired. */
200
- export type RecurrenceTrigger = {
201
- type: "recurrence";
202
- };
203
-
204
197
  /** Creators for each event that can trigger a workflow. */
205
- export const triggers = {
198
+ export const events = {
206
199
  /**
207
200
  * Declare that a workflow can run on `slack.message` events.
208
201
  *
@@ -323,27 +316,9 @@ export const triggers = {
323
316
  };
324
317
  },
325
318
 
326
- /**
327
- * Declare that a workflow can run on `notion.page.created` events.
328
- *
329
- * A page has been added to a database.
330
- */
331
- notionPageCreated(): NotionPageCreatedTrigger {
332
- return {
333
- type: "notion.page.created",
334
- };
335
- },
319
+ notionPageCreated,
336
320
 
337
- /**
338
- * Declare that a workflow can run on `notion.page.updated` events.
339
- *
340
- * A page was updated.
341
- */
342
- notionPageUpdated(): NotionPageUpdatedTrigger {
343
- return {
344
- type: "notion.page.updated",
345
- };
346
- },
321
+ notionPageUpdated,
347
322
 
348
323
  /**
349
324
  * Declare that a workflow can run on `notion.page.deleted` events.
@@ -434,20 +409,11 @@ export const triggers = {
434
409
  };
435
410
  },
436
411
 
437
- /**
438
- * Declare that a workflow can run on `recurrence` events.
439
- *
440
- * A recurring schedule fired.
441
- */
442
- scheduled(): RecurrenceTrigger {
443
- return {
444
- type: "recurrence",
445
- };
446
- },
412
+ scheduled,
447
413
  } as const;
448
414
 
449
415
  /** Trigger creators restricted to the workflow's declared provider keys. */
450
- export type WorkflowTriggerCreators<TConnections extends WorkflowConnectionDeclarations> = {
416
+ export type WorkflowEventCreators<TConnections extends WorkflowConnectionDeclarations> = {
451
417
  slackMessage(options?: { connectionKey: WorkflowConnectionKeys<TConnections, "slack"> }): SlackMessageTrigger<WorkflowConnectionKeys<TConnections, "slack">>;
452
418
  slackReactionAdded(options?: { connectionKey: WorkflowConnectionKeys<TConnections, "slack"> }): SlackReactionAddedTrigger<WorkflowConnectionKeys<TConnections, "slack">>;
453
419
  slackAppMention(options?: { connectionKey: WorkflowConnectionKeys<TConnections, "slack"> }): SlackAppMentionTrigger<WorkflowConnectionKeys<TConnections, "slack">>;
@@ -458,8 +424,8 @@ export type WorkflowTriggerCreators<TConnections extends WorkflowConnectionDecla
458
424
  calendarEventCreated(options?: { connectionKey: WorkflowConnectionKeys<TConnections, "calendar"> }): CalendarEventCreatedTrigger<WorkflowConnectionKeys<TConnections, "calendar">>;
459
425
  calendarEventUpdated(options?: { connectionKey: WorkflowConnectionKeys<TConnections, "calendar"> }): CalendarEventUpdatedTrigger<WorkflowConnectionKeys<TConnections, "calendar">>;
460
426
  calendarEventCanceled(options?: { connectionKey: WorkflowConnectionKeys<TConnections, "calendar"> }): CalendarEventCanceledTrigger<WorkflowConnectionKeys<TConnections, "calendar">>;
461
- notionPageCreated(): NotionPageCreatedTrigger;
462
- notionPageUpdated(): NotionPageUpdatedTrigger;
427
+ notionPageCreated(options?: { dataSource: DataSourceHandle }): NotionPageCreatedTrigger;
428
+ notionPageUpdated<const Schema extends NotionAsCodeSchema>(options?: PageUpdatedOptions<Schema>): NotionPageUpdatedTrigger;
463
429
  notionPageDeleted(): NotionPageDeletedTrigger;
464
430
  notionMeetingNoteSummaryCompleted(): NotionMeetingNoteSummaryCompletedTrigger;
465
431
  notionCommentAdded(): NotionCommentAddedTrigger;
@@ -468,7 +434,7 @@ export type WorkflowTriggerCreators<TConnections extends WorkflowConnectionDecla
468
434
  notionDatabaseAgentUpdated(): NotionDatabaseAgentUpdatedTrigger;
469
435
  webhook(): WebhookTrigger;
470
436
  googleDriveOauthFilesChanged(options?: { connectionKey: WorkflowConnectionKeys<TConnections, "googleDriveOauth"> }): GoogleDriveOauthFilesChangedTrigger<WorkflowConnectionKeys<TConnections, "googleDriveOauth">>;
471
- scheduled(): RecurrenceTrigger;
437
+ scheduled(options?: RecurrenceSchedule): RecurrenceTrigger;
472
438
  };
473
439
 
474
440
  /** Triggers whose connection keys belong to the declared provider. */
@@ -496,8 +462,8 @@ export type WorkflowTriggerForConnections<TConnections extends WorkflowConnectio
496
462
  | RecurrenceTrigger;
497
463
 
498
464
  /** @internal */
499
- export function createWorkflowTriggers<TConnections extends WorkflowConnectionDeclarations>(): WorkflowTriggerCreators<TConnections> {
500
- return triggers;
465
+ export function createWorkflowEvents<TConnections extends WorkflowConnectionDeclarations>(): WorkflowEventCreators<TConnections> {
466
+ return events;
501
467
  }
502
468
 
503
469
  /** Event that can trigger a workflow. */
package/src/index.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export { events } from "./events.generated.js";
1
2
  export { customBlock, type CustomBlockHandle } from "./custom-block.js";
2
3
  export { customAgent } from "./notion-as-code/custom-agent.js";
3
4
  export { database } from "./notion-as-code/database.js";
@@ -18,7 +18,8 @@ describe("Notion-as-Code custom agent declarations", () => {
18
18
  resourceId: "triage-agent",
19
19
  name: "Triage agent",
20
20
  webAccess: true,
21
- trustedUrls: { type: "all" },
21
+ browserUse: true,
22
+ trustedUrls: ["docs.example.com/*", "support.example.com"],
22
23
  access: [access.edit(tasks)],
23
24
  triggers: [
24
25
  {
@@ -30,6 +31,11 @@ describe("Notion-as-Code custom agent declarations", () => {
30
31
  timeZone: "America/Los_Angeles",
31
32
  weekdays: { MO: true, FR: true },
32
33
  },
34
+ {
35
+ type: "page_added",
36
+ resourceId: "task-added-trigger",
37
+ dataSourceResourceId: "tasks",
38
+ },
33
39
  {
34
40
  type: "property_updated",
35
41
  resourceId: "task-update-trigger",
@@ -48,7 +54,8 @@ describe("Notion-as-Code custom agent declarations", () => {
48
54
  resourceId: "triage-agent",
49
55
  name: "Triage agent",
50
56
  webAccess: true,
51
- trustedUrls: { type: "all" },
57
+ browserUse: true,
58
+ trustedUrls: { type: "list", urls: ["docs.example.com/*", "support.example.com"] },
52
59
  access: [{ resourceId: "tasks-db", level: "edit" }],
53
60
  triggers: [
54
61
  {
@@ -60,9 +67,16 @@ describe("Notion-as-Code custom agent declarations", () => {
60
67
  timeZone: "America/Los_Angeles",
61
68
  weekdays: { MO: true, FR: true },
62
69
  },
70
+ {
71
+ type: "page_added",
72
+ resourceId: "task-added-trigger",
73
+ dataSourceResourceId: "tasks",
74
+ waitForEditsToFinish: false,
75
+ },
63
76
  {
64
77
  type: "property_updated",
65
78
  resourceId: "task-update-trigger",
79
+ waitForEditsToFinish: false,
66
80
  dataSourceResourceId: "tasks",
67
81
  propertyConditions: {
68
82
  status: { type: "property_edited" },
@@ -74,6 +88,72 @@ describe("Notion-as-Code custom agent declarations", () => {
74
88
  ]);
75
89
  });
76
90
 
91
+ it("records browser disablement and no trusted URLs", () => {
92
+ const recorder = startMetadataRecording();
93
+ notion.customAgent({
94
+ resourceId: "triage-agent",
95
+ name: "Triage agent",
96
+ browserUse: false,
97
+ trustedUrls: "none",
98
+ });
99
+ expect(finishMetadataRecording(recorder).intents).toEqual([
100
+ {
101
+ type: "custom_agent",
102
+ resourceId: "triage-agent",
103
+ name: "Triage agent",
104
+ browserUse: false,
105
+ trustedUrls: { type: "list", urls: [] },
106
+ },
107
+ ]);
108
+ });
109
+
110
+ it("records unrestricted trust and an explicit wait for edits", () => {
111
+ const recorder = startMetadataRecording();
112
+ notion.customAgent({
113
+ resourceId: "triage-agent",
114
+ name: "Triage agent",
115
+ trustedUrls: "all",
116
+ triggers: [
117
+ {
118
+ type: "page_added",
119
+ resourceId: "task-added-trigger",
120
+ dataSourceResourceId: "tasks",
121
+ waitForEditsToFinish: true,
122
+ },
123
+ {
124
+ type: "property_updated",
125
+ resourceId: "task-update-trigger",
126
+ dataSourceResourceId: "tasks",
127
+ triggerWhenPageContentEdited: true,
128
+ waitForEditsToFinish: true,
129
+ },
130
+ ],
131
+ });
132
+ expect(finishMetadataRecording(recorder).intents).toEqual([
133
+ {
134
+ type: "custom_agent",
135
+ resourceId: "triage-agent",
136
+ name: "Triage agent",
137
+ trustedUrls: { type: "all" },
138
+ triggers: [
139
+ {
140
+ type: "page_added",
141
+ resourceId: "task-added-trigger",
142
+ dataSourceResourceId: "tasks",
143
+ waitForEditsToFinish: true,
144
+ },
145
+ {
146
+ type: "property_updated",
147
+ resourceId: "task-update-trigger",
148
+ dataSourceResourceId: "tasks",
149
+ triggerWhenPageContentEdited: true,
150
+ waitForEditsToFinish: true,
151
+ },
152
+ ],
153
+ },
154
+ ]);
155
+ });
156
+
77
157
  it("rejects reserved resource IDs inside triggers", () => {
78
158
  expect(() =>
79
159
  notion.customAgent({