@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,12 +1,5 @@
1
- import {
2
- afterEach,
3
- beforeEach,
4
- describe,
5
- expect,
6
- it,
7
- type Mock,
8
- vi,
9
- } from "vitest";
1
+ import { afterEach, beforeEach, describe, expect, it, type Mock, vi } from "vitest";
2
+
10
3
  import * as Builder from "../builder.js";
11
4
  import { ExecutionError, RateLimitError } from "../error.js";
12
5
  import { getPacerState } from "../pacer_internal.js";
@@ -18,9 +11,7 @@ describe("Worker.sync", () => {
18
11
  let stdoutSpy: Mock<typeof process.stdout.write>;
19
12
 
20
13
  beforeEach(() => {
21
- stdoutSpy = vi
22
- .spyOn(process.stdout, "write")
23
- .mockImplementation(() => true);
14
+ stdoutSpy = vi.spyOn(process.stdout, "write").mockImplementation(() => true);
24
15
  });
25
16
 
26
17
  afterEach(() => {
@@ -47,10 +38,7 @@ describe("Worker.sync", () => {
47
38
  });
48
39
 
49
40
  it("can be used to build relation arrays", () => {
50
- const value = [
51
- Builder.relation("project-123"),
52
- Builder.relation("project-456"),
53
- ];
41
+ const value = [Builder.relation("project-123"), Builder.relation("project-456")];
54
42
 
55
43
  expect(value).toEqual([
56
44
  { type: "primaryKey", value: "project-123" },
@@ -59,6 +47,34 @@ describe("Worker.sync", () => {
59
47
  });
60
48
  });
61
49
 
50
+ describe("Builder.imageCover", () => {
51
+ it("creates an image cover with the default position", () => {
52
+ const value = Builder.imageCover("https://example.com/cover.jpg");
53
+
54
+ expect(value).toEqual({
55
+ type: "image",
56
+ url: "https://example.com/cover.jpg",
57
+ position: 0.5,
58
+ });
59
+ });
60
+
61
+ it("creates an image cover with a custom position", () => {
62
+ const value = Builder.imageCover("https://example.com/cover.jpg", 0.25);
63
+
64
+ expect(value).toEqual({
65
+ type: "image",
66
+ url: "https://example.com/cover.jpg",
67
+ position: 0.25,
68
+ });
69
+ });
70
+
71
+ it("rejects cover positions outside the supported range", () => {
72
+ expect(() => Builder.imageCover("https://example.com/cover.jpg", 1.5)).toThrow(
73
+ "Expected a number between 0 and 1",
74
+ );
75
+ });
76
+ });
77
+
62
78
  describe("SyncedObject with relation properties", () => {
63
79
  it("allows relation values in synced objects", async () => {
64
80
  const worker = new Worker();
@@ -91,11 +107,44 @@ describe("Worker.sync", () => {
91
107
  key: "task-2",
92
108
  properties: {
93
109
  "Task ID": Builder.title("TASK-002"),
94
- Project: [
95
- Builder.relation("project-123"),
96
- Builder.relation("project-456"),
97
- ],
110
+ Project: [Builder.relation("project-123"), Builder.relation("project-456")],
111
+ },
112
+ },
113
+ ],
114
+ hasMore: false,
115
+ }),
116
+ });
117
+
118
+ expect(capability._tag).toBe("sync");
119
+ expect(capability.config.databaseKey).toBe("tasks");
120
+ });
121
+ });
122
+
123
+ describe("SyncedObject with page covers", () => {
124
+ it("allows cover values in synced objects", async () => {
125
+ const worker = new Worker();
126
+ const tasks = worker.database("tasks", {
127
+ type: "managed",
128
+ initialTitle: "Tasks",
129
+ primaryKeyProperty: "Task ID",
130
+ schema: {
131
+ properties: {
132
+ "Task ID": Schema.title(),
133
+ },
134
+ },
135
+ });
136
+
137
+ const capability = createSyncCapability("tasksSync", {
138
+ database: tasks,
139
+ execute: async () => ({
140
+ changes: [
141
+ {
142
+ type: "upsert",
143
+ key: "task-1",
144
+ properties: {
145
+ "Task ID": Builder.title("TASK-001"),
98
146
  },
147
+ cover: Builder.imageCover("https://example.com/cover.jpg"),
99
148
  },
100
149
  ],
101
150
  hasMore: false,
@@ -243,12 +292,8 @@ describe("Worker.sync", () => {
243
292
 
244
293
  await expect(capability.handler()).rejects.toThrow(ExecutionError);
245
294
 
246
- expect(stdoutSpy).toHaveBeenCalledWith(
247
- expect.stringContaining(`"_tag":"error"`),
248
- );
249
- expect(stdoutSpy).toHaveBeenCalledWith(
250
- expect.stringContaining(`"name":"ExecutionError"`),
251
- );
295
+ expect(stdoutSpy).toHaveBeenCalledWith(expect.stringContaining(`"_tag":"error"`));
296
+ expect(stdoutSpy).toHaveBeenCalledWith(expect.stringContaining(`"name":"ExecutionError"`));
252
297
  });
253
298
 
254
299
  it("wraps RateLimitError in error envelope with _tag rate_limit", async () => {
@@ -273,12 +318,8 @@ describe("Worker.sync", () => {
273
318
 
274
319
  await expect(capability.handler()).rejects.toThrow(RateLimitError);
275
320
 
276
- expect(stdoutSpy).toHaveBeenCalledWith(
277
- expect.stringContaining(`"_tag":"rate_limit"`),
278
- );
279
- expect(stdoutSpy).toHaveBeenCalledWith(
280
- expect.stringContaining(`"name":"RateLimitError"`),
281
- );
321
+ expect(stdoutSpy).toHaveBeenCalledWith(expect.stringContaining(`"_tag":"rate_limit"`));
322
+ expect(stdoutSpy).toHaveBeenCalledWith(expect.stringContaining(`"name":"RateLimitError"`));
282
323
  });
283
324
 
284
325
  it("includes retryAfter in rate_limit error envelope when provided", async () => {
@@ -307,18 +348,14 @@ describe("Worker.sync", () => {
307
348
  .map((call) => String(call[0]))
308
349
  .find((s) => s.includes(`"_tag":"rate_limit"`)) as string;
309
350
  const parsed = JSON.parse(
310
- output.replace(
311
- /\n<__notion_output__>(.*)<\/__notion_output__>\n/,
312
- "$1",
313
- ),
351
+ output.replace(/\n<__notion_output__>(.*)<\/__notion_output__>\n/, "$1"),
314
352
  );
315
353
  expect(parsed).toEqual({
316
354
  _tag: "error",
317
355
  error: {
318
356
  _tag: "rate_limit",
319
357
  name: "RateLimitError",
320
- message:
321
- "Error during worker execution: Rate limited by external API",
358
+ message: "Error during worker execution: Rate limited by external API",
322
359
  retryAfter: 30,
323
360
  },
324
361
  });
@@ -350,10 +387,7 @@ describe("Worker.sync", () => {
350
387
  .map((call) => String(call[0]))
351
388
  .find((s) => s.includes(`"_tag":"rate_limit"`)) as string;
352
389
  const parsed = JSON.parse(
353
- output.replace(
354
- /\n<__notion_output__>(.*)<\/__notion_output__>\n/,
355
- "$1",
356
- ),
390
+ output.replace(/\n<__notion_output__>(.*)<\/__notion_output__>\n/, "$1"),
357
391
  );
358
392
  expect(parsed.error.retryAfter).toBeUndefined();
359
393
  });
@@ -1,15 +1,8 @@
1
1
  import { ExecutionError, RateLimitError } from "../error.js";
2
- import {
3
- getPacerState,
4
- initPacerState,
5
- type PacerDeclaration,
6
- } from "../pacer_internal.js";
7
- import type {
8
- PropertyConfiguration,
9
- PropertySchema,
10
- Schema,
11
- } from "../schema.js";
2
+ import { getPacerState, initPacerState, type PacerDeclaration } from "../pacer_internal.js";
3
+ import type { PropertyConfiguration, PropertySchema, Schema } from "../schema.js";
12
4
  import type {
5
+ Cover,
13
6
  HandlerOptions,
14
7
  Icon,
15
8
  PeopleValue,
@@ -61,10 +54,7 @@ export type SyncMode = StatefulCapabilityMode;
61
54
  /**
62
55
  * A change representing a record to be created or updated.
63
56
  */
64
- export type SyncChangeUpsert<
65
- PK extends string,
66
- S extends PropertySchema<PK>,
67
- > = {
57
+ export type SyncChangeUpsert<PK extends string, S extends PropertySchema<PK>> = {
68
58
  /**
69
59
  * The type of change. Use `"upsert"` to create or update a record.
70
60
  */
@@ -98,6 +88,11 @@ export type SyncChangeUpsert<
98
88
  * Use the `Builder.emojiIcon()`, `Builder.notionIcon()`, or `Builder.imageIcon()` helpers.
99
89
  */
100
90
  icon?: Icon;
91
+ /**
92
+ * Optional cover to use as the cover for this row's page.
93
+ * Use the `Builder.imageCover()` helper.
94
+ */
95
+ cover?: Cover;
101
96
  /**
102
97
  * Optional markdown content to add to the page body.
103
98
  * This will be converted to Notion blocks and added as page content.
@@ -162,11 +157,7 @@ export type SyncExecutionResult<PK extends string, State = unknown> = {
162
157
  /**
163
158
  * Configuration for a sync capability that syncs data to a database.
164
159
  */
165
- export type SyncConfiguration<
166
- PK extends string,
167
- S extends Schema<PK>,
168
- State = unknown,
169
- > = {
160
+ export type SyncConfiguration<PK extends string, S extends Schema<PK>, State = unknown> = {
170
161
  /**
171
162
  * The database to sync data into.
172
163
  */
@@ -227,11 +218,7 @@ export type SyncCapability = ReturnType<typeof createSyncCapability>;
227
218
  * @returns A handler function that executes the sync function, and passes data
228
219
  * needed to complete the sync back to the platform.
229
220
  */
230
- export function createSyncCapability<
231
- PK extends string,
232
- S extends Schema<PK>,
233
- Context = unknown,
234
- >(
221
+ export function createSyncCapability<PK extends string, S extends Schema<PK>, Context = unknown>(
235
222
  key: string,
236
223
  syncConfiguration: SyncConfiguration<PK, S, Context>,
237
224
  pacerDeclarations?: readonly PacerDeclaration[],
@@ -243,14 +230,9 @@ export function createSyncCapability<
243
230
  databaseKey: syncConfiguration.database.key,
244
231
  primaryKeyProperty: syncConfiguration.database.config.primaryKeyProperty,
245
232
  mode: syncConfiguration.mode,
246
- schedule: syncConfiguration.schedule
247
- ? parseSchedule(syncConfiguration.schedule)
248
- : undefined,
233
+ schedule: syncConfiguration.schedule ? parseSchedule(syncConfiguration.schedule) : undefined,
249
234
  },
250
- async handler(
251
- runtimeContext?: StatefulRuntimeContext<Context>,
252
- options?: HandlerOptions,
253
- ) {
235
+ async handler(runtimeContext?: StatefulRuntimeContext<Context>, options?: HandlerOptions) {
254
236
  const capabilityContext = createCapabilityContext("sync");
255
237
  const state = runtimeContext?.state ?? runtimeContext?.userContext;
256
238
 
@@ -258,10 +240,7 @@ export function createSyncCapability<
258
240
 
259
241
  let executionResult: SyncExecutionResult<PK, Context>;
260
242
  try {
261
- executionResult = await syncConfiguration.execute(
262
- state,
263
- capabilityContext,
264
- );
243
+ executionResult = await syncConfiguration.execute(state, capabilityContext);
265
244
  } catch (err) {
266
245
  if (err instanceof RateLimitError) {
267
246
  if (!options?.concreteOutput) {
@@ -271,9 +250,7 @@ export function createSyncCapability<
271
250
  _tag: "rate_limit",
272
251
  name: err.name,
273
252
  message: err.message,
274
- ...(err.retryAfter !== undefined
275
- ? { retryAfter: err.retryAfter }
276
- : {}),
253
+ ...(err.retryAfter !== undefined ? { retryAfter: err.retryAfter } : {}),
277
254
  },
278
255
  };
279
256
  writeOutput(envelope);
@@ -298,8 +275,7 @@ export function createSyncCapability<
298
275
 
299
276
  const changes = executionResult.changes.map((change) => ({
300
277
  ...change,
301
- targetDatabaseKey:
302
- change.targetDatabaseKey ?? syncConfiguration.database.key,
278
+ targetDatabaseKey: change.targetDatabaseKey ?? syncConfiguration.database.key,
303
279
  }));
304
280
 
305
281
  const result = {
@@ -1,12 +1,5 @@
1
- import {
2
- afterEach,
3
- beforeEach,
4
- describe,
5
- expect,
6
- it,
7
- type Mock,
8
- vi,
9
- } from "vitest";
1
+ import { afterEach, beforeEach, describe, expect, it, type Mock, vi } from "vitest";
2
+
10
3
  import { j } from "../schema-builder.js";
11
4
  import { createToolCapability } from "./tool.js";
12
5
 
@@ -14,9 +7,7 @@ describe("Worker.tool", () => {
14
7
  let stdoutSpy: Mock<typeof process.stdout.write>;
15
8
 
16
9
  beforeEach(() => {
17
- stdoutSpy = vi
18
- .spyOn(process.stdout, "write")
19
- .mockImplementation(() => true);
10
+ stdoutSpy = vi.spyOn(process.stdout, "write").mockImplementation(() => true);
20
11
  });
21
12
 
22
13
  afterEach(() => {
@@ -24,17 +15,14 @@ describe("Worker.tool", () => {
24
15
  });
25
16
 
26
17
  it("sync execution", async () => {
27
- const capability = createToolCapability<{ name: string }, string>(
28
- "sayHello",
29
- {
30
- title: "Say Hello",
31
- description: "Greet a user",
32
- schema: j.object({ name: j.string() }),
33
- execute: (input: { name: string }) => {
34
- return `Hello, ${input.name}!`;
35
- },
18
+ const capability = createToolCapability<{ name: string }, string>("sayHello", {
19
+ title: "Say Hello",
20
+ description: "Greet a user",
21
+ schema: j.object({ name: j.string() }),
22
+ execute: (input: { name: string }) => {
23
+ return `Hello, ${input.name}!`;
36
24
  },
37
- );
25
+ });
38
26
 
39
27
  const result = await capability.handler({ name: "Alice" });
40
28
 
@@ -46,19 +34,16 @@ describe("Worker.tool", () => {
46
34
  });
47
35
 
48
36
  it("async execution", async () => {
49
- const capability = createToolCapability<{ id: number }, { data: string }>(
50
- "fetchData",
51
- {
52
- title: "Fetch Data",
53
- description: "Fetch data asynchronously",
54
- schema: j.object({ id: j.number() }),
55
- execute: async (input: { id: number }) => {
56
- // Simulate async operation
57
- await new Promise((resolve) => setTimeout(resolve, 10));
58
- return { data: `Data for ID ${input.id}` };
59
- },
37
+ const capability = createToolCapability<{ id: number }, { data: string }>("fetchData", {
38
+ title: "Fetch Data",
39
+ description: "Fetch data asynchronously",
40
+ schema: j.object({ id: j.number() }),
41
+ execute: async (input: { id: number }) => {
42
+ // Simulate async operation
43
+ await new Promise((resolve) => setTimeout(resolve, 10));
44
+ return { data: `Data for ID ${input.id}` };
60
45
  },
61
- );
46
+ });
62
47
 
63
48
  const result = await capability.handler({ id: 42 });
64
49
 
@@ -73,17 +58,14 @@ describe("Worker.tool", () => {
73
58
  });
74
59
 
75
60
  it("execution error", async () => {
76
- const capability = createToolCapability<Record<string, never>, string>(
77
- "throwError",
78
- {
79
- title: "Throw Error",
80
- description: "Throws an error",
81
- schema: j.object({}),
82
- execute: () => {
83
- throw new Error("Something went wrong");
84
- },
61
+ const capability = createToolCapability<Record<string, never>, string>("throwError", {
62
+ title: "Throw Error",
63
+ description: "Throws an error",
64
+ schema: j.object({}),
65
+ execute: () => {
66
+ throw new Error("Something went wrong");
85
67
  },
86
- );
68
+ });
87
69
 
88
70
  const result = (await capability.handler({})) as {
89
71
  _tag: "error";
@@ -102,17 +84,14 @@ describe("Worker.tool", () => {
102
84
  });
103
85
 
104
86
  it("invalid input", async () => {
105
- const capability = createToolCapability<{ name: string }, string>(
106
- "sayHelloStrict",
107
- {
108
- title: "Say Hello",
109
- description: "Requires a name property",
110
- schema: j.object({ name: j.string() }),
111
- execute: (input: { name: string }) => {
112
- return `Hello, ${input.name}!`;
113
- },
87
+ const capability = createToolCapability<{ name: string }, string>("sayHelloStrict", {
88
+ title: "Say Hello",
89
+ description: "Requires a name property",
90
+ schema: j.object({ name: j.string() }),
91
+ execute: (input: { name: string }) => {
92
+ return `Hello, ${input.name}!`;
114
93
  },
115
- );
94
+ });
116
95
 
117
96
  const result = (await capability.handler({ age: 25 })) as {
118
97
  _tag: "error";
@@ -131,19 +110,19 @@ describe("Worker.tool", () => {
131
110
  });
132
111
 
133
112
  it("invalid output", async () => {
134
- const capability = createToolCapability<
135
- Record<string, never>,
136
- { result: string }
137
- >("invalidOutput", {
138
- title: "Return Invalid Output",
139
- description: "Returns output that doesn't match schema",
140
- schema: j.object({}),
141
- outputSchema: j.object({ result: j.string() }),
142
- // @ts-expect-error Testing invalid output - intentionally returning number instead of string
143
- execute: () => {
144
- return { result: 123 };
113
+ const capability = createToolCapability<Record<string, never>, { result: string }>(
114
+ "invalidOutput",
115
+ {
116
+ title: "Return Invalid Output",
117
+ description: "Returns output that doesn't match schema",
118
+ schema: j.object({}),
119
+ outputSchema: j.object({ result: j.string() }),
120
+ // @ts-expect-error Testing invalid output - intentionally returning number instead of string
121
+ execute: () => {
122
+ return { result: 123 };
123
+ },
145
124
  },
146
- });
125
+ );
147
126
 
148
127
  const result = (await capability.handler({})) as {
149
128
  _tag: "error";
@@ -155,42 +134,37 @@ describe("Worker.tool", () => {
155
134
  expect(result.error.message).toContain("result");
156
135
 
157
136
  expect(stdoutSpy).toHaveBeenCalledWith(
158
- expect.stringContaining(
159
- `"_tag":"error","error":{"name":"InvalidToolOutputError"`,
160
- ),
137
+ expect.stringContaining(`"_tag":"error","error":{"name":"InvalidToolOutputError"`),
161
138
  );
162
139
  });
163
140
 
164
141
  it("accepts schema with format string types (date, datetime, etc.)", () => {
165
- const capability = createToolCapability<
166
- { due: string; createdAt: string },
167
- string
168
- >("dateFormats", {
169
- title: "Date Formats",
170
- description: "Uses date and datetime format strings",
171
- schema: j.object({
172
- due: j.date(),
173
- createdAt: j.datetime(),
174
- }),
175
- execute: ({ due, createdAt }) => `${due} ${createdAt}`,
176
- });
177
-
178
- expect(capability.config.schema).toBeDefined();
179
- });
180
-
181
- it("rejects invalid date-time format at runtime", async () => {
182
- const capability = createToolCapability<{ ts: string }, string>(
183
- "datetimeValidation",
142
+ const capability = createToolCapability<{ due: string; createdAt: string }, string>(
143
+ "dateFormats",
184
144
  {
185
- title: "Datetime Validation",
186
- description: "Validates datetime input",
145
+ title: "Date Formats",
146
+ description: "Uses date and datetime format strings",
187
147
  schema: j.object({
188
- ts: j.datetime(),
148
+ due: j.date(),
149
+ createdAt: j.datetime(),
189
150
  }),
190
- execute: ({ ts }) => ts,
151
+ execute: ({ due, createdAt }) => `${due} ${createdAt}`,
191
152
  },
192
153
  );
193
154
 
155
+ expect(capability.config.schema).toBeDefined();
156
+ });
157
+
158
+ it("rejects invalid date-time format at runtime", async () => {
159
+ const capability = createToolCapability<{ ts: string }, string>("datetimeValidation", {
160
+ title: "Datetime Validation",
161
+ description: "Validates datetime input",
162
+ schema: j.object({
163
+ ts: j.datetime(),
164
+ }),
165
+ execute: ({ ts }) => ts,
166
+ });
167
+
194
168
  const valid = await capability.handler({ ts: "2024-01-01T00:00:00Z" });
195
169
  expect(valid).toEqual({
196
170
  _tag: "success",
@@ -206,78 +180,66 @@ describe("Worker.tool", () => {
206
180
  });
207
181
 
208
182
  it("accepts schema with nullable (string | null) properties", () => {
209
- const capability = createToolCapability<
210
- { name: string; nickname: string | null },
211
- string
212
- >("nullable", {
213
- title: "Nullable",
214
- description: "Has nullable properties",
215
- schema: j.object({
216
- name: j.string(),
217
- nickname: j.string().nullable(),
218
- }),
219
- execute: ({ name, nickname }) => `${name} (${nickname ?? "no nickname"})`,
220
- });
183
+ const capability = createToolCapability<{ name: string; nickname: string | null }, string>(
184
+ "nullable",
185
+ {
186
+ title: "Nullable",
187
+ description: "Has nullable properties",
188
+ schema: j.object({
189
+ name: j.string(),
190
+ nickname: j.string().nullable(),
191
+ }),
192
+ execute: ({ name, nickname }) => `${name} (${nickname ?? "no nickname"})`,
193
+ },
194
+ );
221
195
 
222
196
  expect(capability.config.schema).toBeDefined();
223
197
  });
224
198
 
225
199
  describe("hints", () => {
226
200
  it("omits hints from config when not specified (existing tools unchanged)", () => {
227
- const capability = createToolCapability<{ name: string }, string>(
228
- "noHints",
229
- {
230
- title: "No Hints",
231
- description: "A tool without hints",
232
- schema: j.object({ name: j.string() }),
233
- execute: ({ name }) => `Hello, ${name}`,
234
- },
235
- );
201
+ const capability = createToolCapability<{ name: string }, string>("noHints", {
202
+ title: "No Hints",
203
+ description: "A tool without hints",
204
+ schema: j.object({ name: j.string() }),
205
+ execute: ({ name }) => `Hello, ${name}`,
206
+ });
236
207
 
237
208
  expect(capability.config.hints).toBeUndefined();
238
209
  });
239
210
 
240
211
  it("flows readOnlyHint=true through to config", () => {
241
- const capability = createToolCapability<{ query: string }, string>(
242
- "readOnly",
243
- {
244
- title: "Read Only",
245
- description: "A read-only tool",
246
- schema: j.object({ query: j.string() }),
247
- hints: { readOnlyHint: true },
248
- execute: ({ query }) => query,
249
- },
250
- );
212
+ const capability = createToolCapability<{ query: string }, string>("readOnly", {
213
+ title: "Read Only",
214
+ description: "A read-only tool",
215
+ schema: j.object({ query: j.string() }),
216
+ hints: { readOnlyHint: true },
217
+ execute: ({ query }) => query,
218
+ });
251
219
 
252
220
  expect(capability.config.hints).toEqual({ readOnlyHint: true });
253
221
  });
254
222
 
255
223
  it("flows readOnlyHint=false through to config", () => {
256
- const capability = createToolCapability<{ pageId: string }, string>(
257
- "writeTool",
258
- {
259
- title: "Write Tool",
260
- description: "A write tool",
261
- schema: j.object({ pageId: j.string() }),
262
- hints: { readOnlyHint: false },
263
- execute: ({ pageId }) => pageId,
264
- },
265
- );
224
+ const capability = createToolCapability<{ pageId: string }, string>("writeTool", {
225
+ title: "Write Tool",
226
+ description: "A write tool",
227
+ schema: j.object({ pageId: j.string() }),
228
+ hints: { readOnlyHint: false },
229
+ execute: ({ pageId }) => pageId,
230
+ });
266
231
 
267
232
  expect(capability.config.hints).toEqual({ readOnlyHint: false });
268
233
  });
269
234
 
270
235
  it("does not affect execution behavior", async () => {
271
- const capability = createToolCapability<{ x: number }, number>(
272
- "doubleReadOnly",
273
- {
274
- title: "Double",
275
- description: "Doubles a number",
276
- schema: j.object({ x: j.number() }),
277
- hints: { readOnlyHint: true },
278
- execute: ({ x }) => x * 2,
279
- },
280
- );
236
+ const capability = createToolCapability<{ x: number }, number>("doubleReadOnly", {
237
+ title: "Double",
238
+ description: "Doubles a number",
239
+ schema: j.object({ x: j.number() }),
240
+ hints: { readOnlyHint: true },
241
+ execute: ({ x }) => x * 2,
242
+ });
281
243
 
282
244
  const result = await capability.handler({ x: 7 });
283
245
 
@@ -315,9 +277,7 @@ describe("Worker.tool", () => {
315
277
  expect(result.error.message).toContain("message");
316
278
 
317
279
  expect(stdoutSpy).toHaveBeenCalledWith(
318
- expect.stringContaining(
319
- `"_tag":"error","error":{"name":"InvalidToolOutputError"`,
320
- ),
280
+ expect.stringContaining(`"_tag":"error","error":{"name":"InvalidToolOutputError"`),
321
281
  );
322
282
  });
323
283
  });