@notionhq/workers 0.4.0 → 0.6.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/alpha/builder.d.ts +149 -0
- package/dist/alpha/builder.d.ts.map +1 -0
- package/dist/alpha/builder.js +260 -0
- package/dist/alpha/builder.test.d.ts +2 -0
- package/dist/alpha/builder.test.d.ts.map +1 -0
- package/dist/alpha/cli/build.d.ts +13 -0
- package/dist/alpha/cli/build.d.ts.map +1 -0
- package/dist/alpha/cli/build.js +36 -0
- package/dist/alpha/cli/build.test.d.ts +2 -0
- package/dist/alpha/cli/build.test.d.ts.map +1 -0
- package/dist/alpha/cli/codegen.d.ts +10 -0
- package/dist/alpha/cli/codegen.d.ts.map +1 -0
- package/dist/alpha/cli/codegen.js +41 -0
- package/dist/alpha/cli/codegen.test.d.ts +2 -0
- package/dist/alpha/cli/codegen.test.d.ts.map +1 -0
- package/dist/alpha/cli/discover.d.ts +35 -0
- package/dist/alpha/cli/discover.d.ts.map +1 -0
- package/dist/alpha/cli/discover.js +39 -0
- package/dist/alpha/cli/discover.test.d.ts +2 -0
- package/dist/alpha/cli/discover.test.d.ts.map +1 -0
- package/dist/alpha/cli/emit-manifest.d.ts +33 -0
- package/dist/alpha/cli/emit-manifest.d.ts.map +1 -0
- package/dist/alpha/cli/emit-manifest.js +266 -0
- package/dist/alpha/cli/emit-manifest.test.d.ts +2 -0
- package/dist/alpha/cli/emit-manifest.test.d.ts.map +1 -0
- package/dist/alpha/cli/index.d.ts +3 -0
- package/dist/alpha/cli/index.d.ts.map +1 -0
- package/dist/alpha/cli/index.js +36 -0
- package/dist/alpha/context.d.ts +8 -0
- package/dist/alpha/context.d.ts.map +1 -0
- package/dist/alpha/context.js +33 -0
- package/dist/alpha/database.d.ts +90 -0
- package/dist/alpha/database.d.ts.map +1 -0
- package/dist/alpha/database.js +9 -0
- package/dist/alpha/error.d.ts +41 -0
- package/dist/alpha/error.d.ts.map +1 -0
- package/dist/alpha/error.js +25 -0
- package/dist/alpha/icon-names.d.ts +6 -0
- package/dist/alpha/icon-names.d.ts.map +1 -0
- package/dist/alpha/icon-names.js +0 -0
- package/dist/alpha/json-schema.d.ts +117 -0
- package/dist/alpha/json-schema.d.ts.map +1 -0
- package/dist/alpha/json-schema.js +0 -0
- package/dist/alpha/manifest.d.ts +44 -0
- package/dist/alpha/manifest.d.ts.map +1 -0
- package/dist/alpha/manifest.js +0 -0
- package/dist/alpha/output.d.ts +7 -0
- package/dist/alpha/output.d.ts.map +1 -0
- package/dist/alpha/output.js +8 -0
- package/dist/alpha/pacer.d.ts +74 -0
- package/dist/alpha/pacer.d.ts.map +1 -0
- package/dist/alpha/pacer.js +66 -0
- package/dist/alpha/pacer.test.d.ts +2 -0
- package/dist/alpha/pacer.test.d.ts.map +1 -0
- package/dist/alpha/schedule.d.ts +6 -0
- package/dist/alpha/schedule.d.ts.map +1 -0
- package/dist/alpha/schedule.js +43 -0
- package/dist/alpha/schedule.test.d.ts +2 -0
- package/dist/alpha/schedule.test.d.ts.map +1 -0
- package/dist/alpha/schema-builder.d.ts +79 -0
- package/dist/alpha/schema-builder.d.ts.map +1 -0
- package/dist/alpha/schema-builder.js +135 -0
- package/dist/alpha/schema.d.ts +185 -0
- package/dist/alpha/schema.d.ts.map +1 -0
- package/dist/alpha/schema.js +115 -0
- package/dist/alpha/sync.d.ts +233 -0
- package/dist/alpha/sync.d.ts.map +1 -0
- package/dist/alpha/sync.js +70 -0
- package/dist/alpha/sync.test.d.ts +2 -0
- package/dist/alpha/sync.test.d.ts.map +1 -0
- package/dist/alpha/tool.d.ts +132 -0
- package/dist/alpha/tool.d.ts.map +1 -0
- package/dist/alpha/tool.js +156 -0
- package/dist/alpha/tool.test.d.ts +2 -0
- package/dist/alpha/tool.test.d.ts.map +1 -0
- package/dist/alpha/triggers.d.ts +365 -0
- package/dist/alpha/triggers.d.ts.map +1 -0
- package/dist/alpha/triggers.generated.d.ts +246 -0
- package/dist/alpha/triggers.generated.d.ts.map +1 -0
- package/dist/alpha/triggers.generated.js +239 -0
- package/dist/alpha/triggers.js +0 -0
- package/dist/alpha/types.d.ts +228 -0
- package/dist/alpha/types.d.ts.map +1 -0
- package/dist/alpha/types.js +0 -0
- package/dist/alpha/workflow.d.ts +70 -0
- package/dist/alpha/workflow.d.ts.map +1 -0
- package/dist/alpha/workflow.js +39 -0
- package/dist/alpha/workflow.test.d.ts +2 -0
- package/dist/alpha/workflow.test.d.ts.map +1 -0
- package/dist/builder.d.ts +7 -1
- package/dist/builder.d.ts.map +1 -1
- package/dist/builder.js +12 -3
- package/dist/capabilities/ai_connector.d.ts.map +1 -1
- package/dist/capabilities/automation.d.ts.map +1 -1
- package/dist/capabilities/context.d.ts.map +1 -1
- package/dist/capabilities/context.js +1 -1
- package/dist/capabilities/custom-block.d.ts +124 -0
- package/dist/capabilities/custom-block.d.ts.map +1 -0
- package/dist/capabilities/custom-block.js +22 -0
- package/dist/capabilities/oauth.d.ts.map +1 -1
- package/dist/capabilities/output.d.ts.map +1 -1
- package/dist/capabilities/output.js +2 -4
- package/dist/capabilities/stateful-capability.d.ts.map +1 -1
- package/dist/capabilities/stateful-capability.js +3 -7
- package/dist/capabilities/sync.d.ts +11 -1
- package/dist/capabilities/sync.d.ts.map +1 -1
- package/dist/capabilities/sync.js +2 -8
- package/dist/capabilities/tool.d.ts.map +1 -1
- package/dist/capabilities/tool.js +6 -20
- package/dist/capabilities/webhook.d.ts.map +1 -1
- package/dist/capabilities/workflow.d.ts +50 -0
- package/dist/capabilities/workflow.d.ts.map +1 -0
- package/dist/capabilities/workflow.js +40 -0
- package/dist/capabilities/workflow.test.d.ts +2 -0
- package/dist/capabilities/workflow.test.d.ts.map +1 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/json-schema.d.ts.map +1 -1
- package/dist/schema-builder.d.ts.map +1 -1
- package/dist/schema.d.ts.map +1 -1
- package/dist/triggers.d.ts +365 -0
- package/dist/triggers.d.ts.map +1 -0
- package/dist/triggers.generated.d.ts +246 -0
- package/dist/triggers.generated.d.ts.map +1 -0
- package/dist/triggers.generated.js +239 -0
- package/dist/triggers.js +0 -0
- package/dist/types.d.ts +18 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/worker.d.ts +72 -2
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +85 -4
- package/package.json +81 -14
- package/src/alpha/ajv-formats.d.ts +7 -0
- package/src/alpha/builder.test.ts +197 -0
- package/src/alpha/builder.ts +338 -0
- package/src/alpha/cli/build.test.ts +240 -0
- package/src/alpha/cli/build.ts +57 -0
- package/src/alpha/cli/codegen.test.ts +31 -0
- package/src/alpha/cli/codegen.ts +51 -0
- package/src/alpha/cli/discover.test.ts +43 -0
- package/src/alpha/cli/discover.ts +81 -0
- package/src/alpha/cli/emit-manifest.test.ts +45 -0
- package/src/alpha/cli/emit-manifest.ts +409 -0
- package/src/alpha/cli/index.ts +41 -0
- package/src/alpha/context.ts +61 -0
- package/src/alpha/database.ts +102 -0
- package/src/alpha/error.ts +49 -0
- package/src/alpha/icon-names.ts +890 -0
- package/src/alpha/json-schema.ts +180 -0
- package/src/alpha/manifest.ts +47 -0
- package/src/alpha/output.ts +8 -0
- package/src/alpha/pacer.test.ts +81 -0
- package/src/alpha/pacer.ts +181 -0
- package/src/alpha/schedule.test.ts +31 -0
- package/src/alpha/schedule.ts +53 -0
- package/src/alpha/schema-builder.ts +193 -0
- package/src/alpha/schema.ts +222 -0
- package/src/alpha/sync.test.ts +191 -0
- package/src/alpha/sync.ts +340 -0
- package/src/alpha/tool.test.ts +116 -0
- package/src/alpha/tool.ts +319 -0
- package/src/alpha/triggers.generated.ts +489 -0
- package/src/alpha/triggers.ts +500 -0
- package/src/alpha/types.ts +284 -0
- package/src/alpha/workflow.test.ts +84 -0
- package/src/alpha/workflow.ts +131 -0
- package/src/builder.test.ts +28 -60
- package/src/builder.ts +20 -7
- package/src/capabilities/ai_connector.test.ts +9 -32
- package/src/capabilities/ai_connector.ts +5 -17
- package/src/capabilities/automation.test.ts +3 -12
- package/src/capabilities/automation.ts +3 -11
- package/src/capabilities/context.ts +5 -8
- package/src/capabilities/custom-block.ts +181 -0
- package/src/capabilities/oauth.test.ts +1 -0
- package/src/capabilities/oauth.ts +1 -3
- package/src/capabilities/output.ts +1 -3
- package/src/capabilities/stateful-capability.test.ts +1 -0
- package/src/capabilities/stateful-capability.ts +3 -7
- package/src/capabilities/sync.test.ts +76 -42
- package/src/capabilities/sync.ts +16 -40
- package/src/capabilities/tool.test.ts +106 -146
- package/src/capabilities/tool.ts +17 -39
- package/src/capabilities/webhook.ts +3 -10
- package/src/capabilities/workflow.test.ts +163 -0
- package/src/capabilities/workflow.ts +112 -0
- package/src/index.ts +18 -6
- package/src/json-schema.test.ts +2 -4
- package/src/json-schema.ts +7 -6
- package/src/pacer.test.ts +1 -3
- package/src/pacer_internal.ts +1 -5
- package/src/schema-builder.test.ts +10 -35
- package/src/schema-builder.ts +6 -16
- package/src/schema.ts +2 -10
- package/src/triggers.generated.ts +489 -0
- package/src/triggers.ts +504 -0
- package/src/types.ts +21 -7
- package/src/worker.test.ts +147 -0
- package/src/worker.ts +155 -32
- package/dist/block.d.ts +0 -321
- package/dist/block.d.ts.map +0 -1
- package/src/block.ts +0 -529
- /package/dist/{block.js → alpha/ajv-formats.d.js} +0 -0
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import type { CapabilityContext } from "./context.js";
|
|
2
|
+
import { createCapabilityContext } from "./context.js";
|
|
3
|
+
import type { Database } from "./database.js";
|
|
4
|
+
import { ExecutionError, RateLimitError } from "./error.js";
|
|
5
|
+
import { writeOutput } from "./output.js";
|
|
6
|
+
import { initPacerState, pacerStates, type PacerEntry } from "./pacer.js";
|
|
7
|
+
import { parseSchedule } from "./schedule.js";
|
|
8
|
+
import type { PropertyConfiguration, PropertySchema, Schema } from "./schema.js";
|
|
9
|
+
import type {
|
|
10
|
+
Cover,
|
|
11
|
+
HandlerOptions,
|
|
12
|
+
Icon,
|
|
13
|
+
NormalizedSchedule,
|
|
14
|
+
PeopleValue,
|
|
15
|
+
PlaceValue,
|
|
16
|
+
RelationValue,
|
|
17
|
+
Schedule,
|
|
18
|
+
TextValue,
|
|
19
|
+
} from "./types.js";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Maps a property configuration to its corresponding value type.
|
|
23
|
+
*/
|
|
24
|
+
type PropertyValueType<T extends PropertyConfiguration> = T extends {
|
|
25
|
+
type: "people";
|
|
26
|
+
}
|
|
27
|
+
? PeopleValue
|
|
28
|
+
: T extends { type: "place" }
|
|
29
|
+
? PlaceValue
|
|
30
|
+
: T extends { type: "relation" }
|
|
31
|
+
? RelationValue
|
|
32
|
+
: TextValue;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Sync mode determines how the sync handles data lifecycle.
|
|
36
|
+
*/
|
|
37
|
+
export type SyncMode = "replace" | "incremental";
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* A change representing a record to be created or updated.
|
|
41
|
+
*/
|
|
42
|
+
export type SyncChangeUpsert<S extends PropertySchema = PropertySchema> = {
|
|
43
|
+
/** The type of change. Use `"upsert"` to create or update a record. */
|
|
44
|
+
type: "upsert";
|
|
45
|
+
/** A unique identifier for this record, used to match against existing pages. */
|
|
46
|
+
key: string;
|
|
47
|
+
/**
|
|
48
|
+
* The property values for this record.
|
|
49
|
+
* Keys must match the property names defined in the schema.
|
|
50
|
+
* Use the Builder helpers (e.g. `Builder.title()`, `Builder.richText()`)
|
|
51
|
+
* to create values.
|
|
52
|
+
*/
|
|
53
|
+
properties: { [Property in keyof S]: PropertyValueType<S[Property]> };
|
|
54
|
+
/**
|
|
55
|
+
* When this record was last updated in the upstream system.
|
|
56
|
+
* ISO 8601 string (e.g. `"2026-03-19T12:00:00Z"`).
|
|
57
|
+
* Used for conflict resolution when multiple syncs write to the same
|
|
58
|
+
* database. Recommended, but optional.
|
|
59
|
+
*/
|
|
60
|
+
upstreamUpdatedAt?: string;
|
|
61
|
+
/**
|
|
62
|
+
* Optional icon to use as the icon for this row's page.
|
|
63
|
+
* Use the `Builder.emojiIcon()`, `Builder.notionIcon()`, or
|
|
64
|
+
* `Builder.imageIcon()` helpers.
|
|
65
|
+
*/
|
|
66
|
+
icon?: Icon;
|
|
67
|
+
/**
|
|
68
|
+
* Optional cover to use as the cover for this row's page.
|
|
69
|
+
* Use the `Builder.imageCover()` helper.
|
|
70
|
+
*/
|
|
71
|
+
cover?: Cover;
|
|
72
|
+
/**
|
|
73
|
+
* Optional markdown content to add to the page body.
|
|
74
|
+
* This will be converted to Notion blocks and added as page content.
|
|
75
|
+
*/
|
|
76
|
+
pageContentMarkdown?: string;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* A change representing a record to be deleted.
|
|
81
|
+
* Only applicable when using `mode: "incremental"`.
|
|
82
|
+
*/
|
|
83
|
+
export type SyncChangeDelete = {
|
|
84
|
+
/** The type of change. Use `"delete"` to remove a record. */
|
|
85
|
+
type: "delete";
|
|
86
|
+
/** The unique identifier of the record to delete. */
|
|
87
|
+
key: string;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* A change to be applied to the synced database.
|
|
92
|
+
*/
|
|
93
|
+
export type SyncChange<S extends PropertySchema = PropertySchema> =
|
|
94
|
+
| SyncChangeUpsert<S>
|
|
95
|
+
| SyncChangeDelete;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Result returned from a sync handler.
|
|
99
|
+
*/
|
|
100
|
+
export type SyncHandlerResult<S extends PropertySchema = PropertySchema, State = unknown> = {
|
|
101
|
+
/** The batch of changes to apply in this execution. */
|
|
102
|
+
changes: SyncChange<S>[];
|
|
103
|
+
/**
|
|
104
|
+
* Indicates whether there is more data to fetch. When `true`, the
|
|
105
|
+
* handler is called again with `nextState`.
|
|
106
|
+
*
|
|
107
|
+
* @default false
|
|
108
|
+
*/
|
|
109
|
+
hasMore?: boolean;
|
|
110
|
+
/**
|
|
111
|
+
* Optional state data to pass to the next execution.
|
|
112
|
+
* Required if `hasMore` is `true`, ignored otherwise.
|
|
113
|
+
* This can be any type of data (cursor, page number, timestamp, etc.);
|
|
114
|
+
* the same data is provided as `state` in the next execution.
|
|
115
|
+
*/
|
|
116
|
+
nextState?: State;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Configuration passed to {@link createSync}.
|
|
121
|
+
*/
|
|
122
|
+
export type SyncConfiguration<S extends PropertySchema = PropertySchema, State = unknown> = {
|
|
123
|
+
/**
|
|
124
|
+
* The database to sync data into, declared with `createDatabase(...)`.
|
|
125
|
+
* Declare it inline for a database private to this sync, or import a
|
|
126
|
+
* shared instance that other syncs (and `Schema.relation(...)`) also
|
|
127
|
+
* reference.
|
|
128
|
+
*/
|
|
129
|
+
database: Database<S>;
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* How the sync handles data lifecycle:
|
|
133
|
+
* - "replace": Each run returns the complete dataset; unseen pages are deleted.
|
|
134
|
+
* - "incremental": Each run returns changes only; use delete markers to remove pages.
|
|
135
|
+
*
|
|
136
|
+
* @default "replace"
|
|
137
|
+
*/
|
|
138
|
+
mode?: SyncMode;
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* How often the sync should run.
|
|
142
|
+
* - "continuous": Run as frequently as the system allows
|
|
143
|
+
* - "manual": Only run when explicitly triggered
|
|
144
|
+
* - Interval string: Run at specified intervals, e.g. "1h", "30m", "1d"
|
|
145
|
+
*
|
|
146
|
+
* @default "30m"
|
|
147
|
+
*/
|
|
148
|
+
schedule?: Schedule;
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Fetches the data to sync from the third-party service.
|
|
152
|
+
*
|
|
153
|
+
* This function can return all data at once, or implement pagination by
|
|
154
|
+
* returning a batch of changes with `hasMore: true` and a `nextState`;
|
|
155
|
+
* the runtime calls it again with that state until `hasMore` is `false`.
|
|
156
|
+
*
|
|
157
|
+
* @param state - User-defined state from the previous execution (undefined on first call)
|
|
158
|
+
* @param context - Runtime context, including the Notion client
|
|
159
|
+
*/
|
|
160
|
+
handler: (
|
|
161
|
+
state: State | undefined,
|
|
162
|
+
context: CapabilityContext,
|
|
163
|
+
) => Promise<SyncHandlerResult<S, State>>;
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* The pre-manifest configuration of a sync: everything from
|
|
168
|
+
* {@link SyncConfiguration} except the handler, with the schedule
|
|
169
|
+
* normalized for the backend and the database's schema type erased.
|
|
170
|
+
*
|
|
171
|
+
* The `database` here is still an object reference; the build resolves it
|
|
172
|
+
* to the database's *name* when emitting the manifest.
|
|
173
|
+
*/
|
|
174
|
+
export type SyncManifestConfig = {
|
|
175
|
+
database: {
|
|
176
|
+
_tag: "database";
|
|
177
|
+
name: string;
|
|
178
|
+
config: { type: "managed"; initialTitle?: string; primaryKey: string; schema: Schema };
|
|
179
|
+
};
|
|
180
|
+
mode: SyncMode | undefined;
|
|
181
|
+
schedule: NormalizedSchedule | undefined;
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* The runtime context the platform passes when invoking a sync.
|
|
186
|
+
*/
|
|
187
|
+
export type SyncRuntimeContext = {
|
|
188
|
+
/** The user-defined state (cursor, pagination state, etc.). */
|
|
189
|
+
state?: unknown;
|
|
190
|
+
/** Legacy field for user-defined state. */
|
|
191
|
+
userContext?: unknown;
|
|
192
|
+
/** Pacer state from the server for rate limiting. */
|
|
193
|
+
pacers?: Record<string, PacerEntry>;
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* The result a sync run reports to the platform, via the notion_output
|
|
198
|
+
* envelope on stdout (or directly, under `concreteOutput`). This is the
|
|
199
|
+
* same wire shape v1 syncs produce.
|
|
200
|
+
*/
|
|
201
|
+
export type SyncRunResult = {
|
|
202
|
+
changes: (SyncChange & { targetDatabaseKey: string })[];
|
|
203
|
+
hasMore: boolean;
|
|
204
|
+
nextUserContext?: unknown;
|
|
205
|
+
nextPacerStates: Record<string, PacerEntry>;
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
type SyncErrorEnvelope =
|
|
209
|
+
| { _tag: "error"; error: { _tag: "generic"; name: string; message: string } }
|
|
210
|
+
| {
|
|
211
|
+
_tag: "error";
|
|
212
|
+
error: { _tag: "rate_limit"; name: string; message: string; retryAfter?: number };
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* A sync capability as returned by {@link createSync}.
|
|
217
|
+
*
|
|
218
|
+
* Note that a sync carries no key of its own — the build tool derives the
|
|
219
|
+
* capability key from the file the sync is default-exported from.
|
|
220
|
+
*/
|
|
221
|
+
export type Sync = {
|
|
222
|
+
_tag: "sync";
|
|
223
|
+
config: SyncManifestConfig;
|
|
224
|
+
handler: (
|
|
225
|
+
runtimeContext?: SyncRuntimeContext,
|
|
226
|
+
options?: HandlerOptions,
|
|
227
|
+
) => Promise<SyncRunResult>;
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Create a sync capability.
|
|
232
|
+
*
|
|
233
|
+
* Default-export the result from a file directly under `src/syncs/` —
|
|
234
|
+
* the file's basename becomes the sync's key.
|
|
235
|
+
*
|
|
236
|
+
* @example
|
|
237
|
+
* ```ts
|
|
238
|
+
* // src/syncs/tasks.ts
|
|
239
|
+
* import { createDatabase } from "@notionhq/workers/alpha/database";
|
|
240
|
+
* import { Schema } from "@notionhq/workers/alpha/schema";
|
|
241
|
+
* import { createSync } from "@notionhq/workers/alpha/sync";
|
|
242
|
+
*
|
|
243
|
+
* export default createSync({
|
|
244
|
+
* database: createDatabase("tasks", {
|
|
245
|
+
* type: "managed",
|
|
246
|
+
* primaryKey: "Task ID",
|
|
247
|
+
* schema: {
|
|
248
|
+
* properties: {
|
|
249
|
+
* Title: Schema.title(),
|
|
250
|
+
* "Task ID": Schema.richText(),
|
|
251
|
+
* },
|
|
252
|
+
* },
|
|
253
|
+
* }),
|
|
254
|
+
* mode: "replace",
|
|
255
|
+
* schedule: "24h",
|
|
256
|
+
* handler: async () => ({
|
|
257
|
+
* changes: [
|
|
258
|
+
* {
|
|
259
|
+
* type: "upsert",
|
|
260
|
+
* key: "1",
|
|
261
|
+
* properties: { Title: [["Item 1"]], "Task ID": [["1"]] },
|
|
262
|
+
* },
|
|
263
|
+
* ],
|
|
264
|
+
* }),
|
|
265
|
+
* });
|
|
266
|
+
* ```
|
|
267
|
+
*/
|
|
268
|
+
export function createSync<S extends PropertySchema, State = unknown>(
|
|
269
|
+
configuration: SyncConfiguration<S, State>,
|
|
270
|
+
): Sync {
|
|
271
|
+
return {
|
|
272
|
+
_tag: "sync",
|
|
273
|
+
config: {
|
|
274
|
+
database: configuration.database,
|
|
275
|
+
mode: configuration.mode,
|
|
276
|
+
schedule: configuration.schedule ? parseSchedule(configuration.schedule) : undefined,
|
|
277
|
+
},
|
|
278
|
+
async handler(runtimeContext?: SyncRuntimeContext, options?: HandlerOptions) {
|
|
279
|
+
// The platform guarantees the state it passes back is whatever the
|
|
280
|
+
// previous run returned as nextState.
|
|
281
|
+
const state = (runtimeContext?.state ?? runtimeContext?.userContext) as State | undefined;
|
|
282
|
+
|
|
283
|
+
initPacerState(runtimeContext?.pacers);
|
|
284
|
+
|
|
285
|
+
let handlerResult: SyncHandlerResult<S, State>;
|
|
286
|
+
try {
|
|
287
|
+
handlerResult = await configuration.handler(state, createCapabilityContext("sync"));
|
|
288
|
+
} catch (err) {
|
|
289
|
+
if (err instanceof RateLimitError) {
|
|
290
|
+
if (!options?.concreteOutput) {
|
|
291
|
+
const envelope: SyncErrorEnvelope = {
|
|
292
|
+
_tag: "error",
|
|
293
|
+
error: {
|
|
294
|
+
_tag: "rate_limit",
|
|
295
|
+
name: err.name,
|
|
296
|
+
message: err.message,
|
|
297
|
+
...(err.retryAfter !== undefined ? { retryAfter: err.retryAfter } : {}),
|
|
298
|
+
},
|
|
299
|
+
};
|
|
300
|
+
writeOutput(envelope);
|
|
301
|
+
}
|
|
302
|
+
throw err;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const error = new ExecutionError(err);
|
|
306
|
+
if (!options?.concreteOutput) {
|
|
307
|
+
const envelope: SyncErrorEnvelope = {
|
|
308
|
+
_tag: "error",
|
|
309
|
+
error: {
|
|
310
|
+
_tag: "generic",
|
|
311
|
+
name: error.name,
|
|
312
|
+
message: error.message,
|
|
313
|
+
},
|
|
314
|
+
};
|
|
315
|
+
writeOutput(envelope);
|
|
316
|
+
}
|
|
317
|
+
throw error;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const changes = handlerResult.changes.map((change) => ({
|
|
321
|
+
...change,
|
|
322
|
+
targetDatabaseKey: configuration.database.name,
|
|
323
|
+
}));
|
|
324
|
+
|
|
325
|
+
const result: SyncRunResult = {
|
|
326
|
+
changes,
|
|
327
|
+
hasMore: handlerResult.hasMore ?? false,
|
|
328
|
+
nextUserContext: handlerResult.nextState,
|
|
329
|
+
nextPacerStates: pacerStates(),
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
if (options?.concreteOutput) {
|
|
333
|
+
return result;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
writeOutput({ _tag: "success", value: result });
|
|
337
|
+
return result;
|
|
338
|
+
},
|
|
339
|
+
};
|
|
340
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi, type Mock } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { j } from "./schema-builder.js";
|
|
4
|
+
import { createTool, InvalidToolInputError, ToolExecutionError } from "./tool.js";
|
|
5
|
+
|
|
6
|
+
let stdoutSpy: Mock<typeof process.stdout.write>;
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
stdoutSpy = vi.spyOn(process.stdout, "write").mockImplementation(() => true);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
stdoutSpy.mockRestore();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const sayHello = createTool({
|
|
17
|
+
title: "Say Hello",
|
|
18
|
+
description: "Say hello to the user",
|
|
19
|
+
schema: j.object({ name: j.string() }),
|
|
20
|
+
execute: ({ name }) => `Hello, ${name}!`,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe("createTool", () => {
|
|
24
|
+
it("is tagged and exposes resolved schemas in config", () => {
|
|
25
|
+
expect(sayHello._tag).toBe("tool");
|
|
26
|
+
expect(sayHello.config).toEqual({
|
|
27
|
+
title: "Say Hello",
|
|
28
|
+
description: "Say hello to the user",
|
|
29
|
+
schema: {
|
|
30
|
+
type: "object",
|
|
31
|
+
properties: { name: { type: "string" } },
|
|
32
|
+
required: ["name"],
|
|
33
|
+
additionalProperties: false,
|
|
34
|
+
},
|
|
35
|
+
outputSchema: undefined,
|
|
36
|
+
hints: undefined,
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("writes a success envelope and returns the tagged result", async () => {
|
|
41
|
+
const result = await sayHello.handler({ name: "Alice" });
|
|
42
|
+
|
|
43
|
+
expect(result).toEqual({ _tag: "success", value: "Hello, Alice!" });
|
|
44
|
+
expect(stdoutSpy).toHaveBeenCalledWith(
|
|
45
|
+
`\n<__notion_output__>{"_tag":"success","value":"Hello, Alice!"}</__notion_output__>\n`,
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("returns the raw result under concreteOutput", async () => {
|
|
50
|
+
const result = await sayHello.handler({ name: "Alice" }, { concreteOutput: true });
|
|
51
|
+
|
|
52
|
+
expect(result).toBe("Hello, Alice!");
|
|
53
|
+
expect(stdoutSpy).not.toHaveBeenCalled();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("rejects input that does not match the schema", async () => {
|
|
57
|
+
const result = await sayHello.handler({ name: 42 } as never);
|
|
58
|
+
|
|
59
|
+
expect(result).toMatchObject({
|
|
60
|
+
_tag: "error",
|
|
61
|
+
error: { name: "InvalidToolInputError" },
|
|
62
|
+
});
|
|
63
|
+
expect(stdoutSpy).toHaveBeenCalledWith(
|
|
64
|
+
expect.stringContaining(
|
|
65
|
+
`<__notion_output__>{"_tag":"error","error":{"name":"InvalidToolInputError"`,
|
|
66
|
+
),
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
await expect(
|
|
70
|
+
sayHello.handler({ name: 42 } as never, { concreteOutput: true }),
|
|
71
|
+
).rejects.toBeInstanceOf(InvalidToolInputError);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("rejects output that does not match the output schema", async () => {
|
|
75
|
+
const badOutput = createTool({
|
|
76
|
+
title: "Bad Output",
|
|
77
|
+
description: "Returns output that fails validation",
|
|
78
|
+
schema: j.object({ id: j.number() }),
|
|
79
|
+
outputSchema: j.object({ data: j.string() }),
|
|
80
|
+
execute: () => ({ data: 42 }) as never,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const result = await badOutput.handler({ id: 1 });
|
|
84
|
+
expect(result).toMatchObject({
|
|
85
|
+
_tag: "error",
|
|
86
|
+
error: { name: "InvalidToolOutputError" },
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Mirrors v1: under concreteOutput the output-validation throw happens
|
|
90
|
+
// inside the try block, so it surfaces wrapped in ToolExecutionError.
|
|
91
|
+
await expect(badOutput.handler({ id: 1 }, { concreteOutput: true })).rejects.toBeInstanceOf(
|
|
92
|
+
ToolExecutionError,
|
|
93
|
+
);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("wraps execution failures in ToolExecutionError", async () => {
|
|
97
|
+
const explodes = createTool({
|
|
98
|
+
title: "Explodes",
|
|
99
|
+
description: "Always throws",
|
|
100
|
+
schema: j.object({}),
|
|
101
|
+
execute: () => {
|
|
102
|
+
throw new Error("boom");
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const result = await explodes.handler({});
|
|
107
|
+
expect(result).toMatchObject({
|
|
108
|
+
_tag: "error",
|
|
109
|
+
error: { name: "ToolExecutionError", message: "boom" },
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
await expect(explodes.handler({}, { concreteOutput: true })).rejects.toBeInstanceOf(
|
|
113
|
+
ToolExecutionError,
|
|
114
|
+
);
|
|
115
|
+
});
|
|
116
|
+
});
|