@notionhq/apps 0.0.13 → 0.0.15
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.
- package/README.md +110 -49
- package/dist/box.generated.d.ts +164 -0
- package/dist/box.generated.d.ts.map +1 -0
- package/dist/box.generated.js +24 -0
- package/dist/calendar.generated.d.ts +1036 -0
- package/dist/calendar.generated.d.ts.map +1 -0
- package/dist/calendar.generated.js +42 -0
- package/dist/cli/build.js +7 -6
- package/dist/cli/emit-manifest.d.ts.map +1 -1
- package/dist/cli/emit-manifest.js +1 -4
- package/dist/confluence.generated.d.ts +98 -0
- package/dist/confluence.generated.d.ts.map +1 -0
- package/dist/confluence.generated.js +18 -0
- package/dist/connection-actions.d.ts +2 -0
- package/dist/connection-actions.d.ts.map +1 -0
- package/dist/connection-actions.js +0 -0
- package/dist/connection-trigger-definitions.generated.d.ts +34 -0
- package/dist/connection-trigger-definitions.generated.d.ts.map +1 -0
- package/dist/connection-trigger-definitions.generated.js +45 -0
- package/dist/connections.d.ts +15 -0
- package/dist/connections.d.ts.map +1 -0
- package/dist/connections.js +79 -0
- package/dist/connections.test.d.ts +2 -0
- package/dist/connections.test.d.ts.map +1 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +3 -0
- package/dist/context.test.d.ts +2 -0
- package/dist/context.test.d.ts.map +1 -0
- package/dist/cursor.generated.d.ts +154 -0
- package/dist/cursor.generated.d.ts.map +1 -0
- package/dist/cursor.generated.js +18 -0
- package/dist/custom-block.d.ts +1 -1
- package/dist/custom-block.d.ts.map +1 -1
- package/dist/custom-block.js +3 -3
- package/dist/discord.generated.d.ts +279 -0
- package/dist/discord.generated.d.ts.map +1 -0
- package/dist/discord.generated.js +33 -0
- package/dist/gmail.generated.d.ts +184 -0
- package/dist/gmail.generated.d.ts.map +1 -0
- package/dist/gmail.generated.js +21 -0
- package/dist/googleCalendar.generated.d.ts +83 -0
- package/dist/googleCalendar.generated.d.ts.map +1 -0
- package/dist/googleCalendar.generated.js +15 -0
- package/dist/googleDrive.generated.d.ts +210 -0
- package/dist/googleDrive.generated.d.ts.map +1 -0
- package/dist/googleDrive.generated.js +27 -0
- package/dist/googleDriveOauth.generated.d.ts +851 -0
- package/dist/googleDriveOauth.generated.d.ts.map +1 -0
- package/dist/googleDriveOauth.generated.js +84 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/outlook.generated.d.ts +224 -0
- package/dist/outlook.generated.d.ts.map +1 -0
- package/dist/outlook.generated.js +24 -0
- package/dist/providers.generated.d.ts +117 -0
- package/dist/providers.generated.d.ts.map +1 -0
- package/dist/providers.generated.js +108 -0
- package/dist/salesforce.generated.d.ts +151 -0
- package/dist/salesforce.generated.d.ts.map +1 -0
- package/dist/salesforce.generated.js +24 -0
- package/dist/slack.generated.d.ts +290 -0
- package/dist/slack.generated.d.ts.map +1 -0
- package/dist/slack.generated.js +30 -0
- package/dist/sync.d.ts +2 -2
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +3 -3
- package/dist/triggers.generated.d.ts +107 -32
- package/dist/triggers.generated.d.ts.map +1 -1
- package/dist/triggers.generated.js +36 -24
- package/dist/workflow-connections-types.test.d.ts +2 -0
- package/dist/workflow-connections-types.test.d.ts.map +1 -0
- package/dist/workflow-types.test.d.ts +2 -0
- package/dist/workflow-types.test.d.ts.map +1 -0
- package/dist/workflow.d.ts +24 -20
- package/dist/workflow.d.ts.map +1 -1
- package/dist/workflow.js +16 -19
- package/docs/BUILD.md +83 -14
- package/docs/CONNECTIONS.md +130 -0
- package/package.json +5 -1
- package/src/box.generated.ts +187 -0
- package/src/calendar.generated.ts +1117 -0
- package/src/cli/build.test.ts +35 -4
- package/src/cli/build.ts +6 -5
- package/src/cli/emit-manifest.ts +1 -5
- package/src/confluence.generated.ts +109 -0
- package/src/connection-actions.ts +2 -0
- package/src/connection-trigger-definitions.generated.ts +48 -0
- package/src/connections.test.ts +157 -0
- package/src/connections.ts +126 -0
- package/src/context.test.ts +26 -0
- package/src/context.ts +8 -0
- package/src/cursor.generated.ts +167 -0
- package/src/custom-block.test.ts +4 -4
- package/src/custom-block.ts +2 -2
- package/src/discord.generated.ts +318 -0
- package/src/gmail.generated.ts +205 -0
- package/src/googleCalendar.generated.ts +95 -0
- package/src/googleDrive.generated.ts +235 -0
- package/src/googleDriveOauth.generated.ts +980 -0
- package/src/index.ts +7 -0
- package/src/outlook.generated.ts +249 -0
- package/src/providers.generated.ts +207 -0
- package/src/salesforce.generated.ts +170 -0
- package/src/slack.generated.ts +330 -0
- package/src/sync.ts +2 -2
- package/src/triggers.generated.ts +112 -32
- package/src/workflow-connections-types.test.ts +59 -0
- package/src/workflow-types.test.ts +144 -0
- package/src/workflow.test.ts +135 -25
- package/src/workflow.ts +63 -34
|
@@ -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, workflow } from "./workflow.js";
|
|
97
|
+
import { triggers as globalTriggers } from "./triggers.generated.js";
|
|
98
|
+
const support = connections.slack({ key: "support" });
|
|
99
|
+
workflow({
|
|
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);
|
package/src/workflow.test.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { afterEach, beforeEach, describe, expect, expectTypeOf, it, vi, type Moc
|
|
|
8
8
|
import { ExecutionError } from "./error.js";
|
|
9
9
|
import type { RunMetadata } from "./runtime-metadata.js";
|
|
10
10
|
import { triggers, type NotionPageCreatedEvent } from "./triggers.generated.js";
|
|
11
|
-
import { connections,
|
|
11
|
+
import { connections, workflow, type StepContext, type WorkflowContext } from "./workflow.js";
|
|
12
12
|
|
|
13
13
|
let stdoutSpy: Mock<typeof process.stdout.write>;
|
|
14
14
|
let checkpointDirectory: string | undefined;
|
|
@@ -37,9 +37,9 @@ 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;
|
|
42
|
-
const
|
|
40
|
+
async function createWorkflowContext(): Promise<WorkflowContext<readonly []>> {
|
|
41
|
+
let context: WorkflowContext<readonly []> | undefined;
|
|
42
|
+
const definition = workflow({
|
|
43
43
|
name: "Test workflow",
|
|
44
44
|
description: "Captures its workflow context",
|
|
45
45
|
triggers: [triggers.notionPageCreated()],
|
|
@@ -48,7 +48,7 @@ async function createWorkflowContext(): Promise<WorkflowContext> {
|
|
|
48
48
|
},
|
|
49
49
|
});
|
|
50
50
|
|
|
51
|
-
await
|
|
51
|
+
await definition.handler(event, { concreteOutput: true });
|
|
52
52
|
|
|
53
53
|
if (!context) {
|
|
54
54
|
throw new Error("Workflow context was not created");
|
|
@@ -638,7 +638,7 @@ describe("step", () => {
|
|
|
638
638
|
|
|
639
639
|
it("resets duplicate key tracking for each workflow call", async () => {
|
|
640
640
|
const keys: string[] = [];
|
|
641
|
-
const
|
|
641
|
+
const definition = workflow({
|
|
642
642
|
name: "Reuse step key",
|
|
643
643
|
description: "Checks that a key can be reused in a new invocation",
|
|
644
644
|
triggers: [triggers.notionPageCreated()],
|
|
@@ -654,14 +654,14 @@ describe("step", () => {
|
|
|
654
654
|
},
|
|
655
655
|
});
|
|
656
656
|
|
|
657
|
-
await
|
|
658
|
-
await
|
|
657
|
+
await definition.handler(event, { concreteOutput: true });
|
|
658
|
+
await definition.handler(event, { concreteOutput: true });
|
|
659
659
|
|
|
660
660
|
expect(keys).toEqual([workflowStepKey(["First"]), workflowStepKey(["First"])]);
|
|
661
661
|
});
|
|
662
662
|
|
|
663
663
|
it("writes step events under concreteOutput", async () => {
|
|
664
|
-
const
|
|
664
|
+
const definition = workflow({
|
|
665
665
|
name: "Log Pages",
|
|
666
666
|
description: "Logs created pages",
|
|
667
667
|
triggers: [triggers.notionPageCreated()],
|
|
@@ -670,7 +670,7 @@ describe("step", () => {
|
|
|
670
670
|
},
|
|
671
671
|
});
|
|
672
672
|
|
|
673
|
-
await
|
|
673
|
+
await definition.handler(event, { concreteOutput: true });
|
|
674
674
|
|
|
675
675
|
expect(stdoutSpy).toHaveBeenCalledTimes(3);
|
|
676
676
|
expect(stdoutSpy.mock.calls[0]?.[0]).toEqual(
|
|
@@ -721,17 +721,17 @@ async function writeRunMetadata(overrides: Partial<RunMetadata> = {}): Promise<v
|
|
|
721
721
|
process.env.NOTION_RUN_METADATA_PATH = metadataPath;
|
|
722
722
|
}
|
|
723
723
|
|
|
724
|
-
describe("
|
|
724
|
+
describe("workflow", () => {
|
|
725
725
|
it("is tagged and exposes triggers in config", () => {
|
|
726
|
-
const
|
|
726
|
+
const definition = workflow({
|
|
727
727
|
name: "Log Pages",
|
|
728
728
|
description: "Logs created pages",
|
|
729
729
|
triggers: [triggers.notionPageCreated()],
|
|
730
730
|
handler: () => {},
|
|
731
731
|
});
|
|
732
732
|
|
|
733
|
-
expect(
|
|
734
|
-
expect(
|
|
733
|
+
expect(definition._tag).toBe("workflow");
|
|
734
|
+
expect(definition.config).toEqual({
|
|
735
735
|
name: "Log Pages",
|
|
736
736
|
description: "Logs created pages",
|
|
737
737
|
triggers: [{ type: "notion.page.created" }],
|
|
@@ -739,7 +739,7 @@ describe("createWorkflow", () => {
|
|
|
739
739
|
});
|
|
740
740
|
|
|
741
741
|
it("declares a Calendar connection requirement", () => {
|
|
742
|
-
const
|
|
742
|
+
const definition = workflow({
|
|
743
743
|
name: "Schedule Follow-up",
|
|
744
744
|
description: "Schedules a follow-up after a page is created",
|
|
745
745
|
triggers: [triggers.notionPageCreated()],
|
|
@@ -747,12 +747,12 @@ describe("createWorkflow", () => {
|
|
|
747
747
|
handler: () => {},
|
|
748
748
|
});
|
|
749
749
|
|
|
750
|
-
expect(
|
|
750
|
+
expect(definition.config.connections).toEqual([{ type: "calendar" }]);
|
|
751
751
|
});
|
|
752
752
|
|
|
753
753
|
it("rejects duplicate connection requirements", () => {
|
|
754
754
|
expect(() =>
|
|
755
|
-
|
|
755
|
+
workflow({
|
|
756
756
|
name: "Schedule Follow-up",
|
|
757
757
|
description: "Schedules a follow-up after a page is created",
|
|
758
758
|
triggers: [triggers.notionPageCreated()],
|
|
@@ -764,14 +764,14 @@ describe("createWorkflow", () => {
|
|
|
764
764
|
|
|
765
765
|
it("writes a success envelope after the handler runs", async () => {
|
|
766
766
|
const handler = vi.fn();
|
|
767
|
-
const
|
|
767
|
+
const definition = workflow({
|
|
768
768
|
name: "Log Pages",
|
|
769
769
|
description: "Logs created pages",
|
|
770
770
|
triggers: [triggers.notionPageCreated()],
|
|
771
771
|
handler,
|
|
772
772
|
});
|
|
773
773
|
|
|
774
|
-
await
|
|
774
|
+
await definition.handler(event);
|
|
775
775
|
|
|
776
776
|
expect(handler).toHaveBeenCalledWith(event, expect.anything());
|
|
777
777
|
expect(stdoutSpy).toHaveBeenCalledWith(
|
|
@@ -784,7 +784,7 @@ describe("createWorkflow", () => {
|
|
|
784
784
|
throw new Error("Metadata directory was not created");
|
|
785
785
|
}
|
|
786
786
|
const handler = vi.fn();
|
|
787
|
-
const
|
|
787
|
+
const definition = workflow({
|
|
788
788
|
name: "Log Pages",
|
|
789
789
|
description: "Logs created pages",
|
|
790
790
|
triggers: [triggers.notionPageCreated()],
|
|
@@ -795,27 +795,27 @@ describe("createWorkflow", () => {
|
|
|
795
795
|
await writeFile(inputPath, JSON.stringify(fileEvent));
|
|
796
796
|
process.env.NOTION_INPUT_PAYLOAD_PATH = inputPath;
|
|
797
797
|
|
|
798
|
-
await
|
|
798
|
+
await definition.handler(event, { concreteOutput: true });
|
|
799
799
|
|
|
800
800
|
expect(handler).toHaveBeenCalledWith(fileEvent, expect.anything());
|
|
801
801
|
});
|
|
802
802
|
|
|
803
803
|
it("returns the status without an envelope under concreteOutput", async () => {
|
|
804
|
-
const
|
|
804
|
+
const definition = workflow({
|
|
805
805
|
name: "Log Pages",
|
|
806
806
|
description: "Logs created pages",
|
|
807
807
|
triggers: [triggers.notionPageCreated()],
|
|
808
808
|
handler: () => {},
|
|
809
809
|
});
|
|
810
810
|
|
|
811
|
-
await expect(
|
|
811
|
+
await expect(definition.handler(event, { concreteOutput: true })).resolves.toEqual({
|
|
812
812
|
status: "success",
|
|
813
813
|
});
|
|
814
814
|
expect(stdoutSpy).not.toHaveBeenCalled();
|
|
815
815
|
});
|
|
816
816
|
|
|
817
817
|
it("writes an error envelope and throws ExecutionError on failure", async () => {
|
|
818
|
-
const
|
|
818
|
+
const definition = workflow({
|
|
819
819
|
name: "Log Pages",
|
|
820
820
|
description: "Logs created pages",
|
|
821
821
|
triggers: [triggers.notionPageCreated()],
|
|
@@ -824,7 +824,7 @@ describe("createWorkflow", () => {
|
|
|
824
824
|
},
|
|
825
825
|
});
|
|
826
826
|
|
|
827
|
-
await expect(
|
|
827
|
+
await expect(definition.handler(event)).rejects.toBeInstanceOf(ExecutionError);
|
|
828
828
|
expect(stdoutSpy).toHaveBeenCalledWith(
|
|
829
829
|
expect.stringContaining(
|
|
830
830
|
`<__notion_output__>{"_tag":"error","error":{"name":"ExecutionError","message":"Error during app execution: Error: boom"`,
|
|
@@ -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 definition = workflow({
|
|
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(definition.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 definition = workflow({
|
|
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(definition.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
|
+
workflow({
|
|
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
|
+
workflow({
|
|
916
|
+
...configuration,
|
|
917
|
+
triggers: [
|
|
918
|
+
triggers.slackMessage({ connectionKey: "support" }),
|
|
919
|
+
triggers.slackMessage({ connectionKey: "support" }),
|
|
920
|
+
],
|
|
921
|
+
}),
|
|
922
|
+
).toThrow("Duplicate");
|
|
923
|
+
expect(() =>
|
|
924
|
+
workflow({
|
|
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
|
+
workflow({
|
|
937
|
+
name: "Existing",
|
|
938
|
+
description: "",
|
|
939
|
+
triggers: [triggers.slackMessage()],
|
|
940
|
+
handler() {},
|
|
941
|
+
}),
|
|
942
|
+
).not.toThrow();
|
|
943
|
+
});
|
|
944
|
+
});
|
package/src/workflow.ts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createWorkflowConnections,
|
|
3
|
+
validateConnectionRequirements,
|
|
4
|
+
validateTriggerConnections,
|
|
5
|
+
type WorkflowConnection,
|
|
6
|
+
type WorkflowConnections,
|
|
7
|
+
} from "./connections.js";
|
|
1
8
|
import { createHash } from "node:crypto";
|
|
2
9
|
import { readFile } from "node:fs/promises";
|
|
3
10
|
import { join } from "node:path";
|
|
@@ -8,7 +15,13 @@ import { ExecutionError } from "./error.js";
|
|
|
8
15
|
import { writeOutput } from "./output.js";
|
|
9
16
|
import { resolveRuntimeInput } from "./runtime-input.js";
|
|
10
17
|
import { readRunMetadata, type RunMetadata } from "./runtime-metadata.js";
|
|
11
|
-
import
|
|
18
|
+
import { createWorkflowTriggers } from "./triggers.generated.js";
|
|
19
|
+
import type {
|
|
20
|
+
WorkflowEventMap,
|
|
21
|
+
WorkflowTrigger,
|
|
22
|
+
WorkflowTriggerCreators,
|
|
23
|
+
WorkflowTriggerForConnections,
|
|
24
|
+
} from "./triggers.generated.js";
|
|
12
25
|
|
|
13
26
|
type HandlerOptions = {
|
|
14
27
|
/** Return the concrete result instead of writing the output envelope. */
|
|
@@ -22,24 +35,14 @@ export type WorkflowEventForTrigger<T extends WorkflowTrigger> = WorkflowEventMa
|
|
|
22
35
|
export type WorkflowEventForTriggers<T extends readonly WorkflowTrigger[]> =
|
|
23
36
|
WorkflowEventForTrigger<T[number]>;
|
|
24
37
|
|
|
25
|
-
|
|
26
|
-
export type WorkflowConnection = {
|
|
27
|
-
type: "calendar";
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
/** Connection requirements supported by workflow capabilities. */
|
|
31
|
-
export const connections = {
|
|
32
|
-
/** Require an authorized Calendar connection. */
|
|
33
|
-
calendar(): WorkflowConnection {
|
|
34
|
-
return { type: "calendar" };
|
|
35
|
-
},
|
|
36
|
-
};
|
|
38
|
+
export { connections, type WorkflowConnection, type WorkflowConnections } from "./connections.js";
|
|
37
39
|
|
|
38
40
|
/**
|
|
39
|
-
* Configuration passed to {@link
|
|
41
|
+
* Configuration passed to {@link workflow}.
|
|
40
42
|
*/
|
|
41
43
|
export type WorkflowConfiguration<
|
|
42
44
|
TTriggers extends readonly [WorkflowTrigger, ...WorkflowTrigger[]],
|
|
45
|
+
TConnections extends readonly WorkflowConnection[] = readonly WorkflowConnection[],
|
|
43
46
|
> = {
|
|
44
47
|
/**
|
|
45
48
|
* A human-readable name for the workflow, shown in the UI when viewing workflows
|
|
@@ -61,16 +64,24 @@ export type WorkflowConfiguration<
|
|
|
61
64
|
/**
|
|
62
65
|
* Connections that must be set up before this workflow can run.
|
|
63
66
|
*/
|
|
64
|
-
connections?:
|
|
67
|
+
connections?: TConnections;
|
|
65
68
|
|
|
66
69
|
handler: (
|
|
67
70
|
event: WorkflowEventForTriggers<TTriggers>,
|
|
68
|
-
context: WorkflowContext
|
|
71
|
+
context: WorkflowContext<TConnections>,
|
|
69
72
|
) => Promise<void> | void;
|
|
70
73
|
};
|
|
71
74
|
|
|
75
|
+
/** Configuration that checks trigger keys against declared connections. */
|
|
76
|
+
export type WorkflowTriggerConfiguration<
|
|
77
|
+
TTriggers extends readonly [WorkflowTrigger, ...WorkflowTrigger[]],
|
|
78
|
+
TConnections extends readonly WorkflowConnection[],
|
|
79
|
+
> = Omit<WorkflowConfiguration<TTriggers, TConnections>, "triggers"> & {
|
|
80
|
+
triggers: (context: { triggers: WorkflowTriggerCreators<NoInfer<TConnections>> }) => TTriggers;
|
|
81
|
+
};
|
|
82
|
+
|
|
72
83
|
/**
|
|
73
|
-
* A workflow capability as returned by {@link
|
|
84
|
+
* A workflow capability as returned by {@link workflow}.
|
|
74
85
|
*
|
|
75
86
|
* Note that a workflow carries no key of its own — the build tool derives
|
|
76
87
|
* the capability key from the file the workflow is default-exported from.
|
|
@@ -104,9 +115,9 @@ export type Workflow<
|
|
|
104
115
|
* ```ts
|
|
105
116
|
* // src/workflows/onPageCreated.ts
|
|
106
117
|
* import { triggers } from "@notionhq/apps/triggers";
|
|
107
|
-
* import {
|
|
118
|
+
* import { workflow } from "@notionhq/apps/workflow";
|
|
108
119
|
*
|
|
109
|
-
* export default
|
|
120
|
+
* export default workflow({
|
|
110
121
|
* name: "Send Welcome Email",
|
|
111
122
|
* description: "Sends a welcome email when a new page is added",
|
|
112
123
|
* triggers: [triggers.notionPageCreated()],
|
|
@@ -120,25 +131,38 @@ export type Workflow<
|
|
|
120
131
|
* });
|
|
121
132
|
* ```
|
|
122
133
|
*/
|
|
123
|
-
export function
|
|
134
|
+
export function workflow<
|
|
135
|
+
const TTriggers extends readonly [
|
|
136
|
+
WorkflowTriggerForConnections<NoInfer<TConnections>>,
|
|
137
|
+
...WorkflowTriggerForConnections<NoInfer<TConnections>>[],
|
|
138
|
+
],
|
|
139
|
+
const TConnections extends readonly WorkflowConnection[] = readonly [],
|
|
140
|
+
>(configuration: WorkflowTriggerConfiguration<TTriggers, TConnections>): Workflow<TTriggers>;
|
|
141
|
+
export function workflow<
|
|
124
142
|
const TTriggers extends readonly [WorkflowTrigger, ...WorkflowTrigger[]],
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
143
|
+
const TConnections extends readonly WorkflowConnection[] = readonly [],
|
|
144
|
+
>(configuration: WorkflowConfiguration<TTriggers, TConnections>): Workflow<TTriggers>;
|
|
145
|
+
export function workflow<
|
|
146
|
+
const TTriggers extends readonly [WorkflowTrigger, ...WorkflowTrigger[]],
|
|
147
|
+
const TConnections extends readonly WorkflowConnection[] = readonly [],
|
|
148
|
+
>(
|
|
149
|
+
configuration:
|
|
150
|
+
| WorkflowConfiguration<TTriggers, TConnections>
|
|
151
|
+
| WorkflowTriggerConfiguration<TTriggers, TConnections>,
|
|
152
|
+
): Workflow<TTriggers> {
|
|
153
|
+
const triggers =
|
|
154
|
+
typeof configuration.triggers === "function"
|
|
155
|
+
? configuration.triggers({ triggers: createWorkflowTriggers<TConnections>() })
|
|
156
|
+
: configuration.triggers;
|
|
157
|
+
validateConnectionRequirements(configuration.connections ?? []);
|
|
158
|
+
validateTriggerConnections(triggers, configuration.connections ?? []);
|
|
135
159
|
|
|
136
160
|
return {
|
|
137
161
|
_tag: "workflow",
|
|
138
162
|
config: {
|
|
139
163
|
name: configuration.name,
|
|
140
164
|
description: configuration.description,
|
|
141
|
-
triggers
|
|
165
|
+
triggers,
|
|
142
166
|
...(configuration.connections === undefined
|
|
143
167
|
? {}
|
|
144
168
|
: { connections: configuration.connections }),
|
|
@@ -150,8 +174,10 @@ export function createWorkflow<
|
|
|
150
174
|
try {
|
|
151
175
|
event = await resolveRuntimeInput(event);
|
|
152
176
|
const runMetadata = readRunMetadata();
|
|
177
|
+
const baseContext = createCapabilityContext();
|
|
153
178
|
const capabilityContext: WorkflowContext = {
|
|
154
|
-
...
|
|
179
|
+
...baseContext,
|
|
180
|
+
connections: createWorkflowConnections(baseContext.notion),
|
|
155
181
|
...runMetadata,
|
|
156
182
|
step: createStep(runMetadata),
|
|
157
183
|
};
|
|
@@ -213,8 +239,11 @@ type WorkflowStep = {
|
|
|
213
239
|
};
|
|
214
240
|
|
|
215
241
|
/** Context passed to a workflow handler. */
|
|
216
|
-
export type WorkflowContext
|
|
217
|
-
|
|
242
|
+
export type WorkflowContext<
|
|
243
|
+
TConnections extends readonly WorkflowConnection[] = readonly WorkflowConnection[],
|
|
244
|
+
> = CapabilityContext & {
|
|
245
|
+
connections: WorkflowConnections<TConnections>;
|
|
246
|
+
} & RunMetadata & {
|
|
218
247
|
/**
|
|
219
248
|
* Run an observable unit of work within the workflow.
|
|
220
249
|
*
|