@project-ajax/create 0.0.35 → 0.0.36
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 +222 -65
- package/package.json +5 -3
- package/template/AGENTS.md +60 -55
- package/template/CLAUDE.md +60 -55
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// src/
|
|
3
|
+
// src/run-create.ts
|
|
4
|
+
import { execSync } from "child_process";
|
|
4
5
|
import fs from "fs";
|
|
5
6
|
import path from "path";
|
|
6
7
|
import { fileURLToPath } from "url";
|
|
@@ -10,70 +11,193 @@ import chalk from "chalk";
|
|
|
10
11
|
import ora from "ora";
|
|
11
12
|
var __filename = fileURLToPath(import.meta.url);
|
|
12
13
|
var __dirname = path.dirname(__filename);
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
async function runCreate() {
|
|
15
|
+
try {
|
|
16
|
+
await run();
|
|
17
|
+
process.exit(0);
|
|
18
|
+
} catch (err) {
|
|
19
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
20
|
+
console.error(chalk.red(`
|
|
21
|
+
${message}`));
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
20
25
|
async function run() {
|
|
21
26
|
console.log(chalk.bold.cyan("\n\u{1F680} Create a new worker\n"));
|
|
22
27
|
const { values } = parseArgs({
|
|
23
28
|
options: {
|
|
24
|
-
directory: {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
29
|
+
directory: { type: "string", short: "d" },
|
|
30
|
+
yes: { type: "boolean", short: "y" },
|
|
31
|
+
git: { type: "boolean" },
|
|
32
|
+
"no-git": { type: "boolean" },
|
|
33
|
+
bun: { type: "boolean" },
|
|
34
|
+
install: { type: "boolean" },
|
|
35
|
+
"no-install": { type: "boolean" },
|
|
36
|
+
force: { type: "boolean", short: "f" }
|
|
28
37
|
}
|
|
29
38
|
});
|
|
39
|
+
validateFlags(values);
|
|
40
|
+
const isTTY = process.stdin.isTTY;
|
|
41
|
+
const useDefaults = values.yes ?? false;
|
|
30
42
|
let directoryName = values.directory;
|
|
31
43
|
if (!directoryName) {
|
|
32
|
-
|
|
33
|
-
|
|
44
|
+
if (!isTTY) {
|
|
45
|
+
console.error(
|
|
46
|
+
chalk.red(
|
|
47
|
+
"Provide the path to the new worker project with --directory"
|
|
48
|
+
)
|
|
49
|
+
);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
directoryName = await safeInput({
|
|
53
|
+
message: "Where would you like to create your worker project?",
|
|
34
54
|
default: ".",
|
|
35
|
-
required: true
|
|
36
|
-
noTTY: "Provide the path to the new worker project with --directory"
|
|
55
|
+
required: true
|
|
37
56
|
});
|
|
38
57
|
}
|
|
39
58
|
if (!directoryName) {
|
|
40
59
|
console.log(chalk.red("Cancelled."));
|
|
41
60
|
process.exit(1);
|
|
42
61
|
}
|
|
62
|
+
const destPath = directoryName === "." ? process.cwd() : path.resolve(process.cwd(), directoryName);
|
|
63
|
+
const templatePath = getTemplatePath();
|
|
64
|
+
const conflicts = analyzeConflicts(destPath, templatePath);
|
|
65
|
+
if (conflicts.hasConflicts) {
|
|
66
|
+
if (values.force) {
|
|
67
|
+
} else if (!isTTY) {
|
|
68
|
+
console.error(
|
|
69
|
+
chalk.red(
|
|
70
|
+
`Directory has conflicting files: ${conflicts.conflicts.join(", ")}
|
|
71
|
+
Use --force to overwrite.`
|
|
72
|
+
)
|
|
73
|
+
);
|
|
74
|
+
process.exit(1);
|
|
75
|
+
} else if (useDefaults) {
|
|
76
|
+
console.error(
|
|
77
|
+
chalk.red(
|
|
78
|
+
"Directory has conflicting files. Use --force with --yes to overwrite."
|
|
79
|
+
)
|
|
80
|
+
);
|
|
81
|
+
process.exit(1);
|
|
82
|
+
} else {
|
|
83
|
+
const action = await safeSelect({
|
|
84
|
+
message: `Directory has existing files (${conflicts.conflicts.length} conflicts). What would you like to do?`,
|
|
85
|
+
choices: [
|
|
86
|
+
{
|
|
87
|
+
value: "overwrite",
|
|
88
|
+
name: `Overwrite conflicting files (${conflicts.conflicts.join(", ")})`
|
|
89
|
+
},
|
|
90
|
+
{ value: "cancel", name: "Cancel" }
|
|
91
|
+
]
|
|
92
|
+
});
|
|
93
|
+
if (action === "cancel") {
|
|
94
|
+
console.log(chalk.yellow("Cancelled."));
|
|
95
|
+
process.exit(0);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
const existingGit = fs.existsSync(path.join(destPath, ".git"));
|
|
100
|
+
let shouldInitGit = false;
|
|
101
|
+
if (existingGit) {
|
|
102
|
+
} else if (values.git) {
|
|
103
|
+
shouldInitGit = true;
|
|
104
|
+
} else if (values["no-git"]) {
|
|
105
|
+
shouldInitGit = false;
|
|
106
|
+
} else if (useDefaults) {
|
|
107
|
+
shouldInitGit = true;
|
|
108
|
+
} else if (isTTY) {
|
|
109
|
+
shouldInitGit = await safeConfirm({
|
|
110
|
+
message: "Initialize a git repository?",
|
|
111
|
+
default: true
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
const packageManager = values.bun ? "bun" : "npm";
|
|
115
|
+
let shouldInstall = false;
|
|
116
|
+
if (values.install) {
|
|
117
|
+
shouldInstall = true;
|
|
118
|
+
} else if (values["no-install"]) {
|
|
119
|
+
shouldInstall = false;
|
|
120
|
+
} else if (useDefaults) {
|
|
121
|
+
shouldInstall = true;
|
|
122
|
+
} else if (isTTY) {
|
|
123
|
+
shouldInstall = await safeConfirm({
|
|
124
|
+
message: "Install dependencies now?",
|
|
125
|
+
default: true
|
|
126
|
+
});
|
|
127
|
+
}
|
|
43
128
|
const spinner = ora("Setting up template...").start();
|
|
44
129
|
try {
|
|
45
|
-
|
|
46
|
-
|
|
130
|
+
if (!fs.existsSync(destPath)) {
|
|
131
|
+
fs.mkdirSync(destPath, { recursive: true });
|
|
132
|
+
}
|
|
47
133
|
spinner.text = "Copying template files...";
|
|
48
|
-
const templatePath = getTemplatePath();
|
|
49
134
|
copyTemplate(templatePath, destPath);
|
|
50
|
-
spinner.succeed(
|
|
51
|
-
|
|
135
|
+
spinner.succeed("Template files copied");
|
|
136
|
+
if (shouldInitGit) {
|
|
137
|
+
const gitSpinner = ora("Initializing git repository...").start();
|
|
138
|
+
const gitSuccess = initializeGit(destPath);
|
|
139
|
+
if (gitSuccess) {
|
|
140
|
+
gitSpinner.succeed("Git repository initialized");
|
|
141
|
+
} else {
|
|
142
|
+
gitSpinner.warn(
|
|
143
|
+
"Could not initialize git repository (is git installed?)"
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
} else if (existingGit) {
|
|
147
|
+
console.log(chalk.dim(" Git repository already exists"));
|
|
148
|
+
}
|
|
149
|
+
if (shouldInstall) {
|
|
150
|
+
const installSpinner = ora(
|
|
151
|
+
`Installing dependencies with ${packageManager}...`
|
|
152
|
+
).start();
|
|
153
|
+
const installSuccess = installPackages(destPath, packageManager);
|
|
154
|
+
if (installSuccess) {
|
|
155
|
+
installSpinner.succeed(`Dependencies installed with ${packageManager}`);
|
|
156
|
+
} else {
|
|
157
|
+
installSpinner.fail("Failed to install dependencies");
|
|
158
|
+
const cmd = packageManager === "bun" ? "bun install" : "npm install";
|
|
159
|
+
console.log(
|
|
160
|
+
chalk.yellow(` Run '${cmd}' manually to install dependencies`)
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
console.log(chalk.green("\n\u2728 Worker project created successfully!"));
|
|
165
|
+
printNextSteps({
|
|
166
|
+
directoryName,
|
|
167
|
+
gitInitialized: shouldInitGit || existingGit,
|
|
168
|
+
packagesInstalled: shouldInstall,
|
|
169
|
+
packageManager
|
|
170
|
+
});
|
|
52
171
|
} catch (err) {
|
|
53
172
|
spinner.fail("Failed to create worker project.");
|
|
54
|
-
|
|
55
|
-
chalk.red(`
|
|
56
|
-
${err instanceof Error ? err.message : String(err)}`)
|
|
57
|
-
);
|
|
58
|
-
process.exit(1);
|
|
173
|
+
throw err;
|
|
59
174
|
}
|
|
60
175
|
}
|
|
61
|
-
function
|
|
62
|
-
|
|
176
|
+
function validateFlags(values) {
|
|
177
|
+
if (values.git && values["no-git"]) {
|
|
178
|
+
console.error(chalk.red("Cannot use both --git and --no-git"));
|
|
179
|
+
process.exit(1);
|
|
180
|
+
}
|
|
181
|
+
if (values.install && values["no-install"]) {
|
|
182
|
+
console.error(chalk.red("Cannot use both --install and --no-install"));
|
|
183
|
+
process.exit(1);
|
|
184
|
+
}
|
|
63
185
|
}
|
|
64
|
-
function
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
186
|
+
function analyzeConflicts(destPath, templatePath) {
|
|
187
|
+
if (!fs.existsSync(destPath)) {
|
|
188
|
+
return { conflicts: [], hasConflicts: false };
|
|
189
|
+
}
|
|
190
|
+
const templateFiles = fs.readdirSync(templatePath);
|
|
191
|
+
const conflicts = [];
|
|
192
|
+
for (const file of templateFiles) {
|
|
193
|
+
if (fs.existsSync(path.join(destPath, file))) {
|
|
194
|
+
conflicts.push(file);
|
|
72
195
|
}
|
|
73
|
-
} else if (repoName !== ".") {
|
|
74
|
-
fs.mkdirSync(destPath, { recursive: true });
|
|
75
196
|
}
|
|
76
|
-
return
|
|
197
|
+
return { conflicts, hasConflicts: conflicts.length > 0 };
|
|
198
|
+
}
|
|
199
|
+
function getTemplatePath() {
|
|
200
|
+
return path.resolve(__dirname, "..", "template");
|
|
77
201
|
}
|
|
78
202
|
function copyTemplate(templatePath, destPath) {
|
|
79
203
|
if (!fs.existsSync(templatePath)) {
|
|
@@ -88,33 +212,66 @@ function copyTemplate(templatePath, destPath) {
|
|
|
88
212
|
fs.cpSync(srcPath, destFilePath, { recursive: true });
|
|
89
213
|
}
|
|
90
214
|
}
|
|
91
|
-
function
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
${chalk.bold("npx workers deploy")}
|
|
98
|
-
`);
|
|
99
|
-
} else {
|
|
100
|
-
console.log(`
|
|
101
|
-
${chalk.bold(`cd ${directoryName}`)}
|
|
102
|
-
${chalk.bold("npm install")}
|
|
103
|
-
${chalk.bold("npx workers deploy")}
|
|
104
|
-
`);
|
|
215
|
+
function initializeGit(destPath) {
|
|
216
|
+
try {
|
|
217
|
+
execSync("git init", { cwd: destPath, stdio: "pipe" });
|
|
218
|
+
return true;
|
|
219
|
+
} catch {
|
|
220
|
+
return false;
|
|
105
221
|
}
|
|
106
222
|
}
|
|
107
|
-
function
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
223
|
+
function installPackages(destPath, pm) {
|
|
224
|
+
const cmd = pm === "bun" ? "bun install" : "npm install";
|
|
225
|
+
try {
|
|
226
|
+
execSync(cmd, {
|
|
227
|
+
cwd: destPath,
|
|
228
|
+
stdio: "pipe",
|
|
229
|
+
env: { ...process.env, FORCE_COLOR: "1" }
|
|
230
|
+
});
|
|
231
|
+
return true;
|
|
232
|
+
} catch {
|
|
233
|
+
return false;
|
|
111
234
|
}
|
|
112
|
-
return prompts.input(config).catch((err) => {
|
|
113
|
-
if (err instanceof Error && err.name === "ExitPromptError") {
|
|
114
|
-
console.log(chalk.red("\u{1F44B} Goodbye!"));
|
|
115
|
-
process.exit(1);
|
|
116
|
-
} else {
|
|
117
|
-
throw err;
|
|
118
|
-
}
|
|
119
|
-
});
|
|
120
235
|
}
|
|
236
|
+
function printNextSteps(state) {
|
|
237
|
+
console.log(chalk.cyan("\nNext steps:\n"));
|
|
238
|
+
const steps = [];
|
|
239
|
+
if (state.directoryName !== ".") {
|
|
240
|
+
steps.push(`cd ${state.directoryName}`);
|
|
241
|
+
}
|
|
242
|
+
if (!state.packagesInstalled) {
|
|
243
|
+
const installCmd = state.packageManager === "bun" ? "bun install" : "npm install";
|
|
244
|
+
steps.push(installCmd);
|
|
245
|
+
}
|
|
246
|
+
const runPrefix = state.packageManager === "bun" ? "bunx" : "npx";
|
|
247
|
+
steps.push(`${runPrefix} workers deploy`);
|
|
248
|
+
for (let i = 0; i < steps.length; i++) {
|
|
249
|
+
console.log(` ${chalk.bold(`${i + 1}.`)} ${chalk.bold(steps[i])}`);
|
|
250
|
+
}
|
|
251
|
+
console.log("");
|
|
252
|
+
console.log(
|
|
253
|
+
chalk.dim(
|
|
254
|
+
` Tip: Run '${state.packageManager === "bun" ? "bunx" : "npx"} workers auth login' to connect to your Notion workspace.
|
|
255
|
+
`
|
|
256
|
+
)
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
function safeInput(config) {
|
|
260
|
+
return prompts.input(config).catch(handlePromptExit);
|
|
261
|
+
}
|
|
262
|
+
function safeConfirm(config) {
|
|
263
|
+
return prompts.confirm(config).catch(handlePromptExit);
|
|
264
|
+
}
|
|
265
|
+
function safeSelect(config) {
|
|
266
|
+
return prompts.select(config).catch(handlePromptExit);
|
|
267
|
+
}
|
|
268
|
+
function handlePromptExit(err) {
|
|
269
|
+
if (err instanceof Error && err.name === "ExitPromptError") {
|
|
270
|
+
console.log(chalk.yellow("\nCancelled."));
|
|
271
|
+
process.exit(0);
|
|
272
|
+
}
|
|
273
|
+
throw err;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// src/index.ts
|
|
277
|
+
void runCreate();
|
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@project-ajax/create",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.36",
|
|
4
4
|
"description": "Initialize a new Notion Project Ajax extensions repo.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"create-ajax": "dist/index.js"
|
|
7
7
|
},
|
|
8
8
|
"type": "module",
|
|
9
9
|
"scripts": {
|
|
10
|
-
"build": "tsup"
|
|
10
|
+
"build": "tsup",
|
|
11
|
+
"test": "vitest run"
|
|
11
12
|
},
|
|
12
13
|
"tsup": {
|
|
13
14
|
"entry": [
|
|
@@ -34,6 +35,7 @@
|
|
|
34
35
|
"devDependencies": {
|
|
35
36
|
"@types/node": "^22.19.0",
|
|
36
37
|
"tsup": "^8.5.0",
|
|
37
|
-
"typescript": "^5.9.3"
|
|
38
|
+
"typescript": "^5.9.3",
|
|
39
|
+
"vitest": "^4.0.8"
|
|
38
40
|
}
|
|
39
41
|
}
|
package/template/AGENTS.md
CHANGED
|
@@ -18,25 +18,25 @@ const worker = new Worker();
|
|
|
18
18
|
export default worker;
|
|
19
19
|
|
|
20
20
|
worker.sync("tasksSync", {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
27
|
});
|
|
28
28
|
|
|
29
29
|
worker.tool("sayHello", {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
34
|
});
|
|
35
35
|
|
|
36
36
|
worker.automation("sendWelcomeEmail", {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
title: "Send Welcome Email",
|
|
38
|
+
description: "Runs from a database automation",
|
|
39
|
+
execute: async (event, { notion }) => {},
|
|
40
40
|
});
|
|
41
41
|
|
|
42
42
|
worker.oauth("googleAuth", { name: "my-google-auth", provider: "google" });
|
|
@@ -69,23 +69,23 @@ Syncs run in a "sync cycle": a back-to-back chain of `execute` calls that starts
|
|
|
69
69
|
|
|
70
70
|
```ts
|
|
71
71
|
worker.sync("paginatedSync", {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
89
|
});
|
|
90
90
|
```
|
|
91
91
|
|
|
@@ -94,24 +94,24 @@ worker.sync("paginatedSync", {
|
|
|
94
94
|
**Incremental example (changes only, with deletes):**
|
|
95
95
|
```ts
|
|
96
96
|
worker.sync("incrementalSync", {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
115
|
});
|
|
116
116
|
```
|
|
117
117
|
|
|
@@ -122,29 +122,34 @@ Two syncs can relate to one another using `Schema.relation(relatedSyncKey)` and
|
|
|
122
122
|
```ts
|
|
123
123
|
worker.sync("projectsSync", {
|
|
124
124
|
primaryKeyProperty: "Project ID",
|
|
125
|
-
|
|
125
|
+
...
|
|
126
126
|
});
|
|
127
127
|
|
|
128
128
|
// Example sync worker that syncs sample tasks to a database
|
|
129
129
|
worker.sync("tasksSync", {
|
|
130
130
|
primaryKeyProperty: "Task ID",
|
|
131
|
-
|
|
131
|
+
...
|
|
132
132
|
schema: {
|
|
133
|
-
|
|
133
|
+
...
|
|
134
134
|
properties: {
|
|
135
|
-
|
|
136
|
-
Project: Schema.relation("projectsSync"
|
|
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
|
+
}),
|
|
137
142
|
},
|
|
138
143
|
},
|
|
139
144
|
|
|
140
145
|
execute: async () => {
|
|
141
146
|
// Return sample tasks as database entries
|
|
142
|
-
|
|
147
|
+
const tasks = fetchTasks()
|
|
143
148
|
const changes = tasks.map((task) => ({
|
|
144
149
|
type: "upsert" as const,
|
|
145
150
|
key: task.id,
|
|
146
151
|
properties: {
|
|
147
|
-
|
|
152
|
+
...
|
|
148
153
|
Project: [Builder.relation(task.projectId)],
|
|
149
154
|
},
|
|
150
155
|
}));
|
package/template/CLAUDE.md
CHANGED
|
@@ -18,25 +18,25 @@ const worker = new Worker();
|
|
|
18
18
|
export default worker;
|
|
19
19
|
|
|
20
20
|
worker.sync("tasksSync", {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
27
|
});
|
|
28
28
|
|
|
29
29
|
worker.tool("sayHello", {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
34
|
});
|
|
35
35
|
|
|
36
36
|
worker.automation("sendWelcomeEmail", {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
title: "Send Welcome Email",
|
|
38
|
+
description: "Runs from a database automation",
|
|
39
|
+
execute: async (event, { notion }) => {},
|
|
40
40
|
});
|
|
41
41
|
|
|
42
42
|
worker.oauth("googleAuth", { name: "my-google-auth", provider: "google" });
|
|
@@ -69,23 +69,23 @@ Syncs run in a "sync cycle": a back-to-back chain of `execute` calls that starts
|
|
|
69
69
|
|
|
70
70
|
```ts
|
|
71
71
|
worker.sync("paginatedSync", {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
89
|
});
|
|
90
90
|
```
|
|
91
91
|
|
|
@@ -94,24 +94,24 @@ worker.sync("paginatedSync", {
|
|
|
94
94
|
**Incremental example (changes only, with deletes):**
|
|
95
95
|
```ts
|
|
96
96
|
worker.sync("incrementalSync", {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
115
|
});
|
|
116
116
|
```
|
|
117
117
|
|
|
@@ -122,29 +122,34 @@ Two syncs can relate to one another using `Schema.relation(relatedSyncKey)` and
|
|
|
122
122
|
```ts
|
|
123
123
|
worker.sync("projectsSync", {
|
|
124
124
|
primaryKeyProperty: "Project ID",
|
|
125
|
-
|
|
125
|
+
...
|
|
126
126
|
});
|
|
127
127
|
|
|
128
128
|
// Example sync worker that syncs sample tasks to a database
|
|
129
129
|
worker.sync("tasksSync", {
|
|
130
130
|
primaryKeyProperty: "Task ID",
|
|
131
|
-
|
|
131
|
+
...
|
|
132
132
|
schema: {
|
|
133
|
-
|
|
133
|
+
...
|
|
134
134
|
properties: {
|
|
135
|
-
|
|
136
|
-
Project: Schema.relation("projectsSync"
|
|
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
|
+
}),
|
|
137
142
|
},
|
|
138
143
|
},
|
|
139
144
|
|
|
140
145
|
execute: async () => {
|
|
141
146
|
// Return sample tasks as database entries
|
|
142
|
-
|
|
147
|
+
const tasks = fetchTasks()
|
|
143
148
|
const changes = tasks.map((task) => ({
|
|
144
149
|
type: "upsert" as const,
|
|
145
150
|
key: task.id,
|
|
146
151
|
properties: {
|
|
147
|
-
|
|
152
|
+
...
|
|
148
153
|
Project: [Builder.relation(task.projectId)],
|
|
149
154
|
},
|
|
150
155
|
}));
|