@notionhq/workers 0.4.0 → 0.5.0

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 (44) hide show
  1. package/dist/block.d.ts +1 -1
  2. package/dist/block.d.ts.map +1 -1
  3. package/dist/builder.d.ts +7 -1
  4. package/dist/builder.d.ts.map +1 -1
  5. package/dist/builder.js +13 -0
  6. package/dist/capabilities/context.js +1 -1
  7. package/dist/capabilities/stateful-capability.js +1 -1
  8. package/dist/capabilities/sync.d.ts +11 -1
  9. package/dist/capabilities/sync.d.ts.map +1 -1
  10. package/dist/capabilities/workflow.d.ts +50 -0
  11. package/dist/capabilities/workflow.d.ts.map +1 -0
  12. package/dist/capabilities/workflow.js +40 -0
  13. package/dist/capabilities/workflow.test.d.ts +2 -0
  14. package/dist/capabilities/workflow.test.d.ts.map +1 -0
  15. package/dist/index.d.ts +3 -2
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +8 -1
  18. package/dist/schema-builder.d.ts.map +1 -1
  19. package/dist/triggers.d.ts +365 -0
  20. package/dist/triggers.d.ts.map +1 -0
  21. package/dist/triggers.generated.d.ts +246 -0
  22. package/dist/triggers.generated.d.ts.map +1 -0
  23. package/dist/triggers.generated.js +239 -0
  24. package/dist/triggers.js +0 -0
  25. package/dist/types.d.ts +18 -0
  26. package/dist/types.d.ts.map +1 -1
  27. package/dist/worker.d.ts +32 -2
  28. package/dist/worker.d.ts.map +1 -1
  29. package/dist/worker.js +34 -0
  30. package/package.json +8 -1
  31. package/src/block.ts +40 -40
  32. package/src/builder.ts +20 -0
  33. package/src/capabilities/context.ts +2 -2
  34. package/src/capabilities/stateful-capability.ts +1 -1
  35. package/src/capabilities/sync.test.ts +64 -0
  36. package/src/capabilities/sync.ts +6 -0
  37. package/src/capabilities/workflow.test.ts +184 -0
  38. package/src/capabilities/workflow.ts +119 -0
  39. package/src/index.ts +16 -1
  40. package/src/schema-builder.ts +1 -0
  41. package/src/triggers.generated.ts +489 -0
  42. package/src/triggers.ts +504 -0
  43. package/src/types.ts +20 -0
  44. package/src/worker.ts +99 -8
@@ -0,0 +1,184 @@
1
+ import {
2
+ afterEach,
3
+ beforeEach,
4
+ describe,
5
+ expect,
6
+ expectTypeOf,
7
+ it,
8
+ type Mock,
9
+ vi,
10
+ } from "vitest";
11
+ import { ExecutionError } from "../error.js";
12
+ import {
13
+ type CapabilityContext,
14
+ Worker,
15
+ type WorkflowConfiguration,
16
+ } from "../index.js";
17
+ import {
18
+ type NotionPageCreatedEvent,
19
+ type NotionPageCreatedTrigger,
20
+ triggers,
21
+ } from "../triggers.generated.js";
22
+ import { createWorkflowCapability } from "./workflow.js";
23
+
24
+ const pageCreatedEvent: NotionPageCreatedEvent = {
25
+ type: "notion.page.created",
26
+ vendor: "notion",
27
+ message: "Page created",
28
+ actor: {
29
+ id: "user-1",
30
+ displayName: "Ada Lovelace",
31
+ email: "ada@example.com",
32
+ vendorActor: {
33
+ vendor: "notion",
34
+ id: "user-1",
35
+ displayName: "Ada Lovelace",
36
+ handle: null,
37
+ type: "user",
38
+ url: null,
39
+ },
40
+ },
41
+ url: "https://www.notion.so/page-1",
42
+ timestamp: "2026-06-26T12:00:00.000Z",
43
+ page: {
44
+ id: "page-1",
45
+ Name: "Welcome",
46
+ Status: "New",
47
+ },
48
+ content: "# Welcome\n\nHello, world!",
49
+ edits: [
50
+ {
51
+ before: null,
52
+ after: {
53
+ id: "page-1",
54
+ Name: "Welcome",
55
+ Status: "New",
56
+ },
57
+ afterContent: "# Welcome\n\nHello, world!",
58
+ contentChanged: true,
59
+ timestamp: 1_782_477_600_000,
60
+ editedBy: null,
61
+ },
62
+ ],
63
+ };
64
+
65
+ describe("createWorkflowCapability", () => {
66
+ let stdoutSpy: Mock<typeof process.stdout.write>;
67
+
68
+ beforeEach(() => {
69
+ stdoutSpy = vi
70
+ .spyOn(process.stdout, "write")
71
+ .mockImplementation(() => true);
72
+ });
73
+
74
+ afterEach(() => {
75
+ vi.restoreAllMocks();
76
+ });
77
+
78
+ it("declares a page-created trigger without a database", () => {
79
+ expect(triggers.notionPageCreated()).toEqual({
80
+ type: "notion.page.created",
81
+ });
82
+ });
83
+
84
+ it("requires trigger declarations in workflow configuration", () => {
85
+ type PageAddedConfiguration = WorkflowConfiguration<
86
+ readonly [NotionPageCreatedTrigger]
87
+ >;
88
+ type ConfigurationWithoutTriggers = Omit<
89
+ PageAddedConfiguration,
90
+ "triggers"
91
+ >;
92
+
93
+ expectTypeOf<ConfigurationWithoutTriggers>().not.toMatchTypeOf<PageAddedConfiguration>();
94
+ });
95
+
96
+ it("infers the canonical page-created event and capability context", () => {
97
+ createWorkflowCapability("pageAdded", {
98
+ title: "Page Added",
99
+ description: "Runs when a page is added",
100
+ triggers: [triggers.notionPageCreated()],
101
+ execute: (event, context) => {
102
+ expectTypeOf(event).toEqualTypeOf<NotionPageCreatedEvent>();
103
+ expectTypeOf(event.type).toEqualTypeOf<"notion.page.created">();
104
+ expectTypeOf(context).toEqualTypeOf<CapabilityContext>();
105
+ },
106
+ });
107
+ });
108
+
109
+ it("includes triggers in capability config and the worker manifest", () => {
110
+ const worker = new Worker();
111
+ worker.workflow("pageAdded", {
112
+ title: "Page Added",
113
+ description: "Runs when a page is added",
114
+ triggers: [triggers.notionPageCreated()],
115
+ execute: () => {},
116
+ });
117
+
118
+ expect(worker.manifest.capabilities).toEqual([
119
+ {
120
+ _tag: "workflow",
121
+ key: "pageAdded",
122
+ config: {
123
+ title: "Page Added",
124
+ description: "Runs when a page is added",
125
+ triggers: [{ type: "notion.page.created" }],
126
+ },
127
+ },
128
+ ]);
129
+ });
130
+
131
+ it("executes with the public event and capability context", async () => {
132
+ const execute = vi.fn();
133
+ const capability = createWorkflowCapability("pageAdded", {
134
+ title: "Page Added",
135
+ description: "Runs when a page is added",
136
+ triggers: [triggers.notionPageCreated()],
137
+ execute,
138
+ });
139
+
140
+ await expect(
141
+ capability.handler(pageCreatedEvent, { concreteOutput: true }),
142
+ ).resolves.toEqual({ status: "success" });
143
+ expect(execute).toHaveBeenCalledWith(
144
+ pageCreatedEvent,
145
+ expect.objectContaining({ notion: expect.any(Object) }),
146
+ );
147
+ expect(stdoutSpy).not.toHaveBeenCalled();
148
+ });
149
+
150
+ it("writes the success envelope by default", async () => {
151
+ const capability = createWorkflowCapability("pageAdded", {
152
+ title: "Page Added",
153
+ description: "Runs when a page is added",
154
+ triggers: [triggers.notionPageCreated()],
155
+ execute: () => {},
156
+ });
157
+
158
+ await capability.handler(pageCreatedEvent);
159
+
160
+ expect(stdoutSpy).toHaveBeenCalledWith(
161
+ '\n<__notion_output__>{"_tag":"success","value":{"status":"success"}}</__notion_output__>\n',
162
+ );
163
+ });
164
+
165
+ it("writes and throws an execution error", async () => {
166
+ const capability = createWorkflowCapability("pageAdded", {
167
+ title: "Page Added",
168
+ description: "Runs when a page is added",
169
+ triggers: [triggers.notionPageCreated()],
170
+ execute: () => {
171
+ throw new Error("Something went wrong");
172
+ },
173
+ });
174
+
175
+ await expect(capability.handler(pageCreatedEvent)).rejects.toThrow(
176
+ ExecutionError,
177
+ );
178
+ expect(stdoutSpy).toHaveBeenCalledWith(
179
+ expect.stringContaining(
180
+ '"_tag":"error","error":{"name":"ExecutionError","message":"Error during worker execution: Error: Something went wrong"',
181
+ ),
182
+ );
183
+ });
184
+ });
@@ -0,0 +1,119 @@
1
+ import { ExecutionError } from "../error.js";
2
+ import type {
3
+ WorkflowEventMap,
4
+ WorkflowTrigger,
5
+ } from "../triggers.generated.js";
6
+ import type { HandlerOptions } from "../types.js";
7
+ import type { CapabilityContext } from "./context.js";
8
+ import { createCapabilityContext } from "./context.js";
9
+ import { writeOutput } from "./output.js";
10
+
11
+ export type WorkflowEvent = WorkflowEventMap[keyof WorkflowEventMap];
12
+
13
+ export type WorkflowEventForTrigger<T extends WorkflowTrigger> =
14
+ WorkflowEventMap[T["type"]];
15
+
16
+ export type WorkflowEventForTriggers<T extends readonly WorkflowTrigger[]> =
17
+ WorkflowEventForTrigger<T[number]>;
18
+
19
+ /**
20
+ * Configuration for a workflow capability
21
+ */
22
+ export type WorkflowConfiguration<
23
+ TTriggers extends readonly [WorkflowTrigger, ...WorkflowTrigger[]],
24
+ > = {
25
+ /**
26
+ * A human-readable title for the workflow, shown in the UI when viewing workflows
27
+ */
28
+ title: string;
29
+
30
+ /**
31
+ * A human-readable description of what the workflow does, shown in the UI when viewing workflows
32
+ */
33
+ description: string;
34
+
35
+ /**
36
+ * An array of triggers that can invoke this workflow.
37
+ *
38
+ * Each trigger defines a specific event or condition that causes the workflow to run.
39
+ */
40
+ triggers: TTriggers;
41
+
42
+ execute: (
43
+ event: WorkflowEventForTriggers<TTriggers>,
44
+ context: CapabilityContext,
45
+ ) => Promise<void> | void;
46
+ };
47
+
48
+ export type WorkflowCapability<
49
+ TTriggers extends readonly [
50
+ WorkflowTrigger,
51
+ ...WorkflowTrigger[],
52
+ ] = readonly [WorkflowTrigger, ...WorkflowTrigger[]],
53
+ > = {
54
+ _tag: "workflow";
55
+ key: string;
56
+ config: {
57
+ title: string;
58
+ description: string;
59
+ triggers: TTriggers;
60
+ };
61
+ handler: (
62
+ event: WorkflowEventForTriggers<TTriggers>,
63
+ options?: HandlerOptions,
64
+ ) => Promise<{ status: "success" } | undefined>;
65
+ };
66
+
67
+ /**
68
+ * Creates a workflow capability from configuration.
69
+ *
70
+ * @param key - The unique name for this capability.
71
+ * @param config - The workflow configuration.
72
+ * @returns The capability object.
73
+ */
74
+ export function createWorkflowCapability<
75
+ const TTriggers extends readonly [WorkflowTrigger, ...WorkflowTrigger[]],
76
+ >(
77
+ key: string,
78
+ config: WorkflowConfiguration<TTriggers>,
79
+ ): WorkflowCapability<TTriggers> {
80
+ return {
81
+ _tag: "workflow",
82
+ key,
83
+ config: {
84
+ title: config.title,
85
+ description: config.description,
86
+ triggers: config.triggers,
87
+ },
88
+ async handler(
89
+ event: WorkflowEventForTriggers<TTriggers>,
90
+ options?: HandlerOptions,
91
+ ): Promise<{ status: "success" } | undefined> {
92
+ try {
93
+ const capabilityContext = createCapabilityContext("workflow");
94
+ await config.execute(event, capabilityContext);
95
+
96
+ if (options?.concreteOutput) {
97
+ return { status: "success" };
98
+ }
99
+
100
+ writeOutput({ _tag: "success", value: { status: "success" } });
101
+ } catch (err) {
102
+ const error = new ExecutionError(err);
103
+
104
+ if (!options?.concreteOutput) {
105
+ writeOutput({
106
+ _tag: "error",
107
+ error: {
108
+ name: error.name,
109
+ message: error.message,
110
+ trace: error.stack,
111
+ },
112
+ });
113
+ }
114
+
115
+ throw error;
116
+ }
117
+ },
118
+ };
119
+ }
package/src/index.ts CHANGED
@@ -1,4 +1,10 @@
1
- export { emojiIcon, imageIcon, notionIcon, place } from "./builder.js";
1
+ export {
2
+ emojiIcon,
3
+ imageCover,
4
+ imageIcon,
5
+ notionIcon,
6
+ place,
7
+ } from "./builder.js";
2
8
  export type {
3
9
  AiConnectorArchetype,
4
10
  AiConnectorCapability,
@@ -48,12 +54,21 @@ export type {
48
54
  WebhookEvent,
49
55
  } from "./capabilities/webhook.js";
50
56
  export { WebhookVerificationError } from "./capabilities/webhook.js";
57
+ export type {
58
+ WorkflowCapability,
59
+ WorkflowConfiguration,
60
+ WorkflowEvent,
61
+ WorkflowEventForTrigger,
62
+ WorkflowEventForTriggers,
63
+ } from "./capabilities/workflow.js";
51
64
  export { RateLimitError } from "./error.js";
52
65
  export type { AnyJSONSchema, JSONSchema } from "./json-schema.js";
53
66
  export type { Infer, SchemaBuilder } from "./schema-builder.js";
54
67
  export { getSchema, j } from "./schema-builder.js";
55
68
  export type {
69
+ Cover,
56
70
  Icon,
71
+ ImageCover,
57
72
  ImageIcon,
58
73
  NoticonColor,
59
74
  NoticonName,
@@ -94,6 +94,7 @@ function enumBuilder(
94
94
  const type = typeof values[0] === "string" ? "string" : "number";
95
95
  return makeBuilder({ type, enum: values } as AnyJSONSchema);
96
96
  }
97
+
97
98
  export { enumBuilder as enum };
98
99
 
99
100
  // ---- String format shorthands ----