@project-ajax/create 0.0.38 → 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/dist/index.js +331 -57
- package/package.json +6 -4
- 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
|
@@ -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
|