@project-ajax/create 0.0.39 → 0.0.40-alpha.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/package.json +3 -3
- package/template/.examples/automation-example.ts +6 -2
- package/template/.examples/oauth-example.ts +2 -0
- package/template/.examples/sync-example.ts +4 -0
- package/template/.examples/tool-example.ts +4 -0
- package/template/AGENTS.alpha.md +209 -0
- package/template/AGENTS.md +29 -132
- package/template/CLAUDE.md +29 -132
- package/template/src/index.ts +10 -243
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@project-ajax/create",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.40-alpha.0",
|
|
4
4
|
"description": "Initialize a new Notion Workers extensions repo.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"create-ajax": "dist/index.js"
|
|
@@ -31,10 +31,10 @@
|
|
|
31
31
|
"template/.gitignore"
|
|
32
32
|
],
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@inquirer/prompts": "^8.0.1"
|
|
35
|
-
"@project-ajax/shared": "*"
|
|
34
|
+
"@inquirer/prompts": "^8.0.1"
|
|
36
35
|
},
|
|
37
36
|
"devDependencies": {
|
|
37
|
+
"@project-ajax/shared": "*",
|
|
38
38
|
"@types/node": "^22.19.0",
|
|
39
39
|
"tsup": "^8.5.0",
|
|
40
40
|
"typescript": "^5.9.3",
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Automations are only available in a private alpha.
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
import { Worker } from "@project-ajax/sdk";
|
|
2
6
|
|
|
3
7
|
const worker = new Worker();
|
|
@@ -33,8 +37,8 @@ worker.automation("questionAnswerAutomation", {
|
|
|
33
37
|
emailValue = emailProperty.rich_text.map((rt) => rt.plain_text).join("");
|
|
34
38
|
}
|
|
35
39
|
|
|
36
|
-
// Handle empty email
|
|
37
|
-
if (!emailValue) {
|
|
40
|
+
// Handle empty email or pageId
|
|
41
|
+
if (!emailValue || !pageId) {
|
|
38
42
|
return;
|
|
39
43
|
}
|
|
40
44
|
|
|
@@ -17,6 +17,7 @@ export default worker;
|
|
|
17
17
|
|
|
18
18
|
// Option 1: Notion-managed provider (recommended when available).
|
|
19
19
|
// Notion owns the OAuth app credentials and the backend has pre-configured provider settings.
|
|
20
|
+
// Notion-managed providers are only available in a private alpha.
|
|
20
21
|
const googleAuth = worker.oauth("googleAuth", {
|
|
21
22
|
name: "google-calendar",
|
|
22
23
|
provider: "google",
|
|
@@ -24,6 +25,7 @@ const googleAuth = worker.oauth("googleAuth", {
|
|
|
24
25
|
|
|
25
26
|
// Option 2: User-managed provider (you own the OAuth app credentials).
|
|
26
27
|
// Keep client credentials in worker secrets and read them from `process.env`.
|
|
28
|
+
// Generally available.
|
|
27
29
|
const myCustomAuth = worker.oauth("myCustomAuth", {
|
|
28
30
|
name: "my-custom-provider",
|
|
29
31
|
authorizationEndpoint: "https://provider.example.com/oauth/authorize",
|
|
@@ -0,0 +1,209 @@
|
|
|
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`. By default, syncs run every 30 minutes. Set `schedule` to an interval like `"15m"`, `"1h"`, `"1d"` (min `"1m"`, max `"7d"`), or `"continuous"` to run as fast as possible.
|
|
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
|
+
// Optionally configure a two-way relation. This will automatically create the
|
|
138
|
+
// "Tasks" property on the project synced database: there is no need
|
|
139
|
+
// to configure "Tasks" on the projectSync capability.
|
|
140
|
+
twoWay: true, relatedPropertyName: "Tasks"
|
|
141
|
+
}),
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
execute: async () => {
|
|
146
|
+
// Return sample tasks as database entries
|
|
147
|
+
const tasks = fetchTasks()
|
|
148
|
+
const changes = tasks.map((task) => ({
|
|
149
|
+
type: "upsert" as const,
|
|
150
|
+
key: task.id,
|
|
151
|
+
properties: {
|
|
152
|
+
...
|
|
153
|
+
Project: [Builder.relation(task.projectId)],
|
|
154
|
+
},
|
|
155
|
+
}));
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
changes,
|
|
159
|
+
hasMore: false,
|
|
160
|
+
};
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Build, Test, and Development Commands
|
|
166
|
+
- Node >= 22 and npm >= 10.9.2 (see `package.json` engines).
|
|
167
|
+
- `npm run dev`: run `src/index.ts` with live reload.
|
|
168
|
+
- `npm run build`: compile TypeScript to `dist/`.
|
|
169
|
+
- `npm run check`: type-check only (no emit).
|
|
170
|
+
- `npx workers auth login [--env=dev]`: connect to a Notion workspace.
|
|
171
|
+
- `npx workers deploy`: build and publish capabilities.
|
|
172
|
+
- `npx workers exec <capability>`: run a sync or tool.
|
|
173
|
+
- `npx workers pack`: create a tarball; uses Git when available (respects `.gitignore`), always skips `node_modules`, `dist`, `workers.json`, `workers.*.json`, `.env`, and `.env.*`, and works in non-git repos.
|
|
174
|
+
|
|
175
|
+
## Debugging & Monitoring Runs
|
|
176
|
+
Use `npx workers runs` to inspect run history and logs.
|
|
177
|
+
|
|
178
|
+
**List recent runs:**
|
|
179
|
+
```shell
|
|
180
|
+
npx workers runs list
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
**Get logs for a specific run:**
|
|
184
|
+
```shell
|
|
185
|
+
npx workers runs logs <runId>
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
**Get logs for the latest run (any capability):**
|
|
189
|
+
```shell
|
|
190
|
+
npx workers runs list --plain | head -n1 | cut -f1 | xargs npx workers runs logs
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
**Get logs for the latest run of a specific capability:**
|
|
194
|
+
```shell
|
|
195
|
+
npx workers runs list --plain | grep tasksSync | head -n1 | cut -f1 | xargs npx workers runs logs
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
The `--plain` flag outputs tab-separated values without formatting, making it easy to pipe to other commands.
|
|
199
|
+
|
|
200
|
+
## Coding Style & Naming Conventions
|
|
201
|
+
- TypeScript with `strict` enabled; keep types explicit when shaping I/O.
|
|
202
|
+
- Use tabs for indentation; capability keys in lowerCamelCase.
|
|
203
|
+
|
|
204
|
+
## Testing Guidelines
|
|
205
|
+
- No test runner configured; validate with `npm run check` and a deploy/exec loop.
|
|
206
|
+
|
|
207
|
+
## Commit & Pull Request Guidelines
|
|
208
|
+
- Messages typically use `feat(scope): ...`, `TASK-123: ...`, or version bumps.
|
|
209
|
+
- PRs should describe changes, list commands run, and update examples if behavior changes.
|
package/template/AGENTS.md
CHANGED
|
@@ -6,161 +6,58 @@
|
|
|
6
6
|
- Generated: `dist/` build output, `workers.json` CLI config.
|
|
7
7
|
|
|
8
8
|
## Worker & Capability API (SDK)
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
`@project-ajax/sdk` provides `Worker`, schema helpers, and builders; `@project-ajax/cli` powers `npx workers ...`.
|
|
10
|
+
|
|
11
|
+
### Agent tool calls
|
|
11
12
|
|
|
12
13
|
```ts
|
|
13
14
|
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
15
|
|
|
17
16
|
const worker = new Worker();
|
|
18
17
|
export default worker;
|
|
19
18
|
|
|
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
19
|
worker.tool("sayHello", {
|
|
30
20
|
title: "Say Hello",
|
|
31
21
|
description: "Return a greeting",
|
|
32
22
|
schema: { type: "object", properties: { name: { type: "string" } }, required: ["name"], additionalProperties: false },
|
|
33
|
-
execute: ({ name },
|
|
23
|
+
execute: ({ name }, _context) => `Hello, ${name}`,
|
|
34
24
|
});
|
|
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
25
|
```
|
|
44
26
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
-
|
|
27
|
+
A worker with one or more tools is attachable to Notion agents. Each `tool` becomes a callable function for the agent:
|
|
28
|
+
- `title` and `description` are used both in the Notion UI as well as a helpful description to your agent.
|
|
29
|
+
- `schema` specifies what data the agent must supply.
|
|
48
30
|
|
|
49
|
-
###
|
|
50
|
-
#### Strategy and Pagination
|
|
31
|
+
### OAuth
|
|
51
32
|
|
|
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`. By default, syncs run every 30 minutes. Set `schedule` to an interval like `"15m"`, `"1h"`, `"1d"` (min `"1m"`, max `"7d"`), or `"continuous"` to run as fast as possible.
|
|
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
33
|
```
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
};
|
|
34
|
+
const myOAuth = worker.oauth("myOAuth", {
|
|
35
|
+
name: "my-provider",
|
|
36
|
+
authorizationEndpoint: "https://provider.example.com/oauth/authorize",
|
|
37
|
+
tokenEndpoint: "https://provider.example.com/oauth/token",
|
|
38
|
+
scope: "read write",
|
|
39
|
+
clientId: "1234567890",
|
|
40
|
+
clientSecret: process.env.MY_CUSTOM_OAUTH_CLIENT_SECRET ?? "",
|
|
41
|
+
authorizationParams: {
|
|
42
|
+
access_type: "offline",
|
|
43
|
+
prompt: "consent",
|
|
114
44
|
},
|
|
115
45
|
});
|
|
116
46
|
```
|
|
117
47
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
Two syncs can relate to one another using `Schema.relation(relatedSyncKey)` and `Builder.relation(primaryKey)` entries inside an array.
|
|
48
|
+
The OAuth capability allows you to perform the three legged OAuth flow after specifying paramteres of your OAuth client: `name`, `authorizationEndpoint`, `tokenEndpoint`, `clientId`, `clientSecret`, and `scope` (optional: `authorizationParams`, `callbackUrl`, `accessTokenExpireMs`).
|
|
121
49
|
|
|
122
|
-
|
|
123
|
-
worker.sync("projectsSync", {
|
|
124
|
-
primaryKeyProperty: "Project ID",
|
|
125
|
-
...
|
|
126
|
-
});
|
|
50
|
+
### Other capabilities
|
|
127
51
|
|
|
128
|
-
|
|
129
|
-
worker.sync("tasksSync", {
|
|
130
|
-
primaryKeyProperty: "Task ID",
|
|
131
|
-
...
|
|
132
|
-
schema: {
|
|
133
|
-
...
|
|
134
|
-
properties: {
|
|
135
|
-
...
|
|
136
|
-
Project: Schema.relation("projectsSync", {
|
|
137
|
-
// Optionally configure a two-way relation. This will automatically create the
|
|
138
|
-
// "Tasks" property on the project synced database: there is no need
|
|
139
|
-
// to configure "Tasks" on the projectSync capability.
|
|
140
|
-
twoWay: true, relatedPropertyName: "Tasks"
|
|
141
|
-
}),
|
|
142
|
-
},
|
|
143
|
-
},
|
|
52
|
+
There are additional capability types in the SDK but these are restricted to a private alpha. Only Agent tools and OAuth are generally available.
|
|
144
53
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
...
|
|
153
|
-
Project: [Builder.relation(task.projectId)],
|
|
154
|
-
},
|
|
155
|
-
}));
|
|
156
|
-
|
|
157
|
-
return {
|
|
158
|
-
changes,
|
|
159
|
-
hasMore: false,
|
|
160
|
-
};
|
|
161
|
-
},
|
|
162
|
-
});
|
|
163
|
-
```
|
|
54
|
+
| Capability | Availability |
|
|
55
|
+
|------------|--------------|
|
|
56
|
+
| Agent tools | Generally available |
|
|
57
|
+
| OAuth (user-managed) | Generally available |
|
|
58
|
+
| OAuth (Notion-managed) | Private alpha |
|
|
59
|
+
| Syncs | Private alpha |
|
|
60
|
+
| Automations | Private alpha |
|
|
164
61
|
|
|
165
62
|
## Build, Test, and Development Commands
|
|
166
63
|
- Node >= 22 and npm >= 10.9.2 (see `package.json` engines).
|
|
@@ -169,7 +66,7 @@ worker.sync("tasksSync", {
|
|
|
169
66
|
- `npm run check`: type-check only (no emit).
|
|
170
67
|
- `npx workers auth login [--env=dev]`: connect to a Notion workspace.
|
|
171
68
|
- `npx workers deploy`: build and publish capabilities.
|
|
172
|
-
- `npx workers exec <capability>`: run a sync or tool.
|
|
69
|
+
- `npx workers exec <capability>`: run a sync or tool. Run after `deploy`.
|
|
173
70
|
- `npx workers pack`: create a tarball; uses Git when available (respects `.gitignore`), always skips `node_modules`, `dist`, `workers.json`, `workers.*.json`, `.env`, and `.env.*`, and works in non-git repos.
|
|
174
71
|
|
|
175
72
|
## Debugging & Monitoring Runs
|
package/template/CLAUDE.md
CHANGED
|
@@ -6,161 +6,58 @@
|
|
|
6
6
|
- Generated: `dist/` build output, `workers.json` CLI config.
|
|
7
7
|
|
|
8
8
|
## Worker & Capability API (SDK)
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
`@project-ajax/sdk` provides `Worker`, schema helpers, and builders; `@project-ajax/cli` powers `npx workers ...`.
|
|
10
|
+
|
|
11
|
+
### Agent tool calls
|
|
11
12
|
|
|
12
13
|
```ts
|
|
13
14
|
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
15
|
|
|
17
16
|
const worker = new Worker();
|
|
18
17
|
export default worker;
|
|
19
18
|
|
|
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
19
|
worker.tool("sayHello", {
|
|
30
20
|
title: "Say Hello",
|
|
31
21
|
description: "Return a greeting",
|
|
32
22
|
schema: { type: "object", properties: { name: { type: "string" } }, required: ["name"], additionalProperties: false },
|
|
33
|
-
execute: ({ name },
|
|
23
|
+
execute: ({ name }, _context) => `Hello, ${name}`,
|
|
34
24
|
});
|
|
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
25
|
```
|
|
44
26
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
-
|
|
27
|
+
A worker with one or more tools is attachable to Notion agents. Each `tool` becomes a callable function for the agent:
|
|
28
|
+
- `title` and `description` are used both in the Notion UI as well as a helpful description to your agent.
|
|
29
|
+
- `schema` specifies what data the agent must supply.
|
|
48
30
|
|
|
49
|
-
###
|
|
50
|
-
#### Strategy and Pagination
|
|
31
|
+
### OAuth
|
|
51
32
|
|
|
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`. By default, syncs run every 30 minutes. Set `schedule` to an interval like `"15m"`, `"1h"`, `"1d"` (min `"1m"`, max `"7d"`), or `"continuous"` to run as fast as possible.
|
|
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
33
|
```
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
};
|
|
34
|
+
const myOAuth = worker.oauth("myOAuth", {
|
|
35
|
+
name: "my-provider",
|
|
36
|
+
authorizationEndpoint: "https://provider.example.com/oauth/authorize",
|
|
37
|
+
tokenEndpoint: "https://provider.example.com/oauth/token",
|
|
38
|
+
scope: "read write",
|
|
39
|
+
clientId: "1234567890",
|
|
40
|
+
clientSecret: process.env.MY_CUSTOM_OAUTH_CLIENT_SECRET ?? "",
|
|
41
|
+
authorizationParams: {
|
|
42
|
+
access_type: "offline",
|
|
43
|
+
prompt: "consent",
|
|
114
44
|
},
|
|
115
45
|
});
|
|
116
46
|
```
|
|
117
47
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
Two syncs can relate to one another using `Schema.relation(relatedSyncKey)` and `Builder.relation(primaryKey)` entries inside an array.
|
|
48
|
+
The OAuth capability allows you to perform the three legged OAuth flow after specifying paramteres of your OAuth client: `name`, `authorizationEndpoint`, `tokenEndpoint`, `clientId`, `clientSecret`, and `scope` (optional: `authorizationParams`, `callbackUrl`, `accessTokenExpireMs`).
|
|
121
49
|
|
|
122
|
-
|
|
123
|
-
worker.sync("projectsSync", {
|
|
124
|
-
primaryKeyProperty: "Project ID",
|
|
125
|
-
...
|
|
126
|
-
});
|
|
50
|
+
### Other capabilities
|
|
127
51
|
|
|
128
|
-
|
|
129
|
-
worker.sync("tasksSync", {
|
|
130
|
-
primaryKeyProperty: "Task ID",
|
|
131
|
-
...
|
|
132
|
-
schema: {
|
|
133
|
-
...
|
|
134
|
-
properties: {
|
|
135
|
-
...
|
|
136
|
-
Project: Schema.relation("projectsSync", {
|
|
137
|
-
// Optionally configure a two-way relation. This will automatically create the
|
|
138
|
-
// "Tasks" property on the project synced database: there is no need
|
|
139
|
-
// to configure "Tasks" on the projectSync capability.
|
|
140
|
-
twoWay: true, relatedPropertyName: "Tasks"
|
|
141
|
-
}),
|
|
142
|
-
},
|
|
143
|
-
},
|
|
52
|
+
There are additional capability types in the SDK but these are restricted to a private alpha. Only Agent tools and OAuth are generally available.
|
|
144
53
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
...
|
|
153
|
-
Project: [Builder.relation(task.projectId)],
|
|
154
|
-
},
|
|
155
|
-
}));
|
|
156
|
-
|
|
157
|
-
return {
|
|
158
|
-
changes,
|
|
159
|
-
hasMore: false,
|
|
160
|
-
};
|
|
161
|
-
},
|
|
162
|
-
});
|
|
163
|
-
```
|
|
54
|
+
| Capability | Availability |
|
|
55
|
+
|------------|--------------|
|
|
56
|
+
| Agent tools | Generally available |
|
|
57
|
+
| OAuth (user-managed) | Generally available |
|
|
58
|
+
| OAuth (Notion-managed) | Private alpha |
|
|
59
|
+
| Syncs | Private alpha |
|
|
60
|
+
| Automations | Private alpha |
|
|
164
61
|
|
|
165
62
|
## Build, Test, and Development Commands
|
|
166
63
|
- Node >= 22 and npm >= 10.9.2 (see `package.json` engines).
|
|
@@ -169,7 +66,7 @@ worker.sync("tasksSync", {
|
|
|
169
66
|
- `npm run check`: type-check only (no emit).
|
|
170
67
|
- `npx workers auth login [--env=dev]`: connect to a Notion workspace.
|
|
171
68
|
- `npx workers deploy`: build and publish capabilities.
|
|
172
|
-
- `npx workers exec <capability>`: run a sync or tool.
|
|
69
|
+
- `npx workers exec <capability>`: run a sync or tool. Run after `deploy`.
|
|
173
70
|
- `npx workers pack`: create a tarball; uses Git when available (respects `.gitignore`), always skips `node_modules`, `dist`, `workers.json`, `workers.*.json`, `.env`, and `.env.*`, and works in non-git repos.
|
|
174
71
|
|
|
175
72
|
## Debugging & Monitoring Runs
|
package/template/src/index.ts
CHANGED
|
@@ -1,258 +1,25 @@
|
|
|
1
1
|
import { Worker } from "@project-ajax/sdk";
|
|
2
|
-
import * as Builder from "@project-ajax/sdk/builder";
|
|
3
|
-
import * as Schema from "@project-ajax/sdk/schema";
|
|
4
2
|
|
|
5
3
|
const worker = new Worker();
|
|
6
4
|
export default worker;
|
|
7
5
|
|
|
8
|
-
|
|
9
|
-
const projectName = "Project 1";
|
|
6
|
+
type HelloInput = { name: string };
|
|
10
7
|
|
|
11
|
-
//
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
status: "Completed",
|
|
17
|
-
description: "This is a simple hello world example",
|
|
18
|
-
projectId,
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
id: "task-2",
|
|
22
|
-
title: "Build your first worker",
|
|
23
|
-
status: "In Progress",
|
|
24
|
-
description: "Create a sync or tool worker",
|
|
25
|
-
projectId,
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
id: "task-3",
|
|
29
|
-
title: "Deploy to production",
|
|
30
|
-
status: "Todo",
|
|
31
|
-
description: "Share your worker with your team",
|
|
32
|
-
projectId,
|
|
33
|
-
},
|
|
34
|
-
];
|
|
35
|
-
|
|
36
|
-
worker.sync("projectsSync", {
|
|
37
|
-
primaryKeyProperty: "Project ID",
|
|
38
|
-
schema: {
|
|
39
|
-
defaultName: "Projects",
|
|
40
|
-
databaseIcon: Builder.notionIcon("activity"),
|
|
41
|
-
properties: {
|
|
42
|
-
"Project Name": Schema.title(),
|
|
43
|
-
"Project ID": Schema.richText(),
|
|
44
|
-
},
|
|
45
|
-
},
|
|
46
|
-
execute: async () => {
|
|
47
|
-
return {
|
|
48
|
-
changes: [
|
|
49
|
-
{
|
|
50
|
-
type: "upsert" as const,
|
|
51
|
-
key: projectId,
|
|
52
|
-
icon: Builder.notionIcon("activity"),
|
|
53
|
-
properties: {
|
|
54
|
-
"Project Name": Builder.title(projectName),
|
|
55
|
-
"Project ID": Builder.richText(projectId),
|
|
56
|
-
},
|
|
57
|
-
},
|
|
58
|
-
],
|
|
59
|
-
hasMore: false,
|
|
60
|
-
};
|
|
61
|
-
},
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
// Example sync worker that syncs sample tasks to a database
|
|
65
|
-
worker.sync("tasksSync", {
|
|
66
|
-
primaryKeyProperty: "Task ID",
|
|
67
|
-
|
|
68
|
-
// Optional: How often the sync should run. Defaults to "continuous".
|
|
69
|
-
// Use intervals like "30m", "1h", "1d" (min: 1m, max: 7d)
|
|
70
|
-
schedule: "5m",
|
|
71
|
-
|
|
72
|
-
// Sync mode:
|
|
73
|
-
// - "replace": Each sync cycle returns the complete dataset. After hasMore:false,
|
|
74
|
-
// pages not seen in this sync run are deleted.
|
|
75
|
-
// - "incremental": Each sync cycle returns a subset of the complete dataset. Use delete markers
|
|
76
|
-
// (e.g., { type: "delete", key: "task-1" }) to remove pages.
|
|
77
|
-
// Defaults to "replace".
|
|
78
|
-
// mode: "replace",
|
|
79
|
-
|
|
80
|
-
schema: {
|
|
81
|
-
defaultName: "Sample Tasks",
|
|
82
|
-
databaseIcon: Builder.notionIcon("checklist"),
|
|
83
|
-
properties: {
|
|
84
|
-
"Ticket Title": Schema.title(),
|
|
85
|
-
"Task ID": Schema.richText(),
|
|
86
|
-
Description: Schema.richText(),
|
|
87
|
-
Status: Schema.select([
|
|
88
|
-
{ name: "Completed", color: "green" },
|
|
89
|
-
{ name: "In Progress", color: "blue" },
|
|
90
|
-
{ name: "Todo", color: "default" },
|
|
91
|
-
]),
|
|
92
|
-
Project: Schema.relation("projectsSync", {
|
|
93
|
-
twoWay: true,
|
|
94
|
-
relatedPropertyName: "Tasks",
|
|
95
|
-
}),
|
|
96
|
-
},
|
|
97
|
-
},
|
|
98
|
-
|
|
99
|
-
execute: async (_state, { notion: _notion }) => {
|
|
100
|
-
const emojiForStatus = (status: string) => {
|
|
101
|
-
switch (status) {
|
|
102
|
-
case "Completed":
|
|
103
|
-
return Builder.notionIcon("checkmark", "green");
|
|
104
|
-
case "In Progress":
|
|
105
|
-
return Builder.notionIcon("arrow-right", "blue");
|
|
106
|
-
case "Todo":
|
|
107
|
-
return Builder.notionIcon("clock", "lightgray");
|
|
108
|
-
default:
|
|
109
|
-
return Builder.notionIcon("question-mark", "lightgray");
|
|
110
|
-
}
|
|
111
|
-
};
|
|
112
|
-
// Return sample tasks as database entries
|
|
113
|
-
const changes = sampleTasks.map((task) => ({
|
|
114
|
-
type: "upsert" as const,
|
|
115
|
-
key: task.id,
|
|
116
|
-
icon: emojiForStatus(task.status),
|
|
117
|
-
properties: {
|
|
118
|
-
"Ticket Title": Builder.title(task.title),
|
|
119
|
-
"Task ID": Builder.richText(task.id),
|
|
120
|
-
Description: Builder.richText(task.description),
|
|
121
|
-
Status: Builder.select(task.status),
|
|
122
|
-
Project: [Builder.relation(projectId)],
|
|
123
|
-
},
|
|
124
|
-
pageContentMarkdown: `## ${task.title}\n\n${task.description}`,
|
|
125
|
-
}));
|
|
126
|
-
|
|
127
|
-
return {
|
|
128
|
-
// List of changes to apply to the Notion database.
|
|
129
|
-
changes,
|
|
130
|
-
// Indicates whether there is more data to fetch this sync cycle. If true, the runtime will call `execute` again with the nextState.
|
|
131
|
-
hasMore: false,
|
|
132
|
-
// Optional state data Notion will pass back as `state`
|
|
133
|
-
// in the next execution.
|
|
134
|
-
// This can be any type of data (cursor, page number, timestamp, etc.).
|
|
135
|
-
nextState: undefined,
|
|
136
|
-
};
|
|
137
|
-
},
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
type TaskSearchInput = {
|
|
141
|
-
taskId?: string | null;
|
|
142
|
-
query?: string | null;
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
type TaskSearchOutput = {
|
|
146
|
-
count: number;
|
|
147
|
-
tasks: {
|
|
148
|
-
id: string;
|
|
149
|
-
title: string;
|
|
150
|
-
status: string;
|
|
151
|
-
description: string;
|
|
152
|
-
}[];
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
// Example agent tool for retrieving task information
|
|
156
|
-
worker.tool<TaskSearchInput, TaskSearchOutput>("taskSearchTool", {
|
|
157
|
-
title: "Task Search",
|
|
158
|
-
description:
|
|
159
|
-
"Look up sample tasks by ID or keyword. Helpful for demonstrating agent tool calls.",
|
|
8
|
+
// Example agent tool that returns a greeting
|
|
9
|
+
// Delete this when you're ready to start building your own tools.
|
|
10
|
+
worker.tool<HelloInput, string>("sayHello", {
|
|
11
|
+
title: "Say Hello",
|
|
12
|
+
description: "Returns a friendly greeting for the given name.",
|
|
160
13
|
schema: {
|
|
161
14
|
type: "object",
|
|
162
15
|
properties: {
|
|
163
|
-
|
|
16
|
+
name: {
|
|
164
17
|
type: "string",
|
|
165
|
-
|
|
166
|
-
description: "Return a single task that matches the given task ID.",
|
|
167
|
-
},
|
|
168
|
-
query: {
|
|
169
|
-
type: "string",
|
|
170
|
-
nullable: true,
|
|
171
|
-
description:
|
|
172
|
-
"Match search terms against words in the task title or description.",
|
|
18
|
+
description: "The name to greet.",
|
|
173
19
|
},
|
|
174
20
|
},
|
|
175
|
-
required: [],
|
|
21
|
+
required: ["name"],
|
|
176
22
|
additionalProperties: false,
|
|
177
23
|
},
|
|
178
|
-
execute:
|
|
179
|
-
const { taskId, query } = input;
|
|
180
|
-
|
|
181
|
-
let matchingTasks = sampleTasks;
|
|
182
|
-
|
|
183
|
-
if (taskId) {
|
|
184
|
-
matchingTasks = sampleTasks.filter((task) => task.id === taskId);
|
|
185
|
-
} else if (query) {
|
|
186
|
-
const normalizedQuery = query.trim().toLowerCase();
|
|
187
|
-
|
|
188
|
-
const terms = normalizedQuery.split(/\s+/).filter(Boolean);
|
|
189
|
-
|
|
190
|
-
if (terms.length > 0) {
|
|
191
|
-
const scoredTasks = sampleTasks
|
|
192
|
-
.map((task) => {
|
|
193
|
-
const title = task.title.toLowerCase();
|
|
194
|
-
const description = task.description.toLowerCase();
|
|
195
|
-
const matches = terms.reduce((count, term) => {
|
|
196
|
-
return title.includes(term) || description.includes(term)
|
|
197
|
-
? count + 1
|
|
198
|
-
: count;
|
|
199
|
-
}, 0);
|
|
200
|
-
|
|
201
|
-
return { task, matches };
|
|
202
|
-
})
|
|
203
|
-
.filter(({ matches }) => matches > 0)
|
|
204
|
-
.sort((a, b) => b.matches - a.matches);
|
|
205
|
-
|
|
206
|
-
matchingTasks = scoredTasks.map(({ task }) => task);
|
|
207
|
-
} else {
|
|
208
|
-
matchingTasks = [];
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
return {
|
|
213
|
-
count: matchingTasks.length,
|
|
214
|
-
tasks: matchingTasks.map((task) => ({
|
|
215
|
-
id: task.id,
|
|
216
|
-
title: task.title,
|
|
217
|
-
status: task.status,
|
|
218
|
-
description: task.description,
|
|
219
|
-
})),
|
|
220
|
-
} satisfies TaskSearchOutput;
|
|
221
|
-
},
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
// Example automation that runs when triggered from a database automation
|
|
225
|
-
worker.automation("completeTaskAutomation", {
|
|
226
|
-
title: "Mark Task Complete",
|
|
227
|
-
description: "Automatically marks a task as complete when triggered",
|
|
228
|
-
execute: async (event, { notion: _notion }) => {
|
|
229
|
-
const { pageId, actionType, pageData } = event;
|
|
230
|
-
// The pageData parameter contains the full page object from Notion's Public API
|
|
231
|
-
// with all the database properties already encoded and ready to use.
|
|
232
|
-
|
|
233
|
-
console.log(`Automation triggered for page: ${pageId}`);
|
|
234
|
-
console.log(`Action type: ${actionType}`);
|
|
235
|
-
|
|
236
|
-
if (pageData) {
|
|
237
|
-
// Access all page properties directly
|
|
238
|
-
console.log("Page properties:", pageData.properties);
|
|
239
|
-
|
|
240
|
-
// Example: Access specific properties by their name
|
|
241
|
-
// const taskName = pageData.properties.Name;
|
|
242
|
-
// const status = pageData.properties.Status;
|
|
243
|
-
// const assignee = pageData.properties.Assignee;
|
|
244
|
-
|
|
245
|
-
// The properties are in Notion's Public API format
|
|
246
|
-
// See: https://developers.notion.com/reference/property-value-object
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// In a real implementation, you would:
|
|
250
|
-
// 1. Use the page properties to determine what action to take
|
|
251
|
-
// 2. Update the task status in your system
|
|
252
|
-
// 3. Call external APIs, send notifications, etc.
|
|
253
|
-
|
|
254
|
-
// Example: You could call an external API, update a database, send notifications, etc.
|
|
255
|
-
// For this demo, we just log the execution
|
|
256
|
-
console.log("Task marked as complete!");
|
|
257
|
-
},
|
|
24
|
+
execute: ({ name }) => `Hello, ${name}!`,
|
|
258
25
|
});
|