@notionhq/workers 0.4.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 (204) 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 +7 -1
  91. package/dist/builder.d.ts.map +1 -1
  92. package/dist/builder.js +12 -3
  93. package/dist/capabilities/ai_connector.d.ts.map +1 -1
  94. package/dist/capabilities/automation.d.ts.map +1 -1
  95. package/dist/capabilities/context.d.ts.map +1 -1
  96. package/dist/capabilities/context.js +1 -1
  97. package/dist/capabilities/custom-block.d.ts +124 -0
  98. package/dist/capabilities/custom-block.d.ts.map +1 -0
  99. package/dist/capabilities/custom-block.js +22 -0
  100. package/dist/capabilities/oauth.d.ts.map +1 -1
  101. package/dist/capabilities/output.d.ts.map +1 -1
  102. package/dist/capabilities/output.js +2 -4
  103. package/dist/capabilities/stateful-capability.d.ts.map +1 -1
  104. package/dist/capabilities/stateful-capability.js +3 -7
  105. package/dist/capabilities/sync.d.ts +11 -1
  106. package/dist/capabilities/sync.d.ts.map +1 -1
  107. package/dist/capabilities/sync.js +2 -8
  108. package/dist/capabilities/tool.d.ts.map +1 -1
  109. package/dist/capabilities/tool.js +6 -20
  110. package/dist/capabilities/webhook.d.ts.map +1 -1
  111. package/dist/capabilities/workflow.d.ts +50 -0
  112. package/dist/capabilities/workflow.d.ts.map +1 -0
  113. package/dist/capabilities/workflow.js +40 -0
  114. package/dist/capabilities/workflow.test.d.ts +2 -0
  115. package/dist/capabilities/workflow.test.d.ts.map +1 -0
  116. package/dist/index.d.ts +5 -3
  117. package/dist/index.d.ts.map +1 -1
  118. package/dist/index.js +2 -1
  119. package/dist/json-schema.d.ts.map +1 -1
  120. package/dist/schema-builder.d.ts.map +1 -1
  121. package/dist/schema.d.ts.map +1 -1
  122. package/dist/triggers.d.ts +365 -0
  123. package/dist/triggers.d.ts.map +1 -0
  124. package/dist/triggers.generated.d.ts +246 -0
  125. package/dist/triggers.generated.d.ts.map +1 -0
  126. package/dist/triggers.generated.js +239 -0
  127. package/dist/triggers.js +0 -0
  128. package/dist/types.d.ts +18 -0
  129. package/dist/types.d.ts.map +1 -1
  130. package/dist/worker.d.ts +72 -2
  131. package/dist/worker.d.ts.map +1 -1
  132. package/dist/worker.js +85 -4
  133. package/package.json +81 -14
  134. package/src/alpha/ajv-formats.d.ts +7 -0
  135. package/src/alpha/builder.test.ts +197 -0
  136. package/src/alpha/builder.ts +338 -0
  137. package/src/alpha/cli/build.test.ts +240 -0
  138. package/src/alpha/cli/build.ts +57 -0
  139. package/src/alpha/cli/codegen.test.ts +31 -0
  140. package/src/alpha/cli/codegen.ts +51 -0
  141. package/src/alpha/cli/discover.test.ts +43 -0
  142. package/src/alpha/cli/discover.ts +81 -0
  143. package/src/alpha/cli/emit-manifest.test.ts +45 -0
  144. package/src/alpha/cli/emit-manifest.ts +409 -0
  145. package/src/alpha/cli/index.ts +41 -0
  146. package/src/alpha/context.ts +61 -0
  147. package/src/alpha/database.ts +102 -0
  148. package/src/alpha/error.ts +49 -0
  149. package/src/alpha/icon-names.ts +890 -0
  150. package/src/alpha/json-schema.ts +180 -0
  151. package/src/alpha/manifest.ts +47 -0
  152. package/src/alpha/output.ts +8 -0
  153. package/src/alpha/pacer.test.ts +81 -0
  154. package/src/alpha/pacer.ts +181 -0
  155. package/src/alpha/schedule.test.ts +31 -0
  156. package/src/alpha/schedule.ts +53 -0
  157. package/src/alpha/schema-builder.ts +193 -0
  158. package/src/alpha/schema.ts +222 -0
  159. package/src/alpha/sync.test.ts +191 -0
  160. package/src/alpha/sync.ts +340 -0
  161. package/src/alpha/tool.test.ts +116 -0
  162. package/src/alpha/tool.ts +319 -0
  163. package/src/alpha/triggers.generated.ts +489 -0
  164. package/src/alpha/triggers.ts +500 -0
  165. package/src/alpha/types.ts +284 -0
  166. package/src/alpha/workflow.test.ts +84 -0
  167. package/src/alpha/workflow.ts +131 -0
  168. package/src/builder.test.ts +28 -60
  169. package/src/builder.ts +20 -7
  170. package/src/capabilities/ai_connector.test.ts +9 -32
  171. package/src/capabilities/ai_connector.ts +5 -17
  172. package/src/capabilities/automation.test.ts +3 -12
  173. package/src/capabilities/automation.ts +3 -11
  174. package/src/capabilities/context.ts +5 -8
  175. package/src/capabilities/custom-block.ts +181 -0
  176. package/src/capabilities/oauth.test.ts +1 -0
  177. package/src/capabilities/oauth.ts +1 -3
  178. package/src/capabilities/output.ts +1 -3
  179. package/src/capabilities/stateful-capability.test.ts +1 -0
  180. package/src/capabilities/stateful-capability.ts +3 -7
  181. package/src/capabilities/sync.test.ts +76 -42
  182. package/src/capabilities/sync.ts +16 -40
  183. package/src/capabilities/tool.test.ts +106 -146
  184. package/src/capabilities/tool.ts +17 -39
  185. package/src/capabilities/webhook.ts +3 -10
  186. package/src/capabilities/workflow.test.ts +163 -0
  187. package/src/capabilities/workflow.ts +112 -0
  188. package/src/index.ts +18 -6
  189. package/src/json-schema.test.ts +2 -4
  190. package/src/json-schema.ts +7 -6
  191. package/src/pacer.test.ts +1 -3
  192. package/src/pacer_internal.ts +1 -5
  193. package/src/schema-builder.test.ts +10 -35
  194. package/src/schema-builder.ts +6 -16
  195. package/src/schema.ts +2 -10
  196. package/src/triggers.generated.ts +489 -0
  197. package/src/triggers.ts +504 -0
  198. package/src/types.ts +21 -7
  199. package/src/worker.test.ts +147 -0
  200. package/src/worker.ts +155 -32
  201. package/dist/block.d.ts +0 -321
  202. package/dist/block.d.ts.map +0 -1
  203. package/src/block.ts +0 -529
  204. /package/dist/{block.js → alpha/ajv-formats.d.js} +0 -0
@@ -1,5 +1,6 @@
1
1
  import { Ajv } from "ajv";
2
2
  import addFormats from "ajv-formats";
3
+
3
4
  import type { AnyJSONSchema } from "../json-schema.js";
4
5
  import type { SchemaBuilder } from "../schema-builder.js";
5
6
  import { getSchema } from "../schema-builder.js";
@@ -15,10 +16,7 @@ import { writeOutput } from "./output.js";
15
16
  * Throws a descriptive error at tool registration time (not request time)
16
17
  * so the developer sees it immediately.
17
18
  */
18
- function validateRequiredProperties(
19
- schema: AnyJSONSchema,
20
- path: string = "",
21
- ): void {
19
+ function validateRequiredProperties(schema: AnyJSONSchema, path: string = ""): void {
22
20
  if ("type" in schema && schema.type === "object" && "properties" in schema) {
23
21
  const propKeys = Object.keys(schema.properties).sort();
24
22
  const required = [...(schema.required ?? [])].sort();
@@ -43,20 +41,14 @@ function validateRequiredProperties(
43
41
  // Recurse into $defs
44
42
  if ("$defs" in schema && schema.$defs) {
45
43
  for (const [key, defSchema] of Object.entries(schema.$defs)) {
46
- validateRequiredProperties(
47
- defSchema,
48
- path ? `${path}.$defs.${key}` : `$defs.${key}`,
49
- );
44
+ validateRequiredProperties(defSchema, path ? `${path}.$defs.${key}` : `$defs.${key}`);
50
45
  }
51
46
  }
52
47
  }
53
48
 
54
49
  // Recurse into array items
55
50
  if ("type" in schema && schema.type === "array" && "items" in schema) {
56
- validateRequiredProperties(
57
- schema.items as AnyJSONSchema,
58
- path ? `${path}.items` : "items",
59
- );
51
+ validateRequiredProperties(schema.items as AnyJSONSchema, path ? `${path}.items` : "items");
60
52
  }
61
53
 
62
54
  // Recurse into anyOf branches
@@ -90,10 +82,7 @@ export interface ToolHints {
90
82
  readOnlyHint?: boolean;
91
83
  }
92
84
 
93
- export interface ToolConfiguration<
94
- I extends JSONValue,
95
- O extends JSONValue = JSONValue,
96
- > {
85
+ export interface ToolConfiguration<I extends JSONValue, O extends JSONValue = JSONValue> {
97
86
  title: string;
98
87
  description: string;
99
88
  schema: SchemaBuilder<I>;
@@ -156,10 +145,9 @@ export class ToolExecutionError extends Error {
156
145
  }
157
146
  }
158
147
 
159
- export type ToolCapability<
160
- I extends JSONValue,
161
- O extends JSONValue = JSONValue,
162
- > = ReturnType<typeof createToolCapability<I, O>>;
148
+ export type ToolCapability<I extends JSONValue, O extends JSONValue = JSONValue> = ReturnType<
149
+ typeof createToolCapability<I, O>
150
+ >;
163
151
 
164
152
  /**
165
153
  * Creates a capability definition for a tool to be used by an agent.
@@ -167,16 +155,14 @@ export type ToolCapability<
167
155
  * @param config - The configuration for the tool.
168
156
  * @returns A capability definition for the tool.
169
157
  */
170
- export function createToolCapability<
171
- I extends JSONValue,
172
- O extends JSONValue = JSONValue,
173
- >(key: string, config: ToolConfiguration<I, O>) {
158
+ export function createToolCapability<I extends JSONValue, O extends JSONValue = JSONValue>(
159
+ key: string,
160
+ config: ToolConfiguration<I, O>,
161
+ ) {
174
162
  const inputSchema = getSchema(config.schema);
175
163
  validateRequiredProperties(inputSchema as AnyJSONSchema);
176
164
 
177
- const outputSchema = config.outputSchema
178
- ? getSchema(config.outputSchema)
179
- : undefined;
165
+ const outputSchema = config.outputSchema ? getSchema(config.outputSchema) : undefined;
180
166
 
181
167
  if (outputSchema) {
182
168
  validateRequiredProperties(outputSchema as AnyJSONSchema);
@@ -215,14 +201,10 @@ export function createToolCapability<
215
201
  if (!validateInput(input)) {
216
202
  // ajv resets the "errors" property on each validation call.
217
203
  if (validateInput.errors == null) {
218
- throw new Error(
219
- "Unexpected: No validation errors after failed validation",
220
- );
204
+ throw new Error("Unexpected: No validation errors after failed validation");
221
205
  }
222
206
 
223
- const error = new InvalidToolInputError(
224
- JSON.stringify(validateInput.errors, null, 2),
225
- );
207
+ const error = new InvalidToolInputError(JSON.stringify(validateInput.errors, null, 2));
226
208
 
227
209
  if (options?.concreteOutput) {
228
210
  throw error;
@@ -242,9 +224,7 @@ export function createToolCapability<
242
224
  const capabilityContext = createCapabilityContext("tool");
243
225
  const result = await config.execute(input, capabilityContext);
244
226
  if (validateOutput && !validateOutput(result)) {
245
- const error = new InvalidToolOutputError(
246
- JSON.stringify(validateOutput.errors, null, 2),
247
- );
227
+ const error = new InvalidToolOutputError(JSON.stringify(validateOutput.errors, null, 2));
248
228
 
249
229
  if (options?.concreteOutput) {
250
230
  throw error;
@@ -275,9 +255,7 @@ export function createToolCapability<
275
255
  value: result,
276
256
  };
277
257
  } catch (err) {
278
- const error = new ToolExecutionError(
279
- err instanceof Error ? err.message : String(err),
280
- );
258
+ const error = new ToolExecutionError(err instanceof Error ? err.message : String(err));
281
259
 
282
260
  if (options?.concreteOutput) {
283
261
  throw error;
@@ -94,18 +94,12 @@ export interface WebhookConfiguration {
94
94
  * @param context - The capability execution context (Notion client, etc.)
95
95
  * @returns A promise that resolves when the webhook processing completes
96
96
  */
97
- execute: (
98
- events: WebhookEvent[],
99
- context: CapabilityContext,
100
- ) => Promise<void> | void;
97
+ execute: (events: WebhookEvent[], context: CapabilityContext) => Promise<void> | void;
101
98
  }
102
99
 
103
100
  export type WebhookCapability = ReturnType<typeof createWebhookCapability>;
104
101
 
105
- export function createWebhookCapability(
106
- key: string,
107
- config: WebhookConfiguration,
108
- ) {
102
+ export function createWebhookCapability(key: string, config: WebhookConfiguration) {
109
103
  return {
110
104
  _tag: "webhook" as const,
111
105
  key,
@@ -127,8 +121,7 @@ export function createWebhookCapability(
127
121
  writeOutput({ _tag: "success", value: { status: "success" } });
128
122
  }
129
123
  } catch (err) {
130
- const error =
131
- err instanceof ExecutionError ? err : new ExecutionError(err);
124
+ const error = err instanceof ExecutionError ? err : new ExecutionError(err);
132
125
 
133
126
  if (!options?.concreteOutput) {
134
127
  writeOutput({
@@ -0,0 +1,163 @@
1
+ import { afterEach, beforeEach, describe, expect, expectTypeOf, it, type Mock, vi } from "vitest";
2
+
3
+ import { ExecutionError } from "../error.js";
4
+ import { type CapabilityContext, Worker, type WorkflowConfiguration } from "../index.js";
5
+ import {
6
+ type NotionPageCreatedEvent,
7
+ type NotionPageCreatedTrigger,
8
+ triggers,
9
+ } from "../triggers.generated.js";
10
+ import { createWorkflowCapability } from "./workflow.js";
11
+
12
+ const pageCreatedEvent: NotionPageCreatedEvent = {
13
+ type: "notion.page.created",
14
+ vendor: "notion",
15
+ message: "Page created",
16
+ actor: {
17
+ id: "user-1",
18
+ displayName: "Ada Lovelace",
19
+ email: "ada@example.com",
20
+ vendorActor: {
21
+ vendor: "notion",
22
+ id: "user-1",
23
+ displayName: "Ada Lovelace",
24
+ handle: null,
25
+ type: "user",
26
+ url: null,
27
+ },
28
+ },
29
+ url: "https://www.notion.so/page-1",
30
+ timestamp: "2026-06-26T12:00:00.000Z",
31
+ page: {
32
+ id: "page-1",
33
+ Name: "Welcome",
34
+ Status: "New",
35
+ },
36
+ content: "# Welcome\n\nHello, world!",
37
+ edits: [
38
+ {
39
+ before: null,
40
+ after: {
41
+ id: "page-1",
42
+ Name: "Welcome",
43
+ Status: "New",
44
+ },
45
+ afterContent: "# Welcome\n\nHello, world!",
46
+ contentChanged: true,
47
+ timestamp: 1_782_477_600_000,
48
+ editedBy: null,
49
+ },
50
+ ],
51
+ };
52
+
53
+ describe("createWorkflowCapability", () => {
54
+ let stdoutSpy: Mock<typeof process.stdout.write>;
55
+
56
+ beforeEach(() => {
57
+ stdoutSpy = vi.spyOn(process.stdout, "write").mockImplementation(() => true);
58
+ });
59
+
60
+ afterEach(() => {
61
+ vi.restoreAllMocks();
62
+ });
63
+
64
+ it("declares a page-created trigger without a database", () => {
65
+ expect(triggers.notionPageCreated()).toEqual({
66
+ type: "notion.page.created",
67
+ });
68
+ });
69
+
70
+ it("requires trigger declarations in workflow configuration", () => {
71
+ type PageAddedConfiguration = WorkflowConfiguration<readonly [NotionPageCreatedTrigger]>;
72
+ type ConfigurationWithoutTriggers = Omit<PageAddedConfiguration, "triggers">;
73
+
74
+ expectTypeOf<ConfigurationWithoutTriggers>().not.toMatchTypeOf<PageAddedConfiguration>();
75
+ });
76
+
77
+ it("infers the canonical page-created event and capability context", () => {
78
+ createWorkflowCapability("pageAdded", {
79
+ title: "Page Added",
80
+ description: "Runs when a page is added",
81
+ triggers: [triggers.notionPageCreated()],
82
+ execute: (event, context) => {
83
+ expectTypeOf(event).toEqualTypeOf<NotionPageCreatedEvent>();
84
+ expectTypeOf(event.type).toEqualTypeOf<"notion.page.created">();
85
+ expectTypeOf(context).toEqualTypeOf<CapabilityContext>();
86
+ },
87
+ });
88
+ });
89
+
90
+ it("includes triggers in capability config and the worker manifest", () => {
91
+ const worker = new Worker();
92
+ worker.workflow("pageAdded", {
93
+ title: "Page Added",
94
+ description: "Runs when a page is added",
95
+ triggers: [triggers.notionPageCreated()],
96
+ execute: () => {},
97
+ });
98
+
99
+ expect(worker.manifest.capabilities).toEqual([
100
+ {
101
+ _tag: "workflow",
102
+ key: "pageAdded",
103
+ config: {
104
+ title: "Page Added",
105
+ description: "Runs when a page is added",
106
+ triggers: [{ type: "notion.page.created" }],
107
+ },
108
+ },
109
+ ]);
110
+ });
111
+
112
+ it("executes with the public event and capability context", async () => {
113
+ const execute = vi.fn();
114
+ const capability = createWorkflowCapability("pageAdded", {
115
+ title: "Page Added",
116
+ description: "Runs when a page is added",
117
+ triggers: [triggers.notionPageCreated()],
118
+ execute,
119
+ });
120
+
121
+ await expect(capability.handler(pageCreatedEvent, { concreteOutput: true })).resolves.toEqual({
122
+ status: "success",
123
+ });
124
+ expect(execute).toHaveBeenCalledWith(
125
+ pageCreatedEvent,
126
+ expect.objectContaining({ notion: expect.any(Object) }),
127
+ );
128
+ expect(stdoutSpy).not.toHaveBeenCalled();
129
+ });
130
+
131
+ it("writes the success envelope by default", async () => {
132
+ const capability = createWorkflowCapability("pageAdded", {
133
+ title: "Page Added",
134
+ description: "Runs when a page is added",
135
+ triggers: [triggers.notionPageCreated()],
136
+ execute: () => {},
137
+ });
138
+
139
+ await capability.handler(pageCreatedEvent);
140
+
141
+ expect(stdoutSpy).toHaveBeenCalledWith(
142
+ '\n<__notion_output__>{"_tag":"success","value":{"status":"success"}}</__notion_output__>\n',
143
+ );
144
+ });
145
+
146
+ it("writes and throws an execution error", async () => {
147
+ const capability = createWorkflowCapability("pageAdded", {
148
+ title: "Page Added",
149
+ description: "Runs when a page is added",
150
+ triggers: [triggers.notionPageCreated()],
151
+ execute: () => {
152
+ throw new Error("Something went wrong");
153
+ },
154
+ });
155
+
156
+ await expect(capability.handler(pageCreatedEvent)).rejects.toThrow(ExecutionError);
157
+ expect(stdoutSpy).toHaveBeenCalledWith(
158
+ expect.stringContaining(
159
+ '"_tag":"error","error":{"name":"ExecutionError","message":"Error during worker execution: Error: Something went wrong"',
160
+ ),
161
+ );
162
+ });
163
+ });
@@ -0,0 +1,112 @@
1
+ import { ExecutionError } from "../error.js";
2
+ import type { WorkflowEventMap, WorkflowTrigger } from "../triggers.generated.js";
3
+ import type { HandlerOptions } from "../types.js";
4
+ import type { CapabilityContext } from "./context.js";
5
+ import { createCapabilityContext } from "./context.js";
6
+ import { writeOutput } from "./output.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 for a workflow capability
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
+ execute: (
39
+ event: WorkflowEventForTriggers<TTriggers>,
40
+ context: CapabilityContext,
41
+ ) => Promise<void> | void;
42
+ };
43
+
44
+ export type WorkflowCapability<
45
+ TTriggers extends readonly [WorkflowTrigger, ...WorkflowTrigger[]] = readonly [
46
+ WorkflowTrigger,
47
+ ...WorkflowTrigger[],
48
+ ],
49
+ > = {
50
+ _tag: "workflow";
51
+ key: string;
52
+ config: {
53
+ title: string;
54
+ description: string;
55
+ triggers: TTriggers;
56
+ };
57
+ handler: (
58
+ event: WorkflowEventForTriggers<TTriggers>,
59
+ options?: HandlerOptions,
60
+ ) => Promise<{ status: "success" } | undefined>;
61
+ };
62
+
63
+ /**
64
+ * Creates a workflow capability from configuration.
65
+ *
66
+ * @param key - The unique name for this capability.
67
+ * @param config - The workflow configuration.
68
+ * @returns The capability object.
69
+ */
70
+ export function createWorkflowCapability<
71
+ const TTriggers extends readonly [WorkflowTrigger, ...WorkflowTrigger[]],
72
+ >(key: string, config: WorkflowConfiguration<TTriggers>): WorkflowCapability<TTriggers> {
73
+ return {
74
+ _tag: "workflow",
75
+ key,
76
+ config: {
77
+ title: config.title,
78
+ description: config.description,
79
+ triggers: config.triggers,
80
+ },
81
+ async handler(
82
+ event: WorkflowEventForTriggers<TTriggers>,
83
+ options?: HandlerOptions,
84
+ ): Promise<{ status: "success" } | undefined> {
85
+ try {
86
+ const capabilityContext = createCapabilityContext("workflow");
87
+ await config.execute(event, capabilityContext);
88
+
89
+ if (options?.concreteOutput) {
90
+ return { status: "success" };
91
+ }
92
+
93
+ writeOutput({ _tag: "success", value: { status: "success" } });
94
+ } catch (err) {
95
+ const error = new ExecutionError(err);
96
+
97
+ if (!options?.concreteOutput) {
98
+ writeOutput({
99
+ _tag: "error",
100
+ error: {
101
+ name: error.name,
102
+ message: error.message,
103
+ trace: error.stack,
104
+ },
105
+ });
106
+ }
107
+
108
+ throw error;
109
+ }
110
+ },
111
+ };
112
+ }
package/src/index.ts CHANGED
@@ -1,4 +1,11 @@
1
- export { emojiIcon, imageIcon, notionIcon, place } from "./builder.js";
1
+ export type {
2
+ CustomBlockCapability,
3
+ CustomBlockCapabilityConfig,
4
+ CustomBlockConfiguration,
5
+ CustomBlockManifest,
6
+ CustomBlockManifestDataSource,
7
+ } from "./capabilities/custom-block.js";
8
+ export { emojiIcon, imageCover, imageIcon, notionIcon, place } from "./builder.js";
2
9
  export type {
3
10
  AiConnectorArchetype,
4
11
  AiConnectorCapability,
@@ -37,23 +44,28 @@ export type {
37
44
  SyncExecutionResult,
38
45
  SyncMode,
39
46
  } from "./capabilities/sync.js";
40
- export type {
41
- ToolCapability,
42
- ToolConfiguration,
43
- ToolHints,
44
- } from "./capabilities/tool.js";
47
+ export type { ToolCapability, ToolConfiguration, ToolHints } from "./capabilities/tool.js";
45
48
  export type {
46
49
  WebhookCapability,
47
50
  WebhookConfiguration,
48
51
  WebhookEvent,
49
52
  } from "./capabilities/webhook.js";
50
53
  export { WebhookVerificationError } from "./capabilities/webhook.js";
54
+ export type {
55
+ WorkflowCapability,
56
+ WorkflowConfiguration,
57
+ WorkflowEvent,
58
+ WorkflowEventForTrigger,
59
+ WorkflowEventForTriggers,
60
+ } from "./capabilities/workflow.js";
51
61
  export { RateLimitError } from "./error.js";
52
62
  export type { AnyJSONSchema, JSONSchema } from "./json-schema.js";
53
63
  export type { Infer, SchemaBuilder } from "./schema-builder.js";
54
64
  export { getSchema, j } from "./schema-builder.js";
55
65
  export type {
66
+ Cover,
56
67
  Icon,
68
+ ImageCover,
57
69
  ImageIcon,
58
70
  NoticonColor,
59
71
  NoticonName,
@@ -12,6 +12,7 @@
12
12
  * This ensures tsc sees the error on the line immediately after the directive.
13
13
  */
14
14
  import { describe, expect, it } from "vitest";
15
+
15
16
  import type {
16
17
  AnyJSONSchema,
17
18
  JSONSchema,
@@ -671,10 +672,7 @@ describe("JSONSchema — complex schemas", () => {
671
672
 
672
673
  describe("JSONSchema — structural type assertions", () => {
673
674
  it("JSONSchemaObjectDef requires additionalProperties to be false", () => {
674
- type _Proof = Assert<
675
- JSONSchemaObjectDef<{ x: number }>["additionalProperties"],
676
- false
677
- >;
675
+ type _Proof = Assert<JSONSchemaObjectDef<{ x: number }>["additionalProperties"], false>;
678
676
  });
679
677
 
680
678
  it("JSONSchemaArrayDef minItems is restricted to 0 | 1", () => {
@@ -45,16 +45,18 @@ interface ForbiddenCompositionKeywords {
45
45
 
46
46
  // ---- Individual schema definitions ----
47
47
 
48
- export interface JSONSchemaStringDef<T extends string = string>
49
- extends ForbiddenCompositionKeywords {
48
+ export interface JSONSchemaStringDef<
49
+ T extends string = string,
50
+ > extends ForbiddenCompositionKeywords {
50
51
  type: "string";
51
52
  description?: string;
52
53
  format?: JSONSchemaStringFormat;
53
54
  enum?: readonly T[];
54
55
  }
55
56
 
56
- export interface JSONSchemaNumberDef<T extends number = number>
57
- extends ForbiddenCompositionKeywords {
57
+ export interface JSONSchemaNumberDef<
58
+ T extends number = number,
59
+ > extends ForbiddenCompositionKeywords {
58
60
  type: "number" | "integer";
59
61
  description?: string;
60
62
  enum?: readonly T[];
@@ -70,8 +72,7 @@ export interface JSONSchemaNullDef extends ForbiddenCompositionKeywords {
70
72
  description?: string;
71
73
  }
72
74
 
73
- export interface JSONSchemaArrayDef<E = unknown>
74
- extends ForbiddenCompositionKeywords {
75
+ export interface JSONSchemaArrayDef<E = unknown> extends ForbiddenCompositionKeywords {
75
76
  type: "array";
76
77
  description?: string;
77
78
  items: JSONSchema<E>;
package/src/pacer.test.ts CHANGED
@@ -83,9 +83,7 @@ describe("pacerWait", () => {
83
83
  it("throws when pacer key not found in runtime context", async () => {
84
84
  setPacerState({ pacers: {} });
85
85
 
86
- await expect(pacerWait("unknown")).rejects.toThrow(
87
- 'Pacer "unknown" not found',
88
- );
86
+ await expect(pacerWait("unknown")).rejects.toThrow('Pacer "unknown" not found');
89
87
  });
90
88
 
91
89
  it("preserves allowedRequests and intervalMs across calls", async () => {
@@ -39,11 +39,7 @@ export function initPacerState(
39
39
  declarations?: readonly PacerDeclaration[],
40
40
  ): void {
41
41
  const pacers = runtimePacers ?? {};
42
- if (
43
- Object.keys(pacers).length === 0 &&
44
- declarations &&
45
- declarations.length > 0
46
- ) {
42
+ if (Object.keys(pacers).length === 0 && declarations && declarations.length > 0) {
47
43
  const localPacers: Record<string, PacerEntry> = {};
48
44
  for (const decl of declarations) {
49
45
  localPacers[decl.key] = {
@@ -1,11 +1,7 @@
1
1
  import { describe, expect, it } from "vitest";
2
+
2
3
  import type { JSONSchema } from "./json-schema.js";
3
- import {
4
- getSchema,
5
- type Infer,
6
- j,
7
- type SchemaBuilder,
8
- } from "./schema-builder.js";
4
+ import { getSchema, type Infer, j, type SchemaBuilder } from "./schema-builder.js";
9
5
 
10
6
  // ---------------------------------------------------------------------------
11
7
  // 1. Primitives
@@ -188,9 +184,7 @@ describe("json-schema-builder — array", () => {
188
184
  });
189
185
 
190
186
  it("array of objects", () => {
191
- const schema = getSchema(
192
- j.array(j.object({ name: j.string(), age: j.number() })),
193
- );
187
+ const schema = getSchema(j.array(j.object({ name: j.string(), age: j.number() })));
194
188
  expect(schema).toEqual({
195
189
  type: "array",
196
190
  items: {
@@ -351,9 +345,7 @@ describe("json-schema-builder — anyOf", () => {
351
345
  });
352
346
 
353
347
  it("anyOf with description", () => {
354
- expect(
355
- getSchema(j.anyOf(j.string(), j.number()).describe("string or number")),
356
- ).toEqual({
348
+ expect(getSchema(j.anyOf(j.string(), j.number()).describe("string or number"))).toEqual({
357
349
  anyOf: [{ type: "string" }, { type: "number" }],
358
350
  description: "string or number",
359
351
  });
@@ -463,12 +455,8 @@ describe("json-schema-builder — discriminated unions", () => {
463
455
 
464
456
  it("discriminated union with descriptions on individual variants", () => {
465
457
  const schema = j.anyOf(
466
- j
467
- .object({ type: j.enum("text"), value: j.string() })
468
- .describe("A text node"),
469
- j
470
- .object({ type: j.enum("image"), url: j.string() })
471
- .describe("An image node"),
458
+ j.object({ type: j.enum("text"), value: j.string() }).describe("A text node"),
459
+ j.object({ type: j.enum("image"), url: j.string() }).describe("An image node"),
472
460
  );
473
461
 
474
462
  const raw = getSchema(schema) as unknown as {
@@ -497,9 +485,7 @@ describe("json-schema-builder — discriminated unions", () => {
497
485
  expect(raw.properties.content.anyOf).toHaveLength(2);
498
486
 
499
487
  // The content field is in required
500
- expect(
501
- (getSchema(schema) as unknown as Record<string, unknown>).required,
502
- ).toContain("content");
488
+ expect((getSchema(schema) as unknown as Record<string, unknown>).required).toContain("content");
503
489
  });
504
490
 
505
491
  it("nullable discriminated union", () => {
@@ -614,15 +600,7 @@ describe("json-schema-builder — integration", () => {
614
600
  },
615
601
  createdAt: { type: "string", format: "date-time" },
616
602
  },
617
- required: [
618
- "foo",
619
- "bar",
620
- "count",
621
- "tags",
622
- "address",
623
- "nickname",
624
- "createdAt",
625
- ],
603
+ required: ["foo", "bar", "count", "tags", "address", "nickname", "createdAt"],
626
604
  additionalProperties: false,
627
605
  });
628
606
  });
@@ -634,9 +612,7 @@ describe("json-schema-builder — integration", () => {
634
612
 
635
613
  describe("json-schema-builder — constraint enforcement", () => {
636
614
  it("object always has required matching properties keys", () => {
637
- const schema = getSchema(
638
- j.object({ x: j.number(), y: j.string(), z: j.boolean() }),
639
- );
615
+ const schema = getSchema(j.object({ x: j.number(), y: j.string(), z: j.boolean() }));
640
616
  const raw = schema as unknown as Record<string, unknown>;
641
617
  expect(raw.required).toEqual(["x", "y", "z"]);
642
618
  expect(raw.additionalProperties).toBe(false);
@@ -669,8 +645,7 @@ describe("json-schema-builder — constraint enforcement", () => {
669
645
  }),
670
646
  );
671
647
  const raw = schema as unknown as Record<string, unknown>;
672
- const outer = (raw as { properties: { outer: Record<string, unknown> } })
673
- .properties.outer;
648
+ const outer = (raw as { properties: { outer: Record<string, unknown> } }).properties.outer;
674
649
  expect(outer.required).toEqual(["inner"]);
675
650
  expect(outer.additionalProperties).toBe(false);
676
651
  });