@notionhq/workers 0.1.0 → 0.2.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/ajv-formats.d.js +0 -0
- package/dist/capabilities/automation.js +2 -2
- package/dist/capabilities/sync.d.ts +73 -16
- package/dist/capabilities/sync.d.ts.map +1 -1
- package/dist/capabilities/sync.js +19 -10
- package/dist/capabilities/tool.d.ts.map +1 -1
- package/dist/capabilities/tool.js +18 -10
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/pacer.d.ts +24 -0
- package/dist/pacer.d.ts.map +1 -0
- package/dist/pacer.js +14 -0
- package/dist/pacer.test.d.ts +2 -0
- package/dist/pacer.test.d.ts.map +1 -0
- package/dist/pacer_internal.d.ts +6 -0
- package/dist/pacer_internal.d.ts.map +1 -0
- package/dist/pacer_internal.js +32 -0
- package/dist/schema.d.ts +3 -10
- package/dist/schema.d.ts.map +1 -1
- package/dist/types.d.ts +5 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/worker.d.ts +138 -5
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +73 -7
- package/dist/worker.test.d.ts +2 -0
- package/dist/worker.test.d.ts.map +1 -0
- package/package.json +7 -2
- package/src/ajv-formats.d.ts +7 -0
- package/src/capabilities/automation.test.ts +2 -2
- package/src/capabilities/automation.ts +2 -2
- package/src/capabilities/sync.test.ts +89 -32
- package/src/capabilities/sync.ts +52 -25
- package/src/capabilities/tool.test.ts +47 -3
- package/src/capabilities/tool.ts +12 -4
- package/src/index.ts +9 -0
- package/src/pacer.test.ts +110 -0
- package/src/pacer.ts +25 -0
- package/src/pacer_internal.ts +60 -0
- package/src/schema.ts +3 -10
- package/src/types.ts +4 -2
- package/src/worker.test.ts +167 -0
- package/src/worker.ts +205 -7
package/dist/worker.d.ts
CHANGED
|
@@ -7,8 +7,137 @@ import type { Schema } from "./schema.js";
|
|
|
7
7
|
import type { HandlerOptions, JSONValue } from "./types.js";
|
|
8
8
|
export type { AutomationConfiguration, AutomationEvent, CapabilityContext, OAuthConfiguration, NotionManagedOAuthConfiguration, UserManagedOAuthConfiguration, SyncConfiguration, ToolConfiguration, };
|
|
9
9
|
type Capability = SyncCapability | ToolCapability<any, any> | AutomationCapability | OAuthCapability;
|
|
10
|
+
/**
|
|
11
|
+
* Configuration for a database that Notion creates and manages on behalf
|
|
12
|
+
* of the worker. The database is created on first deploy and its schema
|
|
13
|
+
* is migrated on subsequent deploys.
|
|
14
|
+
*/
|
|
15
|
+
export type ManagedDatabaseConfig<PK extends string, S extends Schema<PK>> = {
|
|
16
|
+
type: "managed";
|
|
17
|
+
/**
|
|
18
|
+
* The title for the database when it is first created in Notion.
|
|
19
|
+
*
|
|
20
|
+
* Updating this after the database has been created will not change the
|
|
21
|
+
* title of the database — users may rename it freely.
|
|
22
|
+
*/
|
|
23
|
+
initialTitle: string;
|
|
24
|
+
/**
|
|
25
|
+
* The property that serves as the primary key for matching records.
|
|
26
|
+
* Must be a property defined in the schema. The value of this property
|
|
27
|
+
* should match the `key` field on each sync change.
|
|
28
|
+
*/
|
|
29
|
+
primaryKeyProperty: PK;
|
|
30
|
+
/**
|
|
31
|
+
* The database schema. Notion will migrate the database to match
|
|
32
|
+
* the given schema on worker deploy.
|
|
33
|
+
*/
|
|
34
|
+
schema: S;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Configuration union for all database types.
|
|
38
|
+
* Currently only `"managed"` is supported.
|
|
39
|
+
*/
|
|
40
|
+
export type DatabaseConfig<PK extends string, S extends Schema<PK>> = ManagedDatabaseConfig<PK, S>;
|
|
41
|
+
/**
|
|
42
|
+
* An opaque handle returned by `worker.database()`. Pass this to
|
|
43
|
+
* `worker.sync()` to associate a sync capability with the database.
|
|
44
|
+
*/
|
|
45
|
+
export type DatabaseHandle<PK extends string, S extends Schema<PK>> = {
|
|
46
|
+
readonly key: string;
|
|
47
|
+
readonly config: DatabaseConfig<PK, S>;
|
|
48
|
+
};
|
|
49
|
+
/** Internal type-erased alias for storing heterogeneous databases. */
|
|
50
|
+
type RegisteredDatabase = DatabaseHandle<string, Schema<string>>;
|
|
51
|
+
/**
|
|
52
|
+
* Configuration for a pacer that rate-limits requests to an external API.
|
|
53
|
+
* If multiple syncs share a pacer, the server apportions the budget evenly.
|
|
54
|
+
*/
|
|
55
|
+
export type PacerConfig = {
|
|
56
|
+
/** Maximum number of requests allowed per interval. */
|
|
57
|
+
allowedRequests: number;
|
|
58
|
+
/** The interval window in milliseconds. */
|
|
59
|
+
intervalMs: number;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* An opaque handle returned by `worker.pacer()`. Call `handle.wait()`
|
|
63
|
+
* before each API request to stay under the rate limit.
|
|
64
|
+
*/
|
|
65
|
+
export type PacerHandle = {
|
|
66
|
+
readonly key: string;
|
|
67
|
+
readonly config: PacerConfig;
|
|
68
|
+
/**
|
|
69
|
+
* Wait until a request can proceed under this pacer's rate limit.
|
|
70
|
+
*
|
|
71
|
+
* Call this before **every** API request inside your sync's `execute` function.
|
|
72
|
+
* The pacer ensures requests are evenly spaced over the interval window.
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```ts
|
|
76
|
+
* const apiPacer = worker.pacer("myApi", { allowedRequests: 10, intervalMs: 1000 });
|
|
77
|
+
*
|
|
78
|
+
* worker.sync("mySync", {
|
|
79
|
+
* database: myDb,
|
|
80
|
+
* execute: async () => {
|
|
81
|
+
* await apiPacer.wait();
|
|
82
|
+
* const data = await fetchFromApi();
|
|
83
|
+
* // ...
|
|
84
|
+
* },
|
|
85
|
+
* });
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
wait(): Promise<void>;
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* A pacer declaration as it appears in the manifest.
|
|
92
|
+
*/
|
|
93
|
+
export type PacerDeclaration = {
|
|
94
|
+
readonly key: string;
|
|
95
|
+
readonly config: PacerConfig;
|
|
96
|
+
};
|
|
97
|
+
/** A capability entry stripped of its handler, used in the manifest. */
|
|
98
|
+
type CapabilityManifestEntry = Pick<Capability, "_tag" | "key" | "config">;
|
|
99
|
+
/**
|
|
100
|
+
* The full worker manifest, read by the server at deploy time to discover
|
|
101
|
+
* databases, pacers, capabilities, and the SDK version.
|
|
102
|
+
*/
|
|
103
|
+
export type WorkerManifest = {
|
|
104
|
+
readonly sdkVersion: string;
|
|
105
|
+
readonly databases: readonly RegisteredDatabase[];
|
|
106
|
+
readonly pacers: readonly PacerDeclaration[];
|
|
107
|
+
readonly capabilities: readonly CapabilityManifestEntry[];
|
|
108
|
+
};
|
|
10
109
|
export declare class Worker {
|
|
11
110
|
#private;
|
|
111
|
+
database<PK extends string, S extends Schema<PK>>(key: string, config: DatabaseConfig<PK, S>): DatabaseHandle<PK, S>;
|
|
112
|
+
/**
|
|
113
|
+
* Register a pacer for rate-limiting requests to an external API.
|
|
114
|
+
*
|
|
115
|
+
* Multiple syncs can share a pacer — the server will apportion the
|
|
116
|
+
* budget evenly across all syncs that reference it.
|
|
117
|
+
*
|
|
118
|
+
* Example:
|
|
119
|
+
*
|
|
120
|
+
* ```ts
|
|
121
|
+
* const jiraPacer = worker.pacer("jira", {
|
|
122
|
+
* allowedRequests: 10,
|
|
123
|
+
* intervalMs: 1000,
|
|
124
|
+
* });
|
|
125
|
+
*
|
|
126
|
+
* worker.sync("jiraSync", {
|
|
127
|
+
* database: tasks,
|
|
128
|
+
* execute: async (state, { notion }) => {
|
|
129
|
+
* await jiraPacer.wait();
|
|
130
|
+
* const data = await fetchFromJira();
|
|
131
|
+
* // ...
|
|
132
|
+
* },
|
|
133
|
+
* });
|
|
134
|
+
* ```
|
|
135
|
+
*
|
|
136
|
+
* @param key - The unique key for this pacer.
|
|
137
|
+
* @param config - The rate limit configuration.
|
|
138
|
+
* @returns A pacer handle. Call `handle.wait()` before each API request.
|
|
139
|
+
*/
|
|
140
|
+
pacer(key: string, config: PacerConfig): PacerHandle;
|
|
12
141
|
/**
|
|
13
142
|
* Register a sync capability.
|
|
14
143
|
*
|
|
@@ -22,26 +151,29 @@ export declare class Worker {
|
|
|
22
151
|
* const worker = new Worker();
|
|
23
152
|
* export default worker;
|
|
24
153
|
*
|
|
25
|
-
* worker.
|
|
26
|
-
*
|
|
154
|
+
* const tasks = worker.database("tasks", {
|
|
155
|
+
* type: "managed",
|
|
156
|
+
* initialTitle: "Tasks",
|
|
27
157
|
* schema: {
|
|
28
|
-
* defaultName: "Tasks",
|
|
29
158
|
* properties: {
|
|
30
159
|
* "Task Name": Schema.title(),
|
|
31
|
-
* "Task ID": Schema.richText(),
|
|
32
160
|
* Status: Schema.select([
|
|
33
161
|
* { name: "Open", color: "default" },
|
|
34
162
|
* { name: "Done", color: "green" },
|
|
35
163
|
* ]),
|
|
36
164
|
* },
|
|
37
165
|
* },
|
|
166
|
+
* });
|
|
167
|
+
*
|
|
168
|
+
* worker.sync("tasksSync", {
|
|
169
|
+
* database: tasks,
|
|
38
170
|
* execute: async () => {
|
|
39
171
|
* const changes = [
|
|
40
172
|
* {
|
|
41
173
|
* key: "task-1",
|
|
174
|
+
* type: "upsert",
|
|
42
175
|
* properties: {
|
|
43
176
|
* "Task Name": Builder.title("Write docs"),
|
|
44
|
-
* "Task ID": Builder.richText("task-1"),
|
|
45
177
|
* Status: Builder.select("Open"),
|
|
46
178
|
* },
|
|
47
179
|
* },
|
|
@@ -159,6 +291,7 @@ export declare class Worker {
|
|
|
159
291
|
* Get all registered capabilities (for discovery) without their handlers.
|
|
160
292
|
*/
|
|
161
293
|
get capabilities(): Pick<Capability, "_tag" | "key" | "config">[];
|
|
294
|
+
get manifest(): WorkerManifest;
|
|
162
295
|
/**
|
|
163
296
|
* Execute a capability by key.
|
|
164
297
|
*
|
package/dist/worker.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../src/worker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,oBAAoB,EACpB,uBAAuB,EACvB,eAAe,EACf,MAAM,8BAA8B,CAAC;AAEtC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,KAAK,EACX,+BAA+B,EAC/B,eAAe,EACf,kBAAkB,EAClB,6BAA6B,EAC7B,MAAM,yBAAyB,CAAC;AAEjC,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAEhF,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../src/worker.ts"],"names":[],"mappings":"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"}
|
package/dist/worker.js
CHANGED
|
@@ -2,8 +2,63 @@ import { createAutomationCapability } from "./capabilities/automation.js";
|
|
|
2
2
|
import { createOAuthCapability } from "./capabilities/oauth.js";
|
|
3
3
|
import { createSyncCapability } from "./capabilities/sync.js";
|
|
4
4
|
import { createToolCapability } from "./capabilities/tool.js";
|
|
5
|
+
import { pacerWait } from "./pacer_internal.js";
|
|
6
|
+
import { createRequire } from "node:module";
|
|
7
|
+
const SDK_VERSION = (() => {
|
|
8
|
+
const require2 = createRequire(import.meta.url);
|
|
9
|
+
const packageJson = require2("../package.json");
|
|
10
|
+
if (typeof packageJson !== "object" || packageJson === null || !("version" in packageJson) || typeof packageJson.version !== "string") {
|
|
11
|
+
throw new Error("Failed to read SDK version from package.json");
|
|
12
|
+
}
|
|
13
|
+
return packageJson.version;
|
|
14
|
+
})();
|
|
5
15
|
class Worker {
|
|
6
16
|
#capabilities = /* @__PURE__ */ new Map();
|
|
17
|
+
#databases = /* @__PURE__ */ new Map();
|
|
18
|
+
#pacers = /* @__PURE__ */ new Map();
|
|
19
|
+
database(key, config) {
|
|
20
|
+
this.#validateUniqueKey(key);
|
|
21
|
+
const database = { key, config };
|
|
22
|
+
this.#databases.set(key, { key, config });
|
|
23
|
+
return database;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Register a pacer for rate-limiting requests to an external API.
|
|
27
|
+
*
|
|
28
|
+
* Multiple syncs can share a pacer — the server will apportion the
|
|
29
|
+
* budget evenly across all syncs that reference it.
|
|
30
|
+
*
|
|
31
|
+
* Example:
|
|
32
|
+
*
|
|
33
|
+
* ```ts
|
|
34
|
+
* const jiraPacer = worker.pacer("jira", {
|
|
35
|
+
* allowedRequests: 10,
|
|
36
|
+
* intervalMs: 1000,
|
|
37
|
+
* });
|
|
38
|
+
*
|
|
39
|
+
* worker.sync("jiraSync", {
|
|
40
|
+
* database: tasks,
|
|
41
|
+
* execute: async (state, { notion }) => {
|
|
42
|
+
* await jiraPacer.wait();
|
|
43
|
+
* const data = await fetchFromJira();
|
|
44
|
+
* // ...
|
|
45
|
+
* },
|
|
46
|
+
* });
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
49
|
+
* @param key - The unique key for this pacer.
|
|
50
|
+
* @param config - The rate limit configuration.
|
|
51
|
+
* @returns A pacer handle. Call `handle.wait()` before each API request.
|
|
52
|
+
*/
|
|
53
|
+
pacer(key, config) {
|
|
54
|
+
this.#validateUniqueKey(key);
|
|
55
|
+
this.#pacers.set(key, { key, config });
|
|
56
|
+
return {
|
|
57
|
+
key,
|
|
58
|
+
config,
|
|
59
|
+
wait: () => pacerWait(key)
|
|
60
|
+
};
|
|
61
|
+
}
|
|
7
62
|
/**
|
|
8
63
|
* Register a sync capability.
|
|
9
64
|
*
|
|
@@ -17,26 +72,29 @@ class Worker {
|
|
|
17
72
|
* const worker = new Worker();
|
|
18
73
|
* export default worker;
|
|
19
74
|
*
|
|
20
|
-
* worker.
|
|
21
|
-
*
|
|
75
|
+
* const tasks = worker.database("tasks", {
|
|
76
|
+
* type: "managed",
|
|
77
|
+
* initialTitle: "Tasks",
|
|
22
78
|
* schema: {
|
|
23
|
-
* defaultName: "Tasks",
|
|
24
79
|
* properties: {
|
|
25
80
|
* "Task Name": Schema.title(),
|
|
26
|
-
* "Task ID": Schema.richText(),
|
|
27
81
|
* Status: Schema.select([
|
|
28
82
|
* { name: "Open", color: "default" },
|
|
29
83
|
* { name: "Done", color: "green" },
|
|
30
84
|
* ]),
|
|
31
85
|
* },
|
|
32
86
|
* },
|
|
87
|
+
* });
|
|
88
|
+
*
|
|
89
|
+
* worker.sync("tasksSync", {
|
|
90
|
+
* database: tasks,
|
|
33
91
|
* execute: async () => {
|
|
34
92
|
* const changes = [
|
|
35
93
|
* {
|
|
36
94
|
* key: "task-1",
|
|
95
|
+
* type: "upsert",
|
|
37
96
|
* properties: {
|
|
38
97
|
* "Task Name": Builder.title("Write docs"),
|
|
39
|
-
* "Task ID": Builder.richText("task-1"),
|
|
40
98
|
* Status: Builder.select("Open"),
|
|
41
99
|
* },
|
|
42
100
|
* },
|
|
@@ -180,6 +238,14 @@ class Worker {
|
|
|
180
238
|
config: c.config
|
|
181
239
|
}));
|
|
182
240
|
}
|
|
241
|
+
get manifest() {
|
|
242
|
+
return {
|
|
243
|
+
sdkVersion: SDK_VERSION,
|
|
244
|
+
databases: Array.from(this.#databases.values()),
|
|
245
|
+
pacers: Array.from(this.#pacers.values()),
|
|
246
|
+
capabilities: this.capabilities
|
|
247
|
+
};
|
|
248
|
+
}
|
|
183
249
|
/**
|
|
184
250
|
* Execute a capability by key.
|
|
185
251
|
*
|
|
@@ -204,8 +270,8 @@ class Worker {
|
|
|
204
270
|
if (!key || typeof key !== "string") {
|
|
205
271
|
throw new Error("Capability key must be a non-empty string");
|
|
206
272
|
}
|
|
207
|
-
if (this.#capabilities.has(key)) {
|
|
208
|
-
throw new Error(`
|
|
273
|
+
if (this.#capabilities.has(key) || this.#databases.has(key) || this.#pacers.has(key)) {
|
|
274
|
+
throw new Error(`Worker item with key "${key}" already registered`);
|
|
209
275
|
}
|
|
210
276
|
}
|
|
211
277
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker.test.d.ts","sourceRoot":"","sources":["../src/worker.test.ts"],"names":[],"mappings":""}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@notionhq/workers",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "An SDK for building workers for the Notion Workers platform",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -32,6 +32,10 @@
|
|
|
32
32
|
"./types": {
|
|
33
33
|
"types": "./dist/types.d.ts",
|
|
34
34
|
"default": "./dist/types.js"
|
|
35
|
+
},
|
|
36
|
+
"./pacer": {
|
|
37
|
+
"types": "./dist/pacer.d.ts",
|
|
38
|
+
"default": "./dist/pacer.js"
|
|
35
39
|
}
|
|
36
40
|
},
|
|
37
41
|
"engines": {
|
|
@@ -69,6 +73,7 @@
|
|
|
69
73
|
"@notionhq/client": "^2.2.15"
|
|
70
74
|
},
|
|
71
75
|
"dependencies": {
|
|
72
|
-
"ajv": "^8.17.1"
|
|
76
|
+
"ajv": "^8.17.1",
|
|
77
|
+
"ajv-formats": "^3.0.1"
|
|
73
78
|
}
|
|
74
79
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// ajv-formats is CJS-only and doesn't ship types compatible with
|
|
2
|
+
// moduleResolution: "nodenext". This declaration provides the correct
|
|
3
|
+
// callable default export signature so we can import it without suppression.
|
|
4
|
+
declare module "ajv-formats" {
|
|
5
|
+
import type { Ajv } from "ajv";
|
|
6
|
+
export default function addFormats(ajv: Ajv): Ajv;
|
|
7
|
+
}
|
|
@@ -59,7 +59,7 @@ describe("createAutomationCapability", () => {
|
|
|
59
59
|
}),
|
|
60
60
|
);
|
|
61
61
|
expect(stdoutSpy).toHaveBeenCalledWith(
|
|
62
|
-
`\n<
|
|
62
|
+
`\n<__notion_output__>{"_tag":"success","value":{"status":"success"}}</__notion_output__>\n`,
|
|
63
63
|
);
|
|
64
64
|
});
|
|
65
65
|
|
|
@@ -105,7 +105,7 @@ describe("createAutomationCapability", () => {
|
|
|
105
105
|
}),
|
|
106
106
|
);
|
|
107
107
|
expect(stdoutSpy).toHaveBeenCalledWith(
|
|
108
|
-
`\n<
|
|
108
|
+
`\n<__notion_output__>{"_tag":"success","value":{"status":"success"}}</__notion_output__>\n`,
|
|
109
109
|
);
|
|
110
110
|
});
|
|
111
111
|
|
|
@@ -111,7 +111,7 @@ export function createAutomationCapability(
|
|
|
111
111
|
return { status: "success" };
|
|
112
112
|
} else {
|
|
113
113
|
process.stdout.write(
|
|
114
|
-
`\n<
|
|
114
|
+
`\n<__notion_output__>${JSON.stringify({ _tag: "success", value: { status: "success" } })}</__notion_output__>\n`,
|
|
115
115
|
);
|
|
116
116
|
}
|
|
117
117
|
} catch (err) {
|
|
@@ -119,7 +119,7 @@ export function createAutomationCapability(
|
|
|
119
119
|
|
|
120
120
|
if (!options?.concreteOutput) {
|
|
121
121
|
process.stdout.write(
|
|
122
|
-
`\n<
|
|
122
|
+
`\n<__notion_output__>${JSON.stringify({ _tag: "error", error: { name: error.name, message: error.message, trace: error.stack } })}</__notion_output__>\n`,
|
|
123
123
|
);
|
|
124
124
|
}
|
|
125
125
|
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
import * as Builder from "../builder.js";
|
|
11
11
|
import { ExecutionError } from "../error.js";
|
|
12
12
|
import * as Schema from "../schema.js";
|
|
13
|
+
import { Worker } from "../worker.js";
|
|
13
14
|
import { createSyncCapability } from "./sync.js";
|
|
14
15
|
|
|
15
16
|
describe("Worker.sync", () => {
|
|
@@ -35,29 +36,6 @@ describe("Worker.sync", () => {
|
|
|
35
36
|
config: { twoWay: false },
|
|
36
37
|
});
|
|
37
38
|
});
|
|
38
|
-
|
|
39
|
-
it("can be used in a schema definition", () => {
|
|
40
|
-
const capability = createSyncCapability("tasksSync", {
|
|
41
|
-
primaryKeyProperty: "Task ID",
|
|
42
|
-
schema: {
|
|
43
|
-
defaultName: "Tasks",
|
|
44
|
-
properties: {
|
|
45
|
-
"Task ID": Schema.title(),
|
|
46
|
-
Project: Schema.relation("projectsSync"),
|
|
47
|
-
},
|
|
48
|
-
},
|
|
49
|
-
execute: async () => ({
|
|
50
|
-
changes: [],
|
|
51
|
-
hasMore: false,
|
|
52
|
-
}),
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
expect(capability.config.schema.properties.Project).toEqual({
|
|
56
|
-
type: "relation",
|
|
57
|
-
relatedSyncKey: "projectsSync",
|
|
58
|
-
config: { twoWay: false },
|
|
59
|
-
});
|
|
60
|
-
});
|
|
61
39
|
});
|
|
62
40
|
|
|
63
41
|
describe("Builder.relation", () => {
|
|
@@ -82,15 +60,21 @@ describe("Worker.sync", () => {
|
|
|
82
60
|
|
|
83
61
|
describe("SyncedObject with relation properties", () => {
|
|
84
62
|
it("allows relation values in synced objects", async () => {
|
|
85
|
-
const
|
|
63
|
+
const worker = new Worker();
|
|
64
|
+
const tasks = worker.database("tasks", {
|
|
65
|
+
type: "managed",
|
|
66
|
+
initialTitle: "Tasks",
|
|
86
67
|
primaryKeyProperty: "Task ID",
|
|
87
68
|
schema: {
|
|
88
|
-
defaultName: "Tasks",
|
|
89
69
|
properties: {
|
|
90
70
|
"Task ID": Schema.title(),
|
|
91
71
|
Project: Schema.relation("projectsSync"),
|
|
92
72
|
},
|
|
93
73
|
},
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const capability = createSyncCapability("tasksSync", {
|
|
77
|
+
database: tasks,
|
|
94
78
|
execute: async () => ({
|
|
95
79
|
changes: [
|
|
96
80
|
{
|
|
@@ -117,22 +101,27 @@ describe("Worker.sync", () => {
|
|
|
117
101
|
}),
|
|
118
102
|
});
|
|
119
103
|
|
|
120
|
-
// Verify the capability is properly configured
|
|
121
104
|
expect(capability._tag).toBe("sync");
|
|
122
|
-
expect(capability.config.
|
|
105
|
+
expect(capability.config.databaseKey).toBe("tasks");
|
|
123
106
|
});
|
|
124
107
|
});
|
|
125
108
|
|
|
126
109
|
describe("handler output envelope", () => {
|
|
127
110
|
it("wraps successful result in success envelope", async () => {
|
|
128
|
-
const
|
|
111
|
+
const worker = new Worker();
|
|
112
|
+
const db = worker.database("test", {
|
|
113
|
+
type: "managed",
|
|
114
|
+
initialTitle: "Test",
|
|
129
115
|
primaryKeyProperty: "ID",
|
|
130
116
|
schema: {
|
|
131
|
-
defaultName: "Test",
|
|
132
117
|
properties: {
|
|
133
118
|
ID: Schema.title(),
|
|
134
119
|
},
|
|
135
120
|
},
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const capability = createSyncCapability("testSync", {
|
|
124
|
+
database: db,
|
|
136
125
|
execute: async () => ({
|
|
137
126
|
changes: [],
|
|
138
127
|
hasMore: false,
|
|
@@ -142,19 +131,25 @@ describe("Worker.sync", () => {
|
|
|
142
131
|
await capability.handler();
|
|
143
132
|
|
|
144
133
|
expect(stdoutSpy).toHaveBeenCalledWith(
|
|
145
|
-
`\n<
|
|
134
|
+
`\n<__notion_output__>${JSON.stringify({ _tag: "success", value: { changes: [], hasMore: false, nextUserContext: undefined, nextPacerStates: {} } })}</__notion_output__>\n`,
|
|
146
135
|
);
|
|
147
136
|
});
|
|
148
137
|
|
|
149
138
|
it("wraps error in error envelope", async () => {
|
|
150
|
-
const
|
|
139
|
+
const worker = new Worker();
|
|
140
|
+
const db = worker.database("test", {
|
|
141
|
+
type: "managed",
|
|
142
|
+
initialTitle: "Test",
|
|
151
143
|
primaryKeyProperty: "ID",
|
|
152
144
|
schema: {
|
|
153
|
-
defaultName: "Test",
|
|
154
145
|
properties: {
|
|
155
146
|
ID: Schema.title(),
|
|
156
147
|
},
|
|
157
148
|
},
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const capability = createSyncCapability("errorSync", {
|
|
152
|
+
database: db,
|
|
158
153
|
execute: async () => {
|
|
159
154
|
throw new Error("sync failed");
|
|
160
155
|
},
|
|
@@ -169,5 +164,67 @@ describe("Worker.sync", () => {
|
|
|
169
164
|
expect.stringContaining(`"name":"ExecutionError"`),
|
|
170
165
|
);
|
|
171
166
|
});
|
|
167
|
+
|
|
168
|
+
it("emits sync output with key and default targetDatabaseKey", async () => {
|
|
169
|
+
const worker = new Worker();
|
|
170
|
+
const tasks = worker.database("tasks", {
|
|
171
|
+
type: "managed",
|
|
172
|
+
initialTitle: "Tasks",
|
|
173
|
+
primaryKeyProperty: "Name",
|
|
174
|
+
schema: {
|
|
175
|
+
properties: {
|
|
176
|
+
Name: Schema.title(),
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
const capability = createSyncCapability("tasksSync", {
|
|
182
|
+
database: tasks,
|
|
183
|
+
execute: async () => ({
|
|
184
|
+
changes: [
|
|
185
|
+
{
|
|
186
|
+
type: "upsert" as const,
|
|
187
|
+
key: "task-1",
|
|
188
|
+
properties: {
|
|
189
|
+
Name: Builder.title("Write docs"),
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
type: "delete" as const,
|
|
194
|
+
key: "task-2",
|
|
195
|
+
},
|
|
196
|
+
],
|
|
197
|
+
hasMore: false,
|
|
198
|
+
}),
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
await capability.handler();
|
|
202
|
+
|
|
203
|
+
expect(stdoutSpy).toHaveBeenCalledWith(
|
|
204
|
+
`\n<__notion_output__>${JSON.stringify({
|
|
205
|
+
_tag: "success",
|
|
206
|
+
value: {
|
|
207
|
+
changes: [
|
|
208
|
+
{
|
|
209
|
+
type: "upsert",
|
|
210
|
+
key: "task-1",
|
|
211
|
+
properties: {
|
|
212
|
+
Name: Builder.title("Write docs"),
|
|
213
|
+
},
|
|
214
|
+
targetDatabaseKey: "tasks",
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
type: "delete",
|
|
218
|
+
key: "task-2",
|
|
219
|
+
targetDatabaseKey: "tasks",
|
|
220
|
+
},
|
|
221
|
+
],
|
|
222
|
+
hasMore: false,
|
|
223
|
+
nextUserContext: undefined,
|
|
224
|
+
nextPacerStates: {},
|
|
225
|
+
},
|
|
226
|
+
})}</__notion_output__>\n`,
|
|
227
|
+
);
|
|
228
|
+
});
|
|
172
229
|
});
|
|
173
230
|
});
|