@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.
- package/dist/alpha/builder.d.ts +149 -0
- package/dist/alpha/builder.d.ts.map +1 -0
- package/dist/alpha/builder.js +260 -0
- package/dist/alpha/builder.test.d.ts +2 -0
- package/dist/alpha/builder.test.d.ts.map +1 -0
- package/dist/alpha/cli/build.d.ts +13 -0
- package/dist/alpha/cli/build.d.ts.map +1 -0
- package/dist/alpha/cli/build.js +36 -0
- package/dist/alpha/cli/build.test.d.ts +2 -0
- package/dist/alpha/cli/build.test.d.ts.map +1 -0
- package/dist/alpha/cli/codegen.d.ts +10 -0
- package/dist/alpha/cli/codegen.d.ts.map +1 -0
- package/dist/alpha/cli/codegen.js +41 -0
- package/dist/alpha/cli/codegen.test.d.ts +2 -0
- package/dist/alpha/cli/codegen.test.d.ts.map +1 -0
- package/dist/alpha/cli/discover.d.ts +35 -0
- package/dist/alpha/cli/discover.d.ts.map +1 -0
- package/dist/alpha/cli/discover.js +39 -0
- package/dist/alpha/cli/discover.test.d.ts +2 -0
- package/dist/alpha/cli/discover.test.d.ts.map +1 -0
- package/dist/alpha/cli/emit-manifest.d.ts +33 -0
- package/dist/alpha/cli/emit-manifest.d.ts.map +1 -0
- package/dist/alpha/cli/emit-manifest.js +266 -0
- package/dist/alpha/cli/emit-manifest.test.d.ts +2 -0
- package/dist/alpha/cli/emit-manifest.test.d.ts.map +1 -0
- package/dist/alpha/cli/index.d.ts +3 -0
- package/dist/alpha/cli/index.d.ts.map +1 -0
- package/dist/alpha/cli/index.js +36 -0
- package/dist/alpha/context.d.ts +8 -0
- package/dist/alpha/context.d.ts.map +1 -0
- package/dist/alpha/context.js +33 -0
- package/dist/alpha/database.d.ts +90 -0
- package/dist/alpha/database.d.ts.map +1 -0
- package/dist/alpha/database.js +9 -0
- package/dist/alpha/error.d.ts +41 -0
- package/dist/alpha/error.d.ts.map +1 -0
- package/dist/alpha/error.js +25 -0
- package/dist/alpha/icon-names.d.ts +6 -0
- package/dist/alpha/icon-names.d.ts.map +1 -0
- package/dist/alpha/icon-names.js +0 -0
- package/dist/alpha/json-schema.d.ts +117 -0
- package/dist/alpha/json-schema.d.ts.map +1 -0
- package/dist/alpha/json-schema.js +0 -0
- package/dist/alpha/manifest.d.ts +44 -0
- package/dist/alpha/manifest.d.ts.map +1 -0
- package/dist/alpha/manifest.js +0 -0
- package/dist/alpha/output.d.ts +7 -0
- package/dist/alpha/output.d.ts.map +1 -0
- package/dist/alpha/output.js +8 -0
- package/dist/alpha/pacer.d.ts +74 -0
- package/dist/alpha/pacer.d.ts.map +1 -0
- package/dist/alpha/pacer.js +66 -0
- package/dist/alpha/pacer.test.d.ts +2 -0
- package/dist/alpha/pacer.test.d.ts.map +1 -0
- package/dist/alpha/schedule.d.ts +6 -0
- package/dist/alpha/schedule.d.ts.map +1 -0
- package/dist/alpha/schedule.js +43 -0
- package/dist/alpha/schedule.test.d.ts +2 -0
- package/dist/alpha/schedule.test.d.ts.map +1 -0
- package/dist/alpha/schema-builder.d.ts +79 -0
- package/dist/alpha/schema-builder.d.ts.map +1 -0
- package/dist/alpha/schema-builder.js +135 -0
- package/dist/alpha/schema.d.ts +185 -0
- package/dist/alpha/schema.d.ts.map +1 -0
- package/dist/alpha/schema.js +115 -0
- package/dist/alpha/sync.d.ts +233 -0
- package/dist/alpha/sync.d.ts.map +1 -0
- package/dist/alpha/sync.js +70 -0
- package/dist/alpha/sync.test.d.ts +2 -0
- package/dist/alpha/sync.test.d.ts.map +1 -0
- package/dist/alpha/tool.d.ts +132 -0
- package/dist/alpha/tool.d.ts.map +1 -0
- package/dist/alpha/tool.js +156 -0
- package/dist/alpha/tool.test.d.ts +2 -0
- package/dist/alpha/tool.test.d.ts.map +1 -0
- package/dist/alpha/triggers.d.ts +365 -0
- package/dist/alpha/triggers.d.ts.map +1 -0
- package/dist/alpha/triggers.generated.d.ts +246 -0
- package/dist/alpha/triggers.generated.d.ts.map +1 -0
- package/dist/alpha/triggers.generated.js +239 -0
- package/dist/alpha/triggers.js +0 -0
- package/dist/alpha/types.d.ts +228 -0
- package/dist/alpha/types.d.ts.map +1 -0
- package/dist/alpha/types.js +0 -0
- package/dist/alpha/workflow.d.ts +70 -0
- package/dist/alpha/workflow.d.ts.map +1 -0
- package/dist/alpha/workflow.js +39 -0
- package/dist/alpha/workflow.test.d.ts +2 -0
- package/dist/alpha/workflow.test.d.ts.map +1 -0
- package/dist/builder.d.ts +7 -1
- package/dist/builder.d.ts.map +1 -1
- package/dist/builder.js +12 -3
- package/dist/capabilities/ai_connector.d.ts.map +1 -1
- package/dist/capabilities/automation.d.ts.map +1 -1
- package/dist/capabilities/context.d.ts.map +1 -1
- package/dist/capabilities/context.js +1 -1
- package/dist/capabilities/custom-block.d.ts +124 -0
- package/dist/capabilities/custom-block.d.ts.map +1 -0
- package/dist/capabilities/custom-block.js +22 -0
- package/dist/capabilities/oauth.d.ts.map +1 -1
- package/dist/capabilities/output.d.ts.map +1 -1
- package/dist/capabilities/output.js +2 -4
- package/dist/capabilities/stateful-capability.d.ts.map +1 -1
- package/dist/capabilities/stateful-capability.js +3 -7
- package/dist/capabilities/sync.d.ts +11 -1
- package/dist/capabilities/sync.d.ts.map +1 -1
- package/dist/capabilities/sync.js +2 -8
- package/dist/capabilities/tool.d.ts.map +1 -1
- package/dist/capabilities/tool.js +6 -20
- package/dist/capabilities/webhook.d.ts.map +1 -1
- package/dist/capabilities/workflow.d.ts +50 -0
- package/dist/capabilities/workflow.d.ts.map +1 -0
- package/dist/capabilities/workflow.js +40 -0
- package/dist/capabilities/workflow.test.d.ts +2 -0
- package/dist/capabilities/workflow.test.d.ts.map +1 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/json-schema.d.ts.map +1 -1
- package/dist/schema-builder.d.ts.map +1 -1
- package/dist/schema.d.ts.map +1 -1
- package/dist/triggers.d.ts +365 -0
- package/dist/triggers.d.ts.map +1 -0
- package/dist/triggers.generated.d.ts +246 -0
- package/dist/triggers.generated.d.ts.map +1 -0
- package/dist/triggers.generated.js +239 -0
- package/dist/triggers.js +0 -0
- package/dist/types.d.ts +18 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/worker.d.ts +72 -2
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +85 -4
- package/package.json +81 -14
- package/src/alpha/ajv-formats.d.ts +7 -0
- package/src/alpha/builder.test.ts +197 -0
- package/src/alpha/builder.ts +338 -0
- package/src/alpha/cli/build.test.ts +240 -0
- package/src/alpha/cli/build.ts +57 -0
- package/src/alpha/cli/codegen.test.ts +31 -0
- package/src/alpha/cli/codegen.ts +51 -0
- package/src/alpha/cli/discover.test.ts +43 -0
- package/src/alpha/cli/discover.ts +81 -0
- package/src/alpha/cli/emit-manifest.test.ts +45 -0
- package/src/alpha/cli/emit-manifest.ts +409 -0
- package/src/alpha/cli/index.ts +41 -0
- package/src/alpha/context.ts +61 -0
- package/src/alpha/database.ts +102 -0
- package/src/alpha/error.ts +49 -0
- package/src/alpha/icon-names.ts +890 -0
- package/src/alpha/json-schema.ts +180 -0
- package/src/alpha/manifest.ts +47 -0
- package/src/alpha/output.ts +8 -0
- package/src/alpha/pacer.test.ts +81 -0
- package/src/alpha/pacer.ts +181 -0
- package/src/alpha/schedule.test.ts +31 -0
- package/src/alpha/schedule.ts +53 -0
- package/src/alpha/schema-builder.ts +193 -0
- package/src/alpha/schema.ts +222 -0
- package/src/alpha/sync.test.ts +191 -0
- package/src/alpha/sync.ts +340 -0
- package/src/alpha/tool.test.ts +116 -0
- package/src/alpha/tool.ts +319 -0
- package/src/alpha/triggers.generated.ts +489 -0
- package/src/alpha/triggers.ts +500 -0
- package/src/alpha/types.ts +284 -0
- package/src/alpha/workflow.test.ts +84 -0
- package/src/alpha/workflow.ts +131 -0
- package/src/builder.test.ts +28 -60
- package/src/builder.ts +20 -7
- package/src/capabilities/ai_connector.test.ts +9 -32
- package/src/capabilities/ai_connector.ts +5 -17
- package/src/capabilities/automation.test.ts +3 -12
- package/src/capabilities/automation.ts +3 -11
- package/src/capabilities/context.ts +5 -8
- package/src/capabilities/custom-block.ts +181 -0
- package/src/capabilities/oauth.test.ts +1 -0
- package/src/capabilities/oauth.ts +1 -3
- package/src/capabilities/output.ts +1 -3
- package/src/capabilities/stateful-capability.test.ts +1 -0
- package/src/capabilities/stateful-capability.ts +3 -7
- package/src/capabilities/sync.test.ts +76 -42
- package/src/capabilities/sync.ts +16 -40
- package/src/capabilities/tool.test.ts +106 -146
- package/src/capabilities/tool.ts +17 -39
- package/src/capabilities/webhook.ts +3 -10
- package/src/capabilities/workflow.test.ts +163 -0
- package/src/capabilities/workflow.ts +112 -0
- package/src/index.ts +18 -6
- package/src/json-schema.test.ts +2 -4
- package/src/json-schema.ts +7 -6
- package/src/pacer.test.ts +1 -3
- package/src/pacer_internal.ts +1 -5
- package/src/schema-builder.test.ts +10 -35
- package/src/schema-builder.ts +6 -16
- package/src/schema.ts +2 -10
- package/src/triggers.generated.ts +489 -0
- package/src/triggers.ts +504 -0
- package/src/types.ts +21 -7
- package/src/worker.test.ts +147 -0
- package/src/worker.ts +155 -32
- package/dist/block.d.ts +0 -321
- package/dist/block.d.ts.map +0 -1
- package/src/block.ts +0 -529
- /package/dist/{block.js → alpha/ajv-formats.d.js} +0 -0
|
@@ -1,12 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
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
|
-
|
|
96
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
});
|
package/src/capabilities/sync.ts
CHANGED
|
@@ -1,15 +1,8 @@
|
|
|
1
1
|
import { ExecutionError, RateLimitError } from "../error.js";
|
|
2
|
-
import {
|
|
3
|
-
|
|
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
|
-
|
|
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
|
-
"
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
"
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
"
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
"
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
136
|
-
{
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
|
|
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: "
|
|
186
|
-
description: "
|
|
145
|
+
title: "Date Formats",
|
|
146
|
+
description: "Uses date and datetime format strings",
|
|
187
147
|
schema: j.object({
|
|
188
|
-
|
|
148
|
+
due: j.date(),
|
|
149
|
+
createdAt: j.datetime(),
|
|
189
150
|
}),
|
|
190
|
-
execute: ({
|
|
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
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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
|
-
"
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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
|
-
"
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
"
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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
|
-
"
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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
|
});
|