@project-ajax/create 0.0.30 → 0.0.31
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/package.json +1 -1
- package/template/.examples/automation-example.ts +2 -7
- package/template/.examples/sync-example.ts +1 -1
- package/template/.examples/tool-example.ts +1 -1
- package/template/AGENTS.md +14 -12
- package/template/CLAUDE.md +178 -0
- package/template/README.md +14 -12
- package/template/src/index.ts +25 -8
package/package.json
CHANGED
|
@@ -1,14 +1,8 @@
|
|
|
1
|
-
import { Client } from "@notionhq/client";
|
|
2
1
|
import { Worker } from "@project-ajax/sdk";
|
|
3
2
|
|
|
4
3
|
const worker = new Worker();
|
|
5
4
|
export default worker;
|
|
6
5
|
|
|
7
|
-
// Initialize the Notion client with OAuth token from environment
|
|
8
|
-
const notion = new Client({
|
|
9
|
-
auth: process.env.NOTION_API_TOKEN,
|
|
10
|
-
});
|
|
11
|
-
|
|
12
6
|
type RichTextProperty = {
|
|
13
7
|
type: "rich_text";
|
|
14
8
|
rich_text: Array<{ plain_text: string }>;
|
|
@@ -26,7 +20,8 @@ worker.automation("questionAnswerAutomation", {
|
|
|
26
20
|
title: "Question Answer Automation",
|
|
27
21
|
description:
|
|
28
22
|
"Reads questions from database pages and updates them with answers",
|
|
29
|
-
execute: async ({
|
|
23
|
+
execute: async (event, { notion }) => {
|
|
24
|
+
const { pageId, pageData } = event;
|
|
30
25
|
// Extract email from the page dat
|
|
31
26
|
const emailProperty = pageData?.properties?.Email as
|
|
32
27
|
| RichTextProperty
|
|
@@ -41,7 +41,7 @@ worker.tool<
|
|
|
41
41
|
additionalProperties: false,
|
|
42
42
|
},
|
|
43
43
|
// The function that executes when the tool is called
|
|
44
|
-
execute: async (input) => {
|
|
44
|
+
execute: async (input, { notion: _notion }) => {
|
|
45
45
|
// Destructure input with default values
|
|
46
46
|
const { query: _query, limit: _limit = 10 } = input;
|
|
47
47
|
|
package/template/AGENTS.md
CHANGED
|
@@ -20,7 +20,7 @@ export default worker;
|
|
|
20
20
|
worker.sync("tasksSync", {
|
|
21
21
|
primaryKeyProperty: "ID",
|
|
22
22
|
schema: { defaultName: "Tasks", properties: { Name: Schema.title(), ID: Schema.richText() } },
|
|
23
|
-
execute: async () => ({
|
|
23
|
+
execute: async (_state, { notion }) => ({
|
|
24
24
|
changes: [{ type: "upsert", key: "1", properties: { Name: Builder.title("Write docs"), ID: Builder.richText("1") } }],
|
|
25
25
|
hasMore: false,
|
|
26
26
|
}),
|
|
@@ -30,18 +30,20 @@ worker.tool("sayHello", {
|
|
|
30
30
|
title: "Say Hello",
|
|
31
31
|
description: "Return a greeting",
|
|
32
32
|
schema: { type: "object", properties: { name: { type: "string" } }, required: ["name"], additionalProperties: false },
|
|
33
|
-
execute: ({ name }) => `Hello, ${name}`,
|
|
33
|
+
execute: ({ name }, { notion }) => `Hello, ${name}`,
|
|
34
34
|
});
|
|
35
35
|
|
|
36
36
|
worker.automation("sendWelcomeEmail", {
|
|
37
37
|
title: "Send Welcome Email",
|
|
38
38
|
description: "Runs from a database automation",
|
|
39
|
-
execute: async () => {},
|
|
39
|
+
execute: async (event, { notion }) => {},
|
|
40
40
|
});
|
|
41
41
|
|
|
42
42
|
worker.oauth("googleAuth", { name: "my-google-auth", provider: "google" });
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
+
- All `execute` handlers receive a Notion SDK client in the second argument as `context.notion`.
|
|
46
|
+
|
|
45
47
|
- For user-managed OAuth, supply `name`, `authorizationEndpoint`, `tokenEndpoint`, `clientId`, `clientSecret`, and `scope` (optional: `authorizationParams`, `callbackUrl`, `accessTokenExpireMs`).
|
|
46
48
|
|
|
47
49
|
### Sync
|
|
@@ -59,8 +61,8 @@ Syncs run in a "sync cycle": a back-to-back chain of `execute` calls that starts
|
|
|
59
61
|
- `incremental`: each sync cycle returns a subset of the full dataset (usually the changes since the last run). Deletions must be explicit via `{ type: "delete", key: "..." }`. Records not mentioned are left unchanged.
|
|
60
62
|
|
|
61
63
|
**How pagination works:**
|
|
62
|
-
1. Return a batch of changes with `hasMore: true` and a `
|
|
63
|
-
2. The runtime calls `execute` again with that
|
|
64
|
+
1. Return a batch of changes with `hasMore: true` and a `nextState` value
|
|
65
|
+
2. The runtime calls `execute` again with that state
|
|
64
66
|
3. Continue until you return `hasMore: false`
|
|
65
67
|
|
|
66
68
|
**Example replace sync:**
|
|
@@ -70,8 +72,8 @@ worker.sync("paginatedSync", {
|
|
|
70
72
|
mode: "replace",
|
|
71
73
|
primaryKeyProperty: "ID",
|
|
72
74
|
schema: { defaultName: "Records", properties: { Name: Schema.title(), ID: Schema.richText() } },
|
|
73
|
-
execute: async (
|
|
74
|
-
const page =
|
|
75
|
+
execute: async (state, { notion }) => {
|
|
76
|
+
const page = state?.page ?? 1;
|
|
75
77
|
const pageSize = 100;
|
|
76
78
|
const { items, hasMore } = await fetchPage(page, pageSize);
|
|
77
79
|
return {
|
|
@@ -81,13 +83,13 @@ worker.sync("paginatedSync", {
|
|
|
81
83
|
properties: { Name: Builder.title(item.name), ID: Builder.richText(item.id) },
|
|
82
84
|
})),
|
|
83
85
|
hasMore,
|
|
84
|
-
|
|
86
|
+
nextState: hasMore ? { page: page + 1 } : undefined,
|
|
85
87
|
};
|
|
86
88
|
},
|
|
87
89
|
});
|
|
88
90
|
```
|
|
89
91
|
|
|
90
|
-
**
|
|
92
|
+
**State types:** The `nextState` can be any serializable value—a cursor string, page number, timestamp, or complex object. Type your execute function's `state` to match.
|
|
91
93
|
|
|
92
94
|
**Incremental example (changes only, with deletes):**
|
|
93
95
|
```ts
|
|
@@ -95,8 +97,8 @@ worker.sync("incrementalSync", {
|
|
|
95
97
|
primaryKeyProperty: "ID",
|
|
96
98
|
mode: "incremental",
|
|
97
99
|
schema: { defaultName: "Records", properties: { Name: Schema.title(), ID: Schema.richText() } },
|
|
98
|
-
execute: async (
|
|
99
|
-
const { upserts, deletes, nextCursor } = await fetchChanges(
|
|
100
|
+
execute: async (state, { notion }) => {
|
|
101
|
+
const { upserts, deletes, nextCursor } = await fetchChanges(state?.cursor);
|
|
100
102
|
return {
|
|
101
103
|
changes: [
|
|
102
104
|
...upserts.map((item) => ({
|
|
@@ -107,7 +109,7 @@ worker.sync("incrementalSync", {
|
|
|
107
109
|
...deletes.map((id) => ({ type: "delete", key: id })),
|
|
108
110
|
],
|
|
109
111
|
hasMore: Boolean(nextCursor),
|
|
110
|
-
|
|
112
|
+
nextState: nextCursor ? { cursor: nextCursor } : undefined,
|
|
111
113
|
};
|
|
112
114
|
},
|
|
113
115
|
});
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# Repository Guidelines
|
|
2
|
+
|
|
3
|
+
## Project Structure & Module Organization
|
|
4
|
+
- `src/index.ts` defines the worker and capabilities.
|
|
5
|
+
- `.examples/` has focused samples (sync, tool, automation, OAuth).
|
|
6
|
+
- Generated: `dist/` build output, `workers.json` CLI config.
|
|
7
|
+
|
|
8
|
+
## Worker & Capability API (SDK)
|
|
9
|
+
- `@project-ajax/sdk` provides `Worker`, schema helpers, and builders; `@project-ajax/cli` powers `npx workers ...`.
|
|
10
|
+
- Capability keys are unique strings used by the CLI (e.g., `npx workers exec tasksSync`).
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import { Worker } from "@project-ajax/sdk";
|
|
14
|
+
import * as Builder from "@project-ajax/sdk/builder";
|
|
15
|
+
import * as Schema from "@project-ajax/sdk/schema";
|
|
16
|
+
|
|
17
|
+
const worker = new Worker();
|
|
18
|
+
export default worker;
|
|
19
|
+
|
|
20
|
+
worker.sync("tasksSync", {
|
|
21
|
+
primaryKeyProperty: "ID",
|
|
22
|
+
schema: { defaultName: "Tasks", properties: { Name: Schema.title(), ID: Schema.richText() } },
|
|
23
|
+
execute: async (_state, { notion }) => ({
|
|
24
|
+
changes: [{ type: "upsert", key: "1", properties: { Name: Builder.title("Write docs"), ID: Builder.richText("1") } }],
|
|
25
|
+
hasMore: false,
|
|
26
|
+
}),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
worker.tool("sayHello", {
|
|
30
|
+
title: "Say Hello",
|
|
31
|
+
description: "Return a greeting",
|
|
32
|
+
schema: { type: "object", properties: { name: { type: "string" } }, required: ["name"], additionalProperties: false },
|
|
33
|
+
execute: ({ name }, { notion }) => `Hello, ${name}`,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
worker.automation("sendWelcomeEmail", {
|
|
37
|
+
title: "Send Welcome Email",
|
|
38
|
+
description: "Runs from a database automation",
|
|
39
|
+
execute: async (event, { notion }) => {},
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
worker.oauth("googleAuth", { name: "my-google-auth", provider: "google" });
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
- All `execute` handlers receive a Notion SDK client in the second argument as `context.notion`.
|
|
46
|
+
|
|
47
|
+
- For user-managed OAuth, supply `name`, `authorizationEndpoint`, `tokenEndpoint`, `clientId`, `clientSecret`, and `scope` (optional: `authorizationParams`, `callbackUrl`, `accessTokenExpireMs`).
|
|
48
|
+
|
|
49
|
+
### Sync
|
|
50
|
+
#### Strategy and Pagination
|
|
51
|
+
|
|
52
|
+
Syncs run in a "sync cycle": a back-to-back chain of `execute` calls that starts at a scheduled trigger and ends when an execution returns `hasMore: false`.
|
|
53
|
+
|
|
54
|
+
- Always use pagination, when available. Returning too many changes in one execution will fail. Start with batch sizes of ~100 changes.
|
|
55
|
+
- `mode=replace` is simpler, and fine for smaller syncs (<10k)
|
|
56
|
+
- Use `mode=incremental` when the sync could return a lot of data (>10k), eg for SaaS tools like Salesforce or Stripe
|
|
57
|
+
- When using `mode=incremental`, emit delete markers as needed if easy to do (below)
|
|
58
|
+
|
|
59
|
+
**Sync strategy (`mode`):**
|
|
60
|
+
- `replace`: each sync cycle must return the full dataset. After the final `hasMore: false`, any records not seen during that cycle are deleted.
|
|
61
|
+
- `incremental`: each sync cycle returns a subset of the full dataset (usually the changes since the last run). Deletions must be explicit via `{ type: "delete", key: "..." }`. Records not mentioned are left unchanged.
|
|
62
|
+
|
|
63
|
+
**How pagination works:**
|
|
64
|
+
1. Return a batch of changes with `hasMore: true` and a `nextState` value
|
|
65
|
+
2. The runtime calls `execute` again with that state
|
|
66
|
+
3. Continue until you return `hasMore: false`
|
|
67
|
+
|
|
68
|
+
**Example replace sync:**
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
worker.sync("paginatedSync", {
|
|
72
|
+
mode: "replace",
|
|
73
|
+
primaryKeyProperty: "ID",
|
|
74
|
+
schema: { defaultName: "Records", properties: { Name: Schema.title(), ID: Schema.richText() } },
|
|
75
|
+
execute: async (state, { notion }) => {
|
|
76
|
+
const page = state?.page ?? 1;
|
|
77
|
+
const pageSize = 100;
|
|
78
|
+
const { items, hasMore } = await fetchPage(page, pageSize);
|
|
79
|
+
return {
|
|
80
|
+
changes: items.map((item) => ({
|
|
81
|
+
type: "upsert",
|
|
82
|
+
key: item.id,
|
|
83
|
+
properties: { Name: Builder.title(item.name), ID: Builder.richText(item.id) },
|
|
84
|
+
})),
|
|
85
|
+
hasMore,
|
|
86
|
+
nextState: hasMore ? { page: page + 1 } : undefined,
|
|
87
|
+
};
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**State types:** The `nextState` can be any serializable value—a cursor string, page number, timestamp, or complex object. Type your execute function's `state` to match.
|
|
93
|
+
|
|
94
|
+
**Incremental example (changes only, with deletes):**
|
|
95
|
+
```ts
|
|
96
|
+
worker.sync("incrementalSync", {
|
|
97
|
+
primaryKeyProperty: "ID",
|
|
98
|
+
mode: "incremental",
|
|
99
|
+
schema: { defaultName: "Records", properties: { Name: Schema.title(), ID: Schema.richText() } },
|
|
100
|
+
execute: async (state, { notion }) => {
|
|
101
|
+
const { upserts, deletes, nextCursor } = await fetchChanges(state?.cursor);
|
|
102
|
+
return {
|
|
103
|
+
changes: [
|
|
104
|
+
...upserts.map((item) => ({
|
|
105
|
+
type: "upsert",
|
|
106
|
+
key: item.id,
|
|
107
|
+
properties: { Name: Builder.title(item.name), ID: Builder.richText(item.id) },
|
|
108
|
+
})),
|
|
109
|
+
...deletes.map((id) => ({ type: "delete", key: id })),
|
|
110
|
+
],
|
|
111
|
+
hasMore: Boolean(nextCursor),
|
|
112
|
+
nextState: nextCursor ? { cursor: nextCursor } : undefined,
|
|
113
|
+
};
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
#### Relations
|
|
119
|
+
|
|
120
|
+
Two syncs can relate to one another using `Schema.relation(relatedSyncKey)` and `Builder.relation(primaryKey)` entries inside an array.
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
worker.sync("projectsSync", {
|
|
124
|
+
primaryKeyProperty: "Project ID",
|
|
125
|
+
...
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// Example sync worker that syncs sample tasks to a database
|
|
129
|
+
worker.sync("tasksSync", {
|
|
130
|
+
primaryKeyProperty: "Task ID",
|
|
131
|
+
...
|
|
132
|
+
schema: {
|
|
133
|
+
...
|
|
134
|
+
properties: {
|
|
135
|
+
...
|
|
136
|
+
Project: Schema.relation("projectsSync"),
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
execute: async () => {
|
|
141
|
+
// Return sample tasks as database entries
|
|
142
|
+
const tasks = fetchTasks()
|
|
143
|
+
const changes = tasks.map((task) => ({
|
|
144
|
+
type: "upsert" as const,
|
|
145
|
+
key: task.id,
|
|
146
|
+
properties: {
|
|
147
|
+
...
|
|
148
|
+
Project: [Builder.relation(task.projectId)],
|
|
149
|
+
},
|
|
150
|
+
}));
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
changes,
|
|
154
|
+
hasMore: false,
|
|
155
|
+
};
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Build, Test, and Development Commands
|
|
161
|
+
- Node >= 22 and npm >= 10.9.2 (see `package.json` engines).
|
|
162
|
+
- `npm run dev`: run `src/index.ts` with live reload.
|
|
163
|
+
- `npm run build`: compile TypeScript to `dist/`.
|
|
164
|
+
- `npm run check`: type-check only (no emit).
|
|
165
|
+
- `npx workers auth login [--env=dev]`: connect to a Notion workspace.
|
|
166
|
+
- `npx workers deploy`: build and publish capabilities.
|
|
167
|
+
- `npx workers exec <capability>`: run a sync or tool.
|
|
168
|
+
|
|
169
|
+
## Coding Style & Naming Conventions
|
|
170
|
+
- TypeScript with `strict` enabled; keep types explicit when shaping I/O.
|
|
171
|
+
- Use tabs for indentation; capability keys in lowerCamelCase.
|
|
172
|
+
|
|
173
|
+
## Testing Guidelines
|
|
174
|
+
- No test runner configured; validate with `npm run check` and a deploy/exec loop.
|
|
175
|
+
|
|
176
|
+
## Commit & Pull Request Guidelines
|
|
177
|
+
- Messages typically use `feat(scope): ...`, `TASK-123: ...`, or version bumps.
|
|
178
|
+
- PRs should describe changes, list commands run, and update examples if behavior changes.
|
package/template/README.md
CHANGED
|
@@ -73,7 +73,8 @@ worker.sync("tasksSync", {
|
|
|
73
73
|
ID: Schema.richText(),
|
|
74
74
|
},
|
|
75
75
|
},
|
|
76
|
-
execute: async () => ({
|
|
76
|
+
execute: async (_state, { notion }) => ({
|
|
77
|
+
// `notion` is the Notion API SDK client.
|
|
77
78
|
changes: [
|
|
78
79
|
{
|
|
79
80
|
type: "upsert",
|
|
@@ -95,15 +96,15 @@ Notion will delete stale rows after each run. A stale row is a row that was in t
|
|
|
95
96
|
|
|
96
97
|
When your sync is pulling in many rows of data (>1k), you'll want to use pagination. Breaking down pages to ~100 is a good starting point.
|
|
97
98
|
|
|
98
|
-
You can use
|
|
99
|
+
You can use `state` to persist things like pagination tokens between `execute` runs. Notion passes `state` as the first argument to `execute`, plus a `context` object that includes the Notion client at `context.notion`. Return `nextState` to set the `state` for the next run:
|
|
99
100
|
|
|
100
101
|
```ts
|
|
101
102
|
worker.sync("fullSync", {
|
|
102
103
|
primaryKeyProperty: "ID",
|
|
103
104
|
mode: "replace",
|
|
104
105
|
schema: { defaultName: "Records", properties: { Name: Schema.title(), ID: Schema.richText() } },
|
|
105
|
-
execute: async (
|
|
106
|
-
const { items , nextCursor } = await fetchPage(
|
|
106
|
+
execute: async (state, { notion }) => {
|
|
107
|
+
const { items , nextCursor } = await fetchPage(state?.page);
|
|
107
108
|
return {
|
|
108
109
|
changes: items.map((item) => ({
|
|
109
110
|
type: "upsert",
|
|
@@ -111,13 +112,13 @@ worker.sync("fullSync", {
|
|
|
111
112
|
properties: { Name: Builder.title(item.name), ID: Builder.richText(item.id) },
|
|
112
113
|
})),
|
|
113
114
|
hasMore: nextCursor ? true : false,
|
|
114
|
-
nextCursor
|
|
115
|
+
nextState: nextCursor ? { cursor: nextCursor } : undefined,
|
|
115
116
|
};
|
|
116
117
|
},
|
|
117
118
|
});
|
|
118
119
|
```
|
|
119
120
|
|
|
120
|
-
Return `hasMore=false` for each run until you reach the end. On the last run, return `hasMore=true`. At the start of the next cycle, Notion will start anew and call `execute` with `
|
|
121
|
+
Return `hasMore=false` for each run until you reach the end. On the last run, return `hasMore=true`. At the start of the next cycle, Notion will start anew and call `execute` with `state` undefined.
|
|
121
122
|
|
|
122
123
|
#### Write a sync that syncs incrementally
|
|
123
124
|
|
|
@@ -130,8 +131,8 @@ worker.sync("incrementalSync", {
|
|
|
130
131
|
primaryKeyProperty: "ID",
|
|
131
132
|
mode: "incremental",
|
|
132
133
|
schema: { defaultName: "Records", properties: { Name: Schema.title(), ID: Schema.richText() } },
|
|
133
|
-
execute: async (
|
|
134
|
-
const { upserts, deletes, nextCursor } = await fetchChanges(
|
|
134
|
+
execute: async (state, { notion }) => {
|
|
135
|
+
const { upserts, deletes, nextCursor } = await fetchChanges(state?.cursor);
|
|
135
136
|
return {
|
|
136
137
|
changes: [
|
|
137
138
|
...upserts.map((item) => ({
|
|
@@ -142,13 +143,13 @@ worker.sync("incrementalSync", {
|
|
|
142
143
|
...deletes.map((id) => ({ type: "delete", key: id })),
|
|
143
144
|
],
|
|
144
145
|
hasMore: Boolean(nextCursor),
|
|
145
|
-
|
|
146
|
+
nextState: nextCursor ? { cursor: nextCursor } : undefined,
|
|
146
147
|
};
|
|
147
148
|
},
|
|
148
149
|
});
|
|
149
150
|
```
|
|
150
151
|
|
|
151
|
-
Unlike the `replace` sync mode, Notion will not drop "stale" rows and `
|
|
152
|
+
Unlike the `replace` sync mode, Notion will not drop "stale" rows and `state` will persist between sync cycles.
|
|
152
153
|
|
|
153
154
|
**Deletes**
|
|
154
155
|
|
|
@@ -184,7 +185,7 @@ worker.tool("sayHello", {
|
|
|
184
185
|
required: ["name"],
|
|
185
186
|
additionalProperties: false,
|
|
186
187
|
},
|
|
187
|
-
execute: ({ name }) => `Hello, ${name}`,
|
|
188
|
+
execute: ({ name }, { notion }) => `Hello, ${name}`,
|
|
188
189
|
});
|
|
189
190
|
```
|
|
190
191
|
|
|
@@ -196,7 +197,8 @@ Automations run from Notion database buttons or automations.
|
|
|
196
197
|
worker.automation("sendWelcomeEmail", {
|
|
197
198
|
title: "Send Welcome Email",
|
|
198
199
|
description: "Runs from a database automation",
|
|
199
|
-
execute: async ({
|
|
200
|
+
execute: async (event, { notion }) => {
|
|
201
|
+
const { pageId } = event;
|
|
200
202
|
console.log("Triggered for page", pageId);
|
|
201
203
|
},
|
|
202
204
|
});
|
package/template/src/index.ts
CHANGED
|
@@ -93,7 +93,7 @@ worker.sync("tasksSync", {
|
|
|
93
93
|
},
|
|
94
94
|
},
|
|
95
95
|
|
|
96
|
-
execute: async () => {
|
|
96
|
+
execute: async (_state, { notion: _notion }) => {
|
|
97
97
|
const emojiForStatus = (status: string) => {
|
|
98
98
|
switch (status) {
|
|
99
99
|
case "Completed":
|
|
@@ -124,17 +124,33 @@ worker.sync("tasksSync", {
|
|
|
124
124
|
return {
|
|
125
125
|
// List of changes to apply to the Notion database.
|
|
126
126
|
changes,
|
|
127
|
-
// Indicates whether there is more data to fetch this sync cycle. If true, the runtime will call `execute` again with the
|
|
127
|
+
// Indicates whether there is more data to fetch this sync cycle. If true, the runtime will call `execute` again with the nextState.
|
|
128
128
|
hasMore: false,
|
|
129
|
-
// Optional
|
|
129
|
+
// Optional state data Notion will pass back as `state`
|
|
130
|
+
// in the next execution.
|
|
130
131
|
// This can be any type of data (cursor, page number, timestamp, etc.).
|
|
131
|
-
|
|
132
|
+
nextState: undefined,
|
|
132
133
|
};
|
|
133
134
|
},
|
|
134
135
|
});
|
|
135
136
|
|
|
137
|
+
type TaskSearchInput = {
|
|
138
|
+
taskId?: string | null;
|
|
139
|
+
query?: string | null;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
type TaskSearchOutput = {
|
|
143
|
+
count: number;
|
|
144
|
+
tasks: {
|
|
145
|
+
id: string;
|
|
146
|
+
title: string;
|
|
147
|
+
status: string;
|
|
148
|
+
description: string;
|
|
149
|
+
}[];
|
|
150
|
+
};
|
|
151
|
+
|
|
136
152
|
// Example agent tool for retrieving task information
|
|
137
|
-
worker.tool("taskSearchTool", {
|
|
153
|
+
worker.tool<TaskSearchInput, TaskSearchOutput>("taskSearchTool", {
|
|
138
154
|
title: "Task Search",
|
|
139
155
|
description:
|
|
140
156
|
"Look up sample tasks by ID or keyword. Helpful for demonstrating agent tool calls.",
|
|
@@ -156,7 +172,7 @@ worker.tool("taskSearchTool", {
|
|
|
156
172
|
required: [],
|
|
157
173
|
additionalProperties: false,
|
|
158
174
|
},
|
|
159
|
-
execute: async (input: {
|
|
175
|
+
execute: async (input: TaskSearchInput, { notion: _notion }) => {
|
|
160
176
|
const { taskId, query } = input;
|
|
161
177
|
|
|
162
178
|
let matchingTasks = sampleTasks;
|
|
@@ -198,7 +214,7 @@ worker.tool("taskSearchTool", {
|
|
|
198
214
|
status: task.status,
|
|
199
215
|
description: task.description,
|
|
200
216
|
})),
|
|
201
|
-
};
|
|
217
|
+
} satisfies TaskSearchOutput;
|
|
202
218
|
},
|
|
203
219
|
});
|
|
204
220
|
|
|
@@ -206,7 +222,8 @@ worker.tool("taskSearchTool", {
|
|
|
206
222
|
worker.automation("completeTaskAutomation", {
|
|
207
223
|
title: "Mark Task Complete",
|
|
208
224
|
description: "Automatically marks a task as complete when triggered",
|
|
209
|
-
execute: async ({
|
|
225
|
+
execute: async (event, { notion: _notion }) => {
|
|
226
|
+
const { pageId, actionType, pageData } = event;
|
|
210
227
|
// The pageData parameter contains the full page object from Notion's Public API
|
|
211
228
|
// with all the database properties already encoded and ready to use.
|
|
212
229
|
|