@notionhq/workers 0.5.0 → 0.6.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 (189) hide show
  1. package/dist/alpha/builder.d.ts +149 -0
  2. package/dist/alpha/builder.d.ts.map +1 -0
  3. package/dist/alpha/builder.js +260 -0
  4. package/dist/alpha/builder.test.d.ts +2 -0
  5. package/dist/alpha/builder.test.d.ts.map +1 -0
  6. package/dist/alpha/cli/build.d.ts +13 -0
  7. package/dist/alpha/cli/build.d.ts.map +1 -0
  8. package/dist/alpha/cli/build.js +36 -0
  9. package/dist/alpha/cli/build.test.d.ts +2 -0
  10. package/dist/alpha/cli/build.test.d.ts.map +1 -0
  11. package/dist/alpha/cli/codegen.d.ts +10 -0
  12. package/dist/alpha/cli/codegen.d.ts.map +1 -0
  13. package/dist/alpha/cli/codegen.js +41 -0
  14. package/dist/alpha/cli/codegen.test.d.ts +2 -0
  15. package/dist/alpha/cli/codegen.test.d.ts.map +1 -0
  16. package/dist/alpha/cli/discover.d.ts +35 -0
  17. package/dist/alpha/cli/discover.d.ts.map +1 -0
  18. package/dist/alpha/cli/discover.js +39 -0
  19. package/dist/alpha/cli/discover.test.d.ts +2 -0
  20. package/dist/alpha/cli/discover.test.d.ts.map +1 -0
  21. package/dist/alpha/cli/emit-manifest.d.ts +33 -0
  22. package/dist/alpha/cli/emit-manifest.d.ts.map +1 -0
  23. package/dist/alpha/cli/emit-manifest.js +266 -0
  24. package/dist/alpha/cli/emit-manifest.test.d.ts +2 -0
  25. package/dist/alpha/cli/emit-manifest.test.d.ts.map +1 -0
  26. package/dist/alpha/cli/index.d.ts +3 -0
  27. package/dist/alpha/cli/index.d.ts.map +1 -0
  28. package/dist/alpha/cli/index.js +36 -0
  29. package/dist/alpha/context.d.ts +8 -0
  30. package/dist/alpha/context.d.ts.map +1 -0
  31. package/dist/alpha/context.js +33 -0
  32. package/dist/alpha/database.d.ts +90 -0
  33. package/dist/alpha/database.d.ts.map +1 -0
  34. package/dist/alpha/database.js +9 -0
  35. package/dist/alpha/error.d.ts +41 -0
  36. package/dist/alpha/error.d.ts.map +1 -0
  37. package/dist/alpha/error.js +25 -0
  38. package/dist/alpha/icon-names.d.ts +6 -0
  39. package/dist/alpha/icon-names.d.ts.map +1 -0
  40. package/dist/alpha/icon-names.js +0 -0
  41. package/dist/alpha/json-schema.d.ts +117 -0
  42. package/dist/alpha/json-schema.d.ts.map +1 -0
  43. package/dist/alpha/json-schema.js +0 -0
  44. package/dist/alpha/manifest.d.ts +44 -0
  45. package/dist/alpha/manifest.d.ts.map +1 -0
  46. package/dist/alpha/manifest.js +0 -0
  47. package/dist/alpha/output.d.ts +7 -0
  48. package/dist/alpha/output.d.ts.map +1 -0
  49. package/dist/alpha/output.js +8 -0
  50. package/dist/alpha/pacer.d.ts +74 -0
  51. package/dist/alpha/pacer.d.ts.map +1 -0
  52. package/dist/alpha/pacer.js +66 -0
  53. package/dist/alpha/pacer.test.d.ts +2 -0
  54. package/dist/alpha/pacer.test.d.ts.map +1 -0
  55. package/dist/alpha/schedule.d.ts +6 -0
  56. package/dist/alpha/schedule.d.ts.map +1 -0
  57. package/dist/alpha/schedule.js +43 -0
  58. package/dist/alpha/schedule.test.d.ts +2 -0
  59. package/dist/alpha/schedule.test.d.ts.map +1 -0
  60. package/dist/alpha/schema-builder.d.ts +79 -0
  61. package/dist/alpha/schema-builder.d.ts.map +1 -0
  62. package/dist/alpha/schema-builder.js +135 -0
  63. package/dist/alpha/schema.d.ts +185 -0
  64. package/dist/alpha/schema.d.ts.map +1 -0
  65. package/dist/alpha/schema.js +115 -0
  66. package/dist/alpha/sync.d.ts +233 -0
  67. package/dist/alpha/sync.d.ts.map +1 -0
  68. package/dist/alpha/sync.js +70 -0
  69. package/dist/alpha/sync.test.d.ts +2 -0
  70. package/dist/alpha/sync.test.d.ts.map +1 -0
  71. package/dist/alpha/tool.d.ts +132 -0
  72. package/dist/alpha/tool.d.ts.map +1 -0
  73. package/dist/alpha/tool.js +156 -0
  74. package/dist/alpha/tool.test.d.ts +2 -0
  75. package/dist/alpha/tool.test.d.ts.map +1 -0
  76. package/dist/alpha/triggers.d.ts +365 -0
  77. package/dist/alpha/triggers.d.ts.map +1 -0
  78. package/dist/alpha/triggers.generated.d.ts +246 -0
  79. package/dist/alpha/triggers.generated.d.ts.map +1 -0
  80. package/dist/alpha/triggers.generated.js +239 -0
  81. package/dist/alpha/triggers.js +0 -0
  82. package/dist/alpha/types.d.ts +228 -0
  83. package/dist/alpha/types.d.ts.map +1 -0
  84. package/dist/alpha/types.js +0 -0
  85. package/dist/alpha/workflow.d.ts +70 -0
  86. package/dist/alpha/workflow.d.ts.map +1 -0
  87. package/dist/alpha/workflow.js +39 -0
  88. package/dist/alpha/workflow.test.d.ts +2 -0
  89. package/dist/alpha/workflow.test.d.ts.map +1 -0
  90. package/dist/builder.d.ts.map +1 -1
  91. package/dist/builder.js +2 -6
  92. package/dist/capabilities/ai_connector.d.ts.map +1 -1
  93. package/dist/capabilities/automation.d.ts.map +1 -1
  94. package/dist/capabilities/context.d.ts.map +1 -1
  95. package/dist/capabilities/custom-block.d.ts +124 -0
  96. package/dist/capabilities/custom-block.d.ts.map +1 -0
  97. package/dist/capabilities/custom-block.js +22 -0
  98. package/dist/capabilities/oauth.d.ts.map +1 -1
  99. package/dist/capabilities/output.d.ts.map +1 -1
  100. package/dist/capabilities/output.js +2 -4
  101. package/dist/capabilities/stateful-capability.d.ts.map +1 -1
  102. package/dist/capabilities/stateful-capability.js +2 -6
  103. package/dist/capabilities/sync.d.ts.map +1 -1
  104. package/dist/capabilities/sync.js +2 -8
  105. package/dist/capabilities/tool.d.ts.map +1 -1
  106. package/dist/capabilities/tool.js +6 -20
  107. package/dist/capabilities/webhook.d.ts.map +1 -1
  108. package/dist/capabilities/workflow.d.ts +2 -2
  109. package/dist/capabilities/workflow.d.ts.map +1 -1
  110. package/dist/index.d.ts +3 -2
  111. package/dist/index.d.ts.map +1 -1
  112. package/dist/index.js +1 -7
  113. package/dist/json-schema.d.ts.map +1 -1
  114. package/dist/schema-builder.d.ts.map +1 -1
  115. package/dist/schema.d.ts.map +1 -1
  116. package/dist/types.d.ts.map +1 -1
  117. package/dist/worker.d.ts +41 -1
  118. package/dist/worker.d.ts.map +1 -1
  119. package/dist/worker.js +51 -4
  120. package/package.json +74 -14
  121. package/src/alpha/ajv-formats.d.ts +7 -0
  122. package/src/alpha/builder.test.ts +197 -0
  123. package/src/alpha/builder.ts +338 -0
  124. package/src/alpha/cli/build.test.ts +240 -0
  125. package/src/alpha/cli/build.ts +57 -0
  126. package/src/alpha/cli/codegen.test.ts +31 -0
  127. package/src/alpha/cli/codegen.ts +51 -0
  128. package/src/alpha/cli/discover.test.ts +43 -0
  129. package/src/alpha/cli/discover.ts +81 -0
  130. package/src/alpha/cli/emit-manifest.test.ts +45 -0
  131. package/src/alpha/cli/emit-manifest.ts +409 -0
  132. package/src/alpha/cli/index.ts +41 -0
  133. package/src/alpha/context.ts +61 -0
  134. package/src/alpha/database.ts +102 -0
  135. package/src/alpha/error.ts +49 -0
  136. package/src/alpha/icon-names.ts +890 -0
  137. package/src/alpha/json-schema.ts +180 -0
  138. package/src/alpha/manifest.ts +47 -0
  139. package/src/alpha/output.ts +8 -0
  140. package/src/alpha/pacer.test.ts +81 -0
  141. package/src/alpha/pacer.ts +181 -0
  142. package/src/alpha/schedule.test.ts +31 -0
  143. package/src/alpha/schedule.ts +53 -0
  144. package/src/alpha/schema-builder.ts +193 -0
  145. package/src/alpha/schema.ts +222 -0
  146. package/src/alpha/sync.test.ts +191 -0
  147. package/src/alpha/sync.ts +340 -0
  148. package/src/alpha/tool.test.ts +116 -0
  149. package/src/alpha/tool.ts +319 -0
  150. package/src/alpha/triggers.generated.ts +489 -0
  151. package/src/alpha/triggers.ts +500 -0
  152. package/src/alpha/types.ts +284 -0
  153. package/src/alpha/workflow.test.ts +84 -0
  154. package/src/alpha/workflow.ts +131 -0
  155. package/src/builder.test.ts +28 -60
  156. package/src/builder.ts +3 -10
  157. package/src/capabilities/ai_connector.test.ts +9 -32
  158. package/src/capabilities/ai_connector.ts +5 -17
  159. package/src/capabilities/automation.test.ts +3 -12
  160. package/src/capabilities/automation.ts +3 -11
  161. package/src/capabilities/context.ts +3 -6
  162. package/src/capabilities/custom-block.ts +181 -0
  163. package/src/capabilities/oauth.test.ts +1 -0
  164. package/src/capabilities/oauth.ts +1 -3
  165. package/src/capabilities/output.ts +1 -3
  166. package/src/capabilities/stateful-capability.test.ts +1 -0
  167. package/src/capabilities/stateful-capability.ts +2 -6
  168. package/src/capabilities/sync.test.ts +15 -45
  169. package/src/capabilities/sync.ts +10 -40
  170. package/src/capabilities/tool.test.ts +106 -146
  171. package/src/capabilities/tool.ts +17 -39
  172. package/src/capabilities/webhook.ts +3 -10
  173. package/src/capabilities/workflow.test.ts +10 -31
  174. package/src/capabilities/workflow.ts +5 -12
  175. package/src/index.ts +9 -12
  176. package/src/json-schema.test.ts +2 -4
  177. package/src/json-schema.ts +7 -6
  178. package/src/pacer.test.ts +1 -3
  179. package/src/pacer_internal.ts +1 -5
  180. package/src/schema-builder.test.ts +10 -35
  181. package/src/schema-builder.ts +5 -16
  182. package/src/schema.ts +2 -10
  183. package/src/types.ts +1 -7
  184. package/src/worker.test.ts +147 -0
  185. package/src/worker.ts +67 -35
  186. package/dist/block.d.ts +0 -321
  187. package/dist/block.d.ts.map +0 -1
  188. package/src/block.ts +0 -529
  189. /package/dist/{block.js → alpha/ajv-formats.d.js} +0 -0
@@ -0,0 +1,284 @@
1
+ import type { NoticonName } from "./icon-names.js";
2
+
3
+ export type { NoticonName } from "./icon-names.js";
4
+
5
+ /**
6
+ * Valid colors for select options.
7
+ */
8
+ export type SelectColor =
9
+ | "default"
10
+ | "gray"
11
+ | "brown"
12
+ | "orange"
13
+ | "yellow"
14
+ | "green"
15
+ | "blue"
16
+ | "purple"
17
+ | "pink"
18
+ | "red";
19
+
20
+ /**
21
+ * A select option with an optional color.
22
+ */
23
+ export interface SelectOption {
24
+ name: string;
25
+ color?: SelectColor;
26
+ }
27
+
28
+ /**
29
+ * Status group types in Notion.
30
+ */
31
+ export type StatusGroupType = "To-do" | "In progress" | "Complete";
32
+
33
+ /**
34
+ * Status group configuration.
35
+ */
36
+ export interface StatusGroup {
37
+ name: StatusGroupType;
38
+ options: SelectOption[];
39
+ }
40
+
41
+ /**
42
+ * A text token is a tuple where the first element is the text content
43
+ * and optional subsequent elements are formatting annotations.
44
+ */
45
+ type TextToken = [string, ...unknown[]];
46
+
47
+ /**
48
+ * A TextValue is an array of text tokens, representing rich text content.
49
+ */
50
+ export type TextValue = Array<TextToken>;
51
+
52
+ /**
53
+ * Date object representing a single date without time.
54
+ */
55
+ export interface NotionDate {
56
+ type: "date";
57
+ start_date: string; // Format: "YYYY-MM-DD"
58
+ }
59
+
60
+ /**
61
+ * Date object representing a date with time.
62
+ */
63
+ export interface NotionDateTime {
64
+ type: "datetime";
65
+ start_date: string; // Format: "YYYY-MM-DD"
66
+ start_time: string; // Format: "HH:mm"
67
+ time_zone?: string; // e.g., "America/Los_Angeles"
68
+ }
69
+
70
+ /**
71
+ * Date object representing a date range.
72
+ */
73
+ export interface DateRange {
74
+ type: "daterange";
75
+ start_date: string; // Format: "YYYY-MM-DD"
76
+ end_date: string; // Format: "YYYY-MM-DD"
77
+ }
78
+
79
+ /**
80
+ * Date object representing a date-time range.
81
+ */
82
+ export interface DateTimeRange {
83
+ type: "datetimerange";
84
+ start_date: string; // Format: "YYYY-MM-DD"
85
+ start_time: string; // Format: "HH:mm"
86
+ end_date: string; // Format: "YYYY-MM-DD"
87
+ end_time: string; // Format: "HH:mm"
88
+ time_zone?: string;
89
+ }
90
+
91
+ /**
92
+ * All possible date value types.
93
+ */
94
+ export type DateValue = NotionDate | NotionDateTime | DateRange | DateTimeRange;
95
+
96
+ /**
97
+ * Person reference - represents a user in a people property.
98
+ */
99
+ export interface PersonReference {
100
+ /**
101
+ * The email address of the user
102
+ */
103
+ email: string;
104
+ }
105
+
106
+ /**
107
+ * A PeopleValue is an array of person references.
108
+ */
109
+ export type PeopleValue = PersonReference[];
110
+
111
+ /**
112
+ * Place value representing a geographic location.
113
+ * Used for place properties in databases.
114
+ */
115
+ export interface PlaceValue {
116
+ /**
117
+ * Latitude coordinate (required)
118
+ */
119
+ lat: number;
120
+ /**
121
+ * Longitude coordinate (required)
122
+ */
123
+ lon: number;
124
+ /**
125
+ * Optional display name for the location
126
+ */
127
+ name?: string;
128
+ /**
129
+ * Optional street address
130
+ */
131
+ address?: string;
132
+ /**
133
+ * Optional Google Place ID for the location
134
+ */
135
+ googlePlaceId?: string;
136
+ }
137
+
138
+ /**
139
+ * A reference to a related record by its primary key.
140
+ */
141
+ export type RelationReference = {
142
+ type: "primaryKey";
143
+ value: string;
144
+ };
145
+
146
+ /**
147
+ * Relation value representing references to related records.
148
+ * Each reference identifies a record in the target database.
149
+ */
150
+ export type RelationValue = RelationReference[];
151
+
152
+ /**
153
+ * Number format types.
154
+ */
155
+ export type NumberFormat =
156
+ | "number"
157
+ | "number_with_commas"
158
+ | "percent"
159
+ | "dollar"
160
+ | "euro"
161
+ | "pound"
162
+ | "yen"
163
+ | "rupee"
164
+ | "won"
165
+ | "yuan";
166
+
167
+ /**
168
+ * Date format types.
169
+ */
170
+ export type DateFormat = "relative" | "MM/DD/YYYY" | "DD/MM/YYYY" | "YYYY/MM/DD" | "ll" | "MMM d";
171
+
172
+ /**
173
+ * Icon representing an emoji.
174
+ */
175
+ export interface EmojiIcon {
176
+ type: "emoji";
177
+ value: string;
178
+ }
179
+
180
+ /**
181
+ * Valid colors for Notion icons.
182
+ */
183
+ export type NoticonColor =
184
+ | "gray"
185
+ | "lightgray"
186
+ | "brown"
187
+ | "yellow"
188
+ | "orange"
189
+ | "green"
190
+ | "blue"
191
+ | "purple"
192
+ | "pink"
193
+ | "red";
194
+
195
+ /**
196
+ * Icon representing a Notion built-in icon.
197
+ */
198
+ export interface NoticonIcon {
199
+ type: "notion";
200
+ /**
201
+ * The name of the Notion icon (e.g., "checkmark", "pizza", "rocket")
202
+ */
203
+ icon: NoticonName;
204
+ /**
205
+ * The color variant of the icon
206
+ */
207
+ color: NoticonColor;
208
+ }
209
+
210
+ /**
211
+ * Icon representing an external image URL.
212
+ */
213
+ export interface ImageIcon {
214
+ type: "image";
215
+ /**
216
+ * The URL of the image (must be a valid http/https URL)
217
+ */
218
+ url: string;
219
+ }
220
+
221
+ /**
222
+ * All possible icon types.
223
+ */
224
+ export type Icon = EmojiIcon | NoticonIcon | ImageIcon;
225
+
226
+ /**
227
+ * Cover image for a Notion page.
228
+ */
229
+ export interface ImageCover {
230
+ type: "image";
231
+ /**
232
+ * The URL of the image (must be a valid http/https URL)
233
+ */
234
+ url: string;
235
+ /**
236
+ * Vertical image position, from 0 (top) to 1 (bottom).
237
+ */
238
+ position?: number;
239
+ }
240
+
241
+ /**
242
+ * All possible cover types.
243
+ */
244
+ export type Cover = ImageCover;
245
+
246
+ /**
247
+ * A unit of time used in interval strings.
248
+ * - "m": minutes
249
+ * - "h": hours
250
+ * - "d": days
251
+ */
252
+ export type TimeUnit = "m" | "h" | "d";
253
+
254
+ /**
255
+ * A string representing an interval, e.g. "30m", "1h", "7d".
256
+ */
257
+ export type IntervalString = `${number}${TimeUnit}`;
258
+
259
+ /**
260
+ * Schedule configuration for sync capabilities.
261
+ * - "continuous": Run as frequently as the system allows
262
+ * - "manual": Only run when explicitly triggered
263
+ * - IntervalString: Run at specified intervals, e.g. "30m", "1h", "1d"
264
+ */
265
+ export type Schedule = "continuous" | "manual" | IntervalString;
266
+
267
+ /**
268
+ * Normalized schedule representation stored in the backend.
269
+ */
270
+ export type NormalizedSchedule =
271
+ | { type: "continuous" }
272
+ | { type: "manual" }
273
+ | { type: "interval"; intervalMs: number };
274
+
275
+ /**
276
+ * Options for invoking a capability handler.
277
+ */
278
+ export type HandlerOptions = {
279
+ /**
280
+ * Return the concrete result instead of writing the notion_output
281
+ * envelope to stdout. Used by tests.
282
+ */
283
+ concreteOutput?: true;
284
+ };
@@ -0,0 +1,84 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi, type Mock } from "vitest";
2
+
3
+ import { ExecutionError } from "./error.js";
4
+ import { triggers, type NotionPageCreatedEvent } from "./triggers.generated.js";
5
+ import { createWorkflow } from "./workflow.js";
6
+
7
+ let stdoutSpy: Mock<typeof process.stdout.write>;
8
+
9
+ beforeEach(() => {
10
+ stdoutSpy = vi.spyOn(process.stdout, "write").mockImplementation(() => true);
11
+ });
12
+
13
+ afterEach(() => {
14
+ stdoutSpy.mockRestore();
15
+ });
16
+
17
+ const event = { type: "notion.page.created" } as NotionPageCreatedEvent;
18
+
19
+ describe("createWorkflow", () => {
20
+ it("is tagged and exposes triggers in config", () => {
21
+ const workflow = createWorkflow({
22
+ title: "Log Pages",
23
+ description: "Logs created pages",
24
+ triggers: [triggers.notionPageCreated()],
25
+ handler: () => {},
26
+ });
27
+
28
+ expect(workflow._tag).toBe("workflow");
29
+ expect(workflow.config).toEqual({
30
+ title: "Log Pages",
31
+ description: "Logs created pages",
32
+ triggers: [{ type: "notion.page.created" }],
33
+ });
34
+ });
35
+
36
+ it("writes a success envelope after the handler runs", async () => {
37
+ const handler = vi.fn();
38
+ const workflow = createWorkflow({
39
+ title: "Log Pages",
40
+ description: "Logs created pages",
41
+ triggers: [triggers.notionPageCreated()],
42
+ handler,
43
+ });
44
+
45
+ await workflow.handler(event);
46
+
47
+ expect(handler).toHaveBeenCalledWith(event, expect.anything());
48
+ expect(stdoutSpy).toHaveBeenCalledWith(
49
+ `\n<__notion_output__>{"_tag":"success","value":{"status":"success"}}</__notion_output__>\n`,
50
+ );
51
+ });
52
+
53
+ it("returns the status without an envelope under concreteOutput", async () => {
54
+ const workflow = createWorkflow({
55
+ title: "Log Pages",
56
+ description: "Logs created pages",
57
+ triggers: [triggers.notionPageCreated()],
58
+ handler: () => {},
59
+ });
60
+
61
+ await expect(workflow.handler(event, { concreteOutput: true })).resolves.toEqual({
62
+ status: "success",
63
+ });
64
+ expect(stdoutSpy).not.toHaveBeenCalled();
65
+ });
66
+
67
+ it("writes an error envelope and throws ExecutionError on failure", async () => {
68
+ const workflow = createWorkflow({
69
+ title: "Log Pages",
70
+ description: "Logs created pages",
71
+ triggers: [triggers.notionPageCreated()],
72
+ handler: () => {
73
+ throw new Error("boom");
74
+ },
75
+ });
76
+
77
+ await expect(workflow.handler(event)).rejects.toBeInstanceOf(ExecutionError);
78
+ expect(stdoutSpy).toHaveBeenCalledWith(
79
+ expect.stringContaining(
80
+ `<__notion_output__>{"_tag":"error","error":{"name":"ExecutionError","message":"Error during worker execution: Error: boom"`,
81
+ ),
82
+ );
83
+ });
84
+ });
@@ -0,0 +1,131 @@
1
+ import type { CapabilityContext } from "./context.js";
2
+ import { createCapabilityContext } from "./context.js";
3
+ import { ExecutionError } from "./error.js";
4
+ import { writeOutput } from "./output.js";
5
+ import type { WorkflowEventMap, WorkflowTrigger } from "./triggers.generated.js";
6
+ import type { HandlerOptions } from "./types.js";
7
+
8
+ export type WorkflowEvent = WorkflowEventMap[keyof WorkflowEventMap];
9
+
10
+ export type WorkflowEventForTrigger<T extends WorkflowTrigger> = WorkflowEventMap[T["type"]];
11
+
12
+ export type WorkflowEventForTriggers<T extends readonly WorkflowTrigger[]> =
13
+ WorkflowEventForTrigger<T[number]>;
14
+
15
+ /**
16
+ * Configuration passed to {@link createWorkflow}.
17
+ */
18
+ export type WorkflowConfiguration<
19
+ TTriggers extends readonly [WorkflowTrigger, ...WorkflowTrigger[]],
20
+ > = {
21
+ /**
22
+ * A human-readable title for the workflow, shown in the UI when viewing workflows
23
+ */
24
+ title: string;
25
+
26
+ /**
27
+ * A human-readable description of what the workflow does, shown in the UI when viewing workflows
28
+ */
29
+ description: string;
30
+
31
+ /**
32
+ * An array of triggers that can invoke this workflow.
33
+ *
34
+ * Each trigger defines a specific event or condition that causes the workflow to run.
35
+ */
36
+ triggers: TTriggers;
37
+
38
+ handler: (
39
+ event: WorkflowEventForTriggers<TTriggers>,
40
+ context: CapabilityContext,
41
+ ) => Promise<void> | void;
42
+ };
43
+
44
+ /**
45
+ * A workflow capability as returned by {@link createWorkflow}.
46
+ *
47
+ * Note that a workflow carries no key of its own — the build tool derives
48
+ * the capability key from the file the workflow is default-exported from.
49
+ */
50
+ export type Workflow<
51
+ TTriggers extends readonly [WorkflowTrigger, ...WorkflowTrigger[]] = readonly [
52
+ WorkflowTrigger,
53
+ ...WorkflowTrigger[],
54
+ ],
55
+ > = {
56
+ _tag: "workflow";
57
+ config: {
58
+ title: string;
59
+ description: string;
60
+ triggers: TTriggers;
61
+ };
62
+ handler: (
63
+ event: WorkflowEventForTriggers<TTriggers>,
64
+ options?: HandlerOptions,
65
+ ) => Promise<{ status: "success" } | undefined>;
66
+ };
67
+
68
+ /**
69
+ * Create a workflow capability.
70
+ *
71
+ * Default-export the result from a file directly under `src/workflows/` —
72
+ * the file's basename becomes the workflow's key.
73
+ *
74
+ * @example
75
+ * ```ts
76
+ * // src/workflows/onPageCreated.ts
77
+ * import { triggers } from "@notionhq/workers/alpha/triggers";
78
+ * import { createWorkflow } from "@notionhq/workers/alpha/workflow";
79
+ *
80
+ * export default createWorkflow({
81
+ * title: "Send Welcome Email",
82
+ * description: "Sends a welcome email when a new page is added",
83
+ * triggers: [triggers.notionPageCreated()],
84
+ * handler: async (event) => {
85
+ * console.log(event.page);
86
+ * },
87
+ * });
88
+ * ```
89
+ */
90
+ export function createWorkflow<
91
+ const TTriggers extends readonly [WorkflowTrigger, ...WorkflowTrigger[]],
92
+ >(configuration: WorkflowConfiguration<TTriggers>): Workflow<TTriggers> {
93
+ return {
94
+ _tag: "workflow",
95
+ config: {
96
+ title: configuration.title,
97
+ description: configuration.description,
98
+ triggers: configuration.triggers,
99
+ },
100
+ async handler(
101
+ event: WorkflowEventForTriggers<TTriggers>,
102
+ options?: HandlerOptions,
103
+ ): Promise<{ status: "success" } | undefined> {
104
+ try {
105
+ const capabilityContext = createCapabilityContext("workflow");
106
+ await configuration.handler(event, capabilityContext);
107
+
108
+ if (options?.concreteOutput) {
109
+ return { status: "success" };
110
+ }
111
+
112
+ writeOutput({ _tag: "success", value: { status: "success" } });
113
+ } catch (err) {
114
+ const error = new ExecutionError(err);
115
+
116
+ if (!options?.concreteOutput) {
117
+ writeOutput({
118
+ _tag: "error",
119
+ error: {
120
+ name: error.name,
121
+ message: error.message,
122
+ trace: error.stack,
123
+ },
124
+ });
125
+ }
126
+
127
+ throw error;
128
+ }
129
+ },
130
+ };
131
+ }
@@ -1,13 +1,12 @@
1
1
  import { describe, expect, it } from "vitest";
2
+
2
3
  import * as Builder from "./builder.js";
3
4
 
4
5
  /**
5
6
  * Helper to extract the DateValue from a date token.
6
7
  * Date tokens have the shape [["\u2023", [["d", dateValue]]]].
7
8
  */
8
- // biome-ignore lint/suspicious/noExplicitAny: test helper
9
9
  function extractDateValue(token: unknown): any {
10
- // biome-ignore lint/suspicious/noExplicitAny: test helper
11
10
  const arr = token as any;
12
11
  return arr[0][1][0][1];
13
12
  }
@@ -59,9 +58,7 @@ describe("Builder.dateTime", () => {
59
58
  });
60
59
 
61
60
  it("parses ISO 8601 with positive timezone offset", () => {
62
- expect(
63
- extractDateValue(Builder.dateTime("2024-01-15T10:30:00+05:30")),
64
- ).toEqual({
61
+ expect(extractDateValue(Builder.dateTime("2024-01-15T10:30:00+05:30"))).toEqual({
65
62
  type: "datetime",
66
63
  start_date: "2024-01-15",
67
64
  start_time: "10:30",
@@ -69,9 +66,7 @@ describe("Builder.dateTime", () => {
69
66
  });
70
67
 
71
68
  it("parses ISO 8601 with negative timezone offset", () => {
72
- expect(
73
- extractDateValue(Builder.dateTime("2024-01-15T10:30:00-08:00")),
74
- ).toEqual({
69
+ expect(extractDateValue(Builder.dateTime("2024-01-15T10:30:00-08:00"))).toEqual({
75
70
  type: "datetime",
76
71
  start_date: "2024-01-15",
77
72
  start_time: "10:30",
@@ -79,9 +74,7 @@ describe("Builder.dateTime", () => {
79
74
  });
80
75
 
81
76
  it("parses ISO 8601 with milliseconds", () => {
82
- expect(
83
- extractDateValue(Builder.dateTime("2024-01-15T10:30:00.000Z")),
84
- ).toEqual({
77
+ expect(extractDateValue(Builder.dateTime("2024-01-15T10:30:00.000Z"))).toEqual({
85
78
  type: "datetime",
86
79
  start_date: "2024-01-15",
87
80
  start_time: "10:30",
@@ -89,11 +82,7 @@ describe("Builder.dateTime", () => {
89
82
  });
90
83
 
91
84
  it("includes timeZone when provided", () => {
92
- expect(
93
- extractDateValue(
94
- Builder.dateTime("2024-01-15T10:30", "America/New_York"),
95
- ),
96
- ).toEqual({
85
+ expect(extractDateValue(Builder.dateTime("2024-01-15T10:30", "America/New_York"))).toEqual({
97
86
  type: "datetime",
98
87
  start_date: "2024-01-15",
99
88
  start_time: "10:30",
@@ -118,15 +107,11 @@ describe("Builder.dateTime", () => {
118
107
  });
119
108
 
120
109
  it("rejects date-only strings", () => {
121
- expect(() => Builder.dateTime("2024-01-15")).toThrow(
122
- "Invalid ISO 8601 datetime",
123
- );
110
+ expect(() => Builder.dateTime("2024-01-15")).toThrow("Invalid ISO 8601 datetime");
124
111
  });
125
112
 
126
113
  it("rejects plain text", () => {
127
- expect(() => Builder.dateTime("not a date")).toThrow(
128
- "Invalid ISO 8601 datetime",
129
- );
114
+ expect(() => Builder.dateTime("not a date")).toThrow("Invalid ISO 8601 datetime");
130
115
  });
131
116
 
132
117
  it("rejects empty string", () => {
@@ -134,17 +119,13 @@ describe("Builder.dateTime", () => {
134
119
  });
135
120
 
136
121
  it("rejects date with space instead of T", () => {
137
- expect(() => Builder.dateTime("2024-01-15 10:30")).toThrow(
138
- "Invalid ISO 8601 datetime",
139
- );
122
+ expect(() => Builder.dateTime("2024-01-15 10:30")).toThrow("Invalid ISO 8601 datetime");
140
123
  });
141
124
  });
142
125
 
143
126
  describe("Builder.dateRange", () => {
144
127
  it("creates a date range token", () => {
145
- expect(
146
- extractDateValue(Builder.dateRange("2024-01-15", "2024-01-20")),
147
- ).toEqual({
128
+ expect(extractDateValue(Builder.dateRange("2024-01-15", "2024-01-20"))).toEqual({
148
129
  type: "daterange",
149
130
  start_date: "2024-01-15",
150
131
  end_date: "2024-01-20",
@@ -152,40 +133,31 @@ describe("Builder.dateRange", () => {
152
133
  });
153
134
 
154
135
  it("rejects invalid start date", () => {
155
- expect(() => Builder.dateRange("bad", "2024-01-20")).toThrow(
156
- "Expected YYYY-MM-DD",
157
- );
136
+ expect(() => Builder.dateRange("bad", "2024-01-20")).toThrow("Expected YYYY-MM-DD");
158
137
  });
159
138
 
160
139
  it("rejects invalid end date", () => {
161
- expect(() => Builder.dateRange("2024-01-15", "bad")).toThrow(
162
- "Expected YYYY-MM-DD",
163
- );
140
+ expect(() => Builder.dateRange("2024-01-15", "bad")).toThrow("Expected YYYY-MM-DD");
164
141
  });
165
142
  });
166
143
 
167
144
  describe("Builder.dateTimeRange", () => {
168
145
  it("creates a datetime range token from ISO strings", () => {
169
- expect(
170
- extractDateValue(
171
- Builder.dateTimeRange("2024-01-15T10:30", "2024-01-16T14:00"),
172
- ),
173
- ).toEqual({
174
- type: "datetimerange",
175
- start_date: "2024-01-15",
176
- start_time: "10:30",
177
- end_date: "2024-01-16",
178
- end_time: "14:00",
179
- });
146
+ expect(extractDateValue(Builder.dateTimeRange("2024-01-15T10:30", "2024-01-16T14:00"))).toEqual(
147
+ {
148
+ type: "datetimerange",
149
+ start_date: "2024-01-15",
150
+ start_time: "10:30",
151
+ end_date: "2024-01-16",
152
+ end_time: "14:00",
153
+ },
154
+ );
180
155
  });
181
156
 
182
157
  it("parses mixed ISO formats", () => {
183
158
  expect(
184
159
  extractDateValue(
185
- Builder.dateTimeRange(
186
- "2024-01-15T10:30:00Z",
187
- "2024-01-16T14:00:00.000+05:00",
188
- ),
160
+ Builder.dateTimeRange("2024-01-15T10:30:00Z", "2024-01-16T14:00:00.000+05:00"),
189
161
  ),
190
162
  ).toEqual({
191
163
  type: "datetimerange",
@@ -199,11 +171,7 @@ describe("Builder.dateTimeRange", () => {
199
171
  it("includes timeZone when provided", () => {
200
172
  expect(
201
173
  extractDateValue(
202
- Builder.dateTimeRange(
203
- "2024-01-15T10:30",
204
- "2024-01-16T14:00",
205
- "Europe/London",
206
- ),
174
+ Builder.dateTimeRange("2024-01-15T10:30", "2024-01-16T14:00", "Europe/London"),
207
175
  ),
208
176
  ).toEqual({
209
177
  type: "datetimerange",
@@ -216,14 +184,14 @@ describe("Builder.dateTimeRange", () => {
216
184
  });
217
185
 
218
186
  it("rejects invalid start datetime", () => {
219
- expect(() =>
220
- Builder.dateTimeRange("2024-01-15", "2024-01-16T14:00"),
221
- ).toThrow("Invalid ISO 8601 datetime");
187
+ expect(() => Builder.dateTimeRange("2024-01-15", "2024-01-16T14:00")).toThrow(
188
+ "Invalid ISO 8601 datetime",
189
+ );
222
190
  });
223
191
 
224
192
  it("rejects invalid end datetime", () => {
225
- expect(() =>
226
- Builder.dateTimeRange("2024-01-15T10:30", "2024-01-16"),
227
- ).toThrow("Invalid ISO 8601 datetime");
193
+ expect(() => Builder.dateTimeRange("2024-01-15T10:30", "2024-01-16")).toThrow(
194
+ "Invalid ISO 8601 datetime",
195
+ );
228
196
  });
229
197
  });