@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.
@@ -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.
@@ -6,161 +6,58 @@
6
6
  - Generated: `dist/` build output, `workers.json` CLI config.
7
7
 
8
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`).
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 }, { notion }) => `Hello, ${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
- - 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`).
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
- ### Sync
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
- **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
- };
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
- #### Relations
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
- ```ts
123
- worker.sync("projectsSync", {
124
- primaryKeyProperty: "Project ID",
125
- ...
126
- });
50
+ ### Other capabilities
127
51
 
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
- },
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
- 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
- ```
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
@@ -6,161 +6,58 @@
6
6
  - Generated: `dist/` build output, `workers.json` CLI config.
7
7
 
8
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`).
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 }, { notion }) => `Hello, ${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
- - 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`).
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
- ### Sync
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
- **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
- };
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
- #### Relations
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
- ```ts
123
- worker.sync("projectsSync", {
124
- primaryKeyProperty: "Project ID",
125
- ...
126
- });
50
+ ### Other capabilities
127
51
 
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
- },
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
- 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
- ```
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