@notionhq/workers 0.2.0 → 0.3.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/capabilities/ai_connector.d.ts +97 -0
- package/dist/capabilities/ai_connector.d.ts.map +1 -0
- package/dist/capabilities/ai_connector.js +54 -0
- package/dist/capabilities/ai_connector.test.d.ts +2 -0
- package/dist/capabilities/ai_connector.test.d.ts.map +1 -0
- package/dist/capabilities/automation.d.ts.map +1 -1
- package/dist/capabilities/automation.js +10 -10
- package/dist/capabilities/output.d.ts +5 -0
- package/dist/capabilities/output.d.ts.map +1 -0
- package/dist/capabilities/output.js +10 -0
- package/dist/capabilities/stateful-capability.d.ts +30 -0
- package/dist/capabilities/stateful-capability.d.ts.map +1 -0
- package/dist/capabilities/stateful-capability.js +50 -0
- package/dist/capabilities/stateful-capability.test.d.ts +2 -0
- package/dist/capabilities/stateful-capability.test.d.ts.map +1 -0
- package/dist/capabilities/sync.d.ts +9 -19
- package/dist/capabilities/sync.d.ts.map +1 -1
- package/dist/capabilities/sync.js +13 -60
- package/dist/capabilities/tool.d.ts.map +1 -1
- package/dist/capabilities/tool.js +5 -20
- package/dist/capabilities/webhook.d.ts +101 -0
- package/dist/capabilities/webhook.d.ts.map +1 -0
- package/dist/capabilities/webhook.js +47 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/pacer_internal.d.ts +7 -0
- package/dist/pacer_internal.d.ts.map +1 -1
- package/dist/pacer_internal.js +17 -0
- package/dist/schema.d.ts +28 -11
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +2 -2
- package/dist/worker.d.ts +91 -9
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +111 -1
- package/package.json +1 -1
- package/src/capabilities/ai_connector.test.ts +341 -0
- package/src/capabilities/ai_connector.ts +194 -0
- package/src/capabilities/automation.ts +10 -6
- package/src/capabilities/output.ts +8 -0
- package/src/capabilities/stateful-capability.test.ts +25 -0
- package/src/capabilities/stateful-capability.ts +87 -0
- package/src/capabilities/sync.test.ts +89 -3
- package/src/capabilities/sync.ts +22 -86
- package/src/capabilities/tool.ts +5 -12
- package/src/capabilities/webhook.ts +148 -0
- package/src/index.ts +22 -0
- package/src/pacer_internal.ts +34 -0
- package/src/schema.ts +29 -12
- package/src/worker.ts +137 -10
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
} from "vitest";
|
|
10
10
|
import * as Builder from "../builder.js";
|
|
11
11
|
import { ExecutionError } from "../error.js";
|
|
12
|
+
import { getPacerState } from "../pacer_internal.js";
|
|
12
13
|
import * as Schema from "../schema.js";
|
|
13
14
|
import { Worker } from "../worker.js";
|
|
14
15
|
import { createSyncCapability } from "./sync.js";
|
|
@@ -28,11 +29,11 @@ describe("Worker.sync", () => {
|
|
|
28
29
|
|
|
29
30
|
describe("Schema.relation", () => {
|
|
30
31
|
it("creates a relation property configuration", () => {
|
|
31
|
-
const relationProp = Schema.relation("
|
|
32
|
+
const relationProp = Schema.relation("projects");
|
|
32
33
|
|
|
33
34
|
expect(relationProp).toEqual({
|
|
34
35
|
type: "relation",
|
|
35
|
-
|
|
36
|
+
relatedDatabaseKey: "projects",
|
|
36
37
|
config: { twoWay: false },
|
|
37
38
|
});
|
|
38
39
|
});
|
|
@@ -68,7 +69,7 @@ describe("Worker.sync", () => {
|
|
|
68
69
|
schema: {
|
|
69
70
|
properties: {
|
|
70
71
|
"Task ID": Schema.title(),
|
|
71
|
-
Project: Schema.relation("
|
|
72
|
+
Project: Schema.relation("projects"),
|
|
72
73
|
},
|
|
73
74
|
},
|
|
74
75
|
});
|
|
@@ -106,6 +107,91 @@ describe("Worker.sync", () => {
|
|
|
106
107
|
});
|
|
107
108
|
});
|
|
108
109
|
|
|
110
|
+
describe("pacer local fallback", () => {
|
|
111
|
+
it("seeds pacer state from declarations when runtime context has no pacers", async () => {
|
|
112
|
+
const worker = new Worker();
|
|
113
|
+
const db = worker.database("test", {
|
|
114
|
+
type: "managed",
|
|
115
|
+
initialTitle: "Test",
|
|
116
|
+
primaryKeyProperty: "ID",
|
|
117
|
+
schema: { properties: { ID: Schema.title() } },
|
|
118
|
+
});
|
|
119
|
+
const pacer = worker.pacer("myApi", {
|
|
120
|
+
allowedRequests: 10,
|
|
121
|
+
intervalMs: 1000,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
let pacerWaitCalled = false;
|
|
125
|
+
const capability = createSyncCapability(
|
|
126
|
+
"testSync",
|
|
127
|
+
{
|
|
128
|
+
database: db,
|
|
129
|
+
execute: async () => {
|
|
130
|
+
await pacer.wait();
|
|
131
|
+
pacerWaitCalled = true;
|
|
132
|
+
return { changes: [], hasMore: false };
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
[{ key: "myApi", config: { allowedRequests: 10, intervalMs: 1000 } }],
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
// No runtimeContext → no pacers from server → should fall back to declarations
|
|
139
|
+
await capability.handler(undefined, { concreteOutput: true });
|
|
140
|
+
|
|
141
|
+
expect(pacerWaitCalled).toBe(true);
|
|
142
|
+
const state = getPacerState();
|
|
143
|
+
expect(state.pacers.myApi).toBeDefined();
|
|
144
|
+
expect(state.pacers.myApi?.allowedRequests).toBe(10);
|
|
145
|
+
expect(state.pacers.myApi?.intervalMs).toBe(1000);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("uses server-provided pacers when available", async () => {
|
|
149
|
+
const worker = new Worker();
|
|
150
|
+
const db = worker.database("test", {
|
|
151
|
+
type: "managed",
|
|
152
|
+
initialTitle: "Test",
|
|
153
|
+
primaryKeyProperty: "ID",
|
|
154
|
+
schema: { properties: { ID: Schema.title() } },
|
|
155
|
+
});
|
|
156
|
+
const pacer = worker.pacer("myApi", {
|
|
157
|
+
allowedRequests: 10,
|
|
158
|
+
intervalMs: 1000,
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
let pacerWaitCalled = false;
|
|
162
|
+
const capability = createSyncCapability(
|
|
163
|
+
"testSync",
|
|
164
|
+
{
|
|
165
|
+
database: db,
|
|
166
|
+
execute: async () => {
|
|
167
|
+
await pacer.wait();
|
|
168
|
+
pacerWaitCalled = true;
|
|
169
|
+
return { changes: [], hasMore: false };
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
[{ key: "myApi", config: { allowedRequests: 10, intervalMs: 1000 } }],
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
// Server provides apportioned budget (e.g., 5 instead of 10)
|
|
176
|
+
await capability.handler(
|
|
177
|
+
{
|
|
178
|
+
pacers: {
|
|
179
|
+
myApi: {
|
|
180
|
+
lastScheduledAtMs: 0,
|
|
181
|
+
allowedRequests: 5,
|
|
182
|
+
intervalMs: 1000,
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
{ concreteOutput: true },
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
expect(pacerWaitCalled).toBe(true);
|
|
190
|
+
// Should use server-provided value, not the declaration
|
|
191
|
+
expect(getPacerState().pacers.myApi?.allowedRequests).toBe(5);
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
109
195
|
describe("handler output envelope", () => {
|
|
110
196
|
it("wraps successful result in success envelope", async () => {
|
|
111
197
|
const worker = new Worker();
|
package/src/capabilities/sync.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { ExecutionError
|
|
1
|
+
import { ExecutionError } from "../error.js";
|
|
2
2
|
import {
|
|
3
3
|
getPacerState,
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
initPacerState,
|
|
5
|
+
type PacerDeclaration,
|
|
6
6
|
} from "../pacer_internal.js";
|
|
7
7
|
import type {
|
|
8
8
|
PropertyConfiguration,
|
|
@@ -16,13 +16,17 @@ import type {
|
|
|
16
16
|
PlaceValue,
|
|
17
17
|
RelationValue,
|
|
18
18
|
Schedule,
|
|
19
|
-
SyncSchedule,
|
|
20
19
|
TextValue,
|
|
21
|
-
TimeUnit,
|
|
22
20
|
} from "../types.js";
|
|
23
21
|
import type { DatabaseHandle } from "../worker.js";
|
|
24
22
|
import type { CapabilityContext } from "./context.js";
|
|
25
23
|
import { createCapabilityContext } from "./context.js";
|
|
24
|
+
import { writeOutput } from "./output.js";
|
|
25
|
+
import {
|
|
26
|
+
parseSchedule,
|
|
27
|
+
type StatefulCapabilityMode,
|
|
28
|
+
type RuntimeContext as StatefulRuntimeContext,
|
|
29
|
+
} from "./stateful-capability.js";
|
|
26
30
|
|
|
27
31
|
/**
|
|
28
32
|
* Maps a property configuration to its corresponding value type.
|
|
@@ -40,7 +44,7 @@ type PropertyValueType<T extends PropertyConfiguration> = T extends {
|
|
|
40
44
|
/**
|
|
41
45
|
* Sync mode determines how the sync handles data lifecycle.
|
|
42
46
|
*/
|
|
43
|
-
export type SyncMode =
|
|
47
|
+
export type SyncMode = StatefulCapabilityMode;
|
|
44
48
|
|
|
45
49
|
/**
|
|
46
50
|
* A change representing a record to be created or updated.
|
|
@@ -204,18 +208,6 @@ export type SyncConfiguration<
|
|
|
204
208
|
|
|
205
209
|
export type SyncCapability = ReturnType<typeof createSyncCapability>;
|
|
206
210
|
|
|
207
|
-
/**
|
|
208
|
-
* Runtime context object passed from the runtime to sync capability handlers.
|
|
209
|
-
*/
|
|
210
|
-
type RuntimeContext<UserContext = unknown> = {
|
|
211
|
-
/** The user-defined/-controlled state (cursor, pagination state, etc.) */
|
|
212
|
-
state?: UserContext;
|
|
213
|
-
/** Legacy field for user-defined/-controlled state. */
|
|
214
|
-
userContext?: UserContext;
|
|
215
|
-
/** Pacer state from the server for rate limiting. */
|
|
216
|
-
pacers?: Record<string, PacerEntry>;
|
|
217
|
-
};
|
|
218
|
-
|
|
219
211
|
/**
|
|
220
212
|
* Creates a special handler for syncing third-party data to a database.
|
|
221
213
|
*
|
|
@@ -227,7 +219,11 @@ export function createSyncCapability<
|
|
|
227
219
|
PK extends string,
|
|
228
220
|
S extends Schema<PK>,
|
|
229
221
|
Context = unknown,
|
|
230
|
-
>(
|
|
222
|
+
>(
|
|
223
|
+
key: string,
|
|
224
|
+
syncConfiguration: SyncConfiguration<PK, S, Context>,
|
|
225
|
+
pacerDeclarations?: readonly PacerDeclaration[],
|
|
226
|
+
) {
|
|
231
227
|
return {
|
|
232
228
|
_tag: "sync" as const,
|
|
233
229
|
key,
|
|
@@ -240,14 +236,13 @@ export function createSyncCapability<
|
|
|
240
236
|
: undefined,
|
|
241
237
|
},
|
|
242
238
|
async handler(
|
|
243
|
-
runtimeContext?:
|
|
239
|
+
runtimeContext?: StatefulRuntimeContext<Context>,
|
|
244
240
|
options?: HandlerOptions,
|
|
245
241
|
) {
|
|
246
242
|
const capabilityContext = createCapabilityContext();
|
|
247
243
|
const state = runtimeContext?.state ?? runtimeContext?.userContext;
|
|
248
244
|
|
|
249
|
-
|
|
250
|
-
setPacerState({ pacers: runtimeContext?.pacers ?? {} });
|
|
245
|
+
initPacerState(runtimeContext?.pacers, pacerDeclarations);
|
|
251
246
|
|
|
252
247
|
let executionResult: SyncExecutionResult<PK, Context>;
|
|
253
248
|
try {
|
|
@@ -258,9 +253,10 @@ export function createSyncCapability<
|
|
|
258
253
|
} catch (err) {
|
|
259
254
|
const error = new ExecutionError(err);
|
|
260
255
|
if (!options?.concreteOutput) {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
256
|
+
writeOutput({
|
|
257
|
+
_tag: "error",
|
|
258
|
+
error: { name: error.name, message: error.message },
|
|
259
|
+
});
|
|
264
260
|
}
|
|
265
261
|
throw error;
|
|
266
262
|
}
|
|
@@ -281,70 +277,10 @@ export function createSyncCapability<
|
|
|
281
277
|
if (options?.concreteOutput) {
|
|
282
278
|
return result;
|
|
283
279
|
} else {
|
|
284
|
-
|
|
285
|
-
`\n<__notion_output__>${JSON.stringify({ _tag: "success", value: result })}</__notion_output__>\n`,
|
|
286
|
-
);
|
|
280
|
+
writeOutput({ _tag: "success", value: result });
|
|
287
281
|
}
|
|
288
282
|
|
|
289
283
|
return result;
|
|
290
284
|
},
|
|
291
285
|
};
|
|
292
286
|
}
|
|
293
|
-
|
|
294
|
-
const MS_PER_MINUTE = 60 * 1000;
|
|
295
|
-
const MS_PER_HOUR = 60 * MS_PER_MINUTE;
|
|
296
|
-
const MS_PER_DAY = 24 * MS_PER_HOUR;
|
|
297
|
-
|
|
298
|
-
const MIN_INTERVAL_MS = MS_PER_MINUTE; // 1m
|
|
299
|
-
const MAX_INTERVAL_MS = 7 * MS_PER_DAY; // 7d
|
|
300
|
-
|
|
301
|
-
/**
|
|
302
|
-
* Parses a user-friendly schedule string into the normalized backend format.
|
|
303
|
-
*/
|
|
304
|
-
function parseSchedule(schedule: Schedule): SyncSchedule {
|
|
305
|
-
if (schedule === "continuous") {
|
|
306
|
-
return { type: "continuous" };
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
if (schedule === "manual") {
|
|
310
|
-
return { type: "manual" };
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
const match = schedule.match(/^(\d+)(m|h|d)$/);
|
|
314
|
-
if (!match || !match[1] || !match[2]) {
|
|
315
|
-
throw new Error(
|
|
316
|
-
`Invalid schedule format: "${schedule}". Use "continuous" or an interval like "30m", "1h", "1d".`,
|
|
317
|
-
);
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
const value = parseInt(match[1], 10);
|
|
321
|
-
const unit = match[2] as TimeUnit;
|
|
322
|
-
|
|
323
|
-
let intervalMs: number;
|
|
324
|
-
switch (unit) {
|
|
325
|
-
case "m":
|
|
326
|
-
intervalMs = value * MS_PER_MINUTE;
|
|
327
|
-
break;
|
|
328
|
-
case "h":
|
|
329
|
-
intervalMs = value * MS_PER_HOUR;
|
|
330
|
-
break;
|
|
331
|
-
case "d":
|
|
332
|
-
intervalMs = value * MS_PER_DAY;
|
|
333
|
-
break;
|
|
334
|
-
default:
|
|
335
|
-
unreachable(unit);
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
if (intervalMs < MIN_INTERVAL_MS) {
|
|
339
|
-
throw new Error(
|
|
340
|
-
`Schedule interval must be at least 1 minute. Got: "${schedule}"`,
|
|
341
|
-
);
|
|
342
|
-
}
|
|
343
|
-
if (intervalMs > MAX_INTERVAL_MS) {
|
|
344
|
-
throw new Error(
|
|
345
|
-
`Schedule interval must be at most 7 days. Got: "${schedule}"`,
|
|
346
|
-
);
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
return { type: "interval", intervalMs };
|
|
350
|
-
}
|
package/src/capabilities/tool.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { getSchema } from "../schema-builder.js";
|
|
|
6
6
|
import type { HandlerOptions, JSONValue } from "../types.js";
|
|
7
7
|
import type { CapabilityContext } from "./context.js";
|
|
8
8
|
import { createCapabilityContext } from "./context.js";
|
|
9
|
+
import { writeOutput } from "./output.js";
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Validates that every object schema in the tree has all its `properties`
|
|
@@ -211,9 +212,7 @@ export function createToolCapability<
|
|
|
211
212
|
error: { name: error.name, message: error.message, trace: error.stack },
|
|
212
213
|
};
|
|
213
214
|
|
|
214
|
-
|
|
215
|
-
`\n<__notion_output__>${JSON.stringify(result)}</__notion_output__>\n`,
|
|
216
|
-
);
|
|
215
|
+
writeOutput(result);
|
|
217
216
|
|
|
218
217
|
return result;
|
|
219
218
|
}
|
|
@@ -239,9 +238,7 @@ export function createToolCapability<
|
|
|
239
238
|
},
|
|
240
239
|
};
|
|
241
240
|
|
|
242
|
-
|
|
243
|
-
`\n<__notion_output__>${JSON.stringify(result)}</__notion_output__>\n`,
|
|
244
|
-
);
|
|
241
|
+
writeOutput(result);
|
|
245
242
|
|
|
246
243
|
return result;
|
|
247
244
|
}
|
|
@@ -250,9 +247,7 @@ export function createToolCapability<
|
|
|
250
247
|
return result;
|
|
251
248
|
}
|
|
252
249
|
|
|
253
|
-
|
|
254
|
-
`\n<__notion_output__>${JSON.stringify({ _tag: "success", value: result })}</__notion_output__>\n`,
|
|
255
|
-
);
|
|
250
|
+
writeOutput({ _tag: "success", value: result });
|
|
256
251
|
|
|
257
252
|
return {
|
|
258
253
|
_tag: "success" as const,
|
|
@@ -272,9 +267,7 @@ export function createToolCapability<
|
|
|
272
267
|
error: { name: error.name, message: error.message, trace: error.stack },
|
|
273
268
|
};
|
|
274
269
|
|
|
275
|
-
|
|
276
|
-
`\n<__notion_output__>${JSON.stringify(result)}</__notion_output__>\n`,
|
|
277
|
-
);
|
|
270
|
+
writeOutput(result);
|
|
278
271
|
|
|
279
272
|
return result;
|
|
280
273
|
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { ExecutionError } from "../error.js";
|
|
2
|
+
import type { HandlerOptions } from "../types.js";
|
|
3
|
+
import type { CapabilityContext } from "./context.js";
|
|
4
|
+
import { createCapabilityContext } from "./context.js";
|
|
5
|
+
import { writeOutput } from "./output.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A single incoming webhook request.
|
|
9
|
+
*/
|
|
10
|
+
export interface WebhookEvent {
|
|
11
|
+
/**
|
|
12
|
+
* Unique ID for this webhook delivery, stable across retries.
|
|
13
|
+
* Best-effort idempotency key — prefer using the webhook provider's
|
|
14
|
+
* own delivery/event ID when available, since providers may redeliver
|
|
15
|
+
* the same event with a new deliveryId.
|
|
16
|
+
*/
|
|
17
|
+
deliveryId: string;
|
|
18
|
+
/**
|
|
19
|
+
* The parsed JSON body of the incoming request, or an empty object if
|
|
20
|
+
* the body is not valid JSON.
|
|
21
|
+
*/
|
|
22
|
+
body: Record<string, unknown>;
|
|
23
|
+
/**
|
|
24
|
+
* The raw request body as a string, useful for signature verification.
|
|
25
|
+
*/
|
|
26
|
+
rawBody: string;
|
|
27
|
+
/**
|
|
28
|
+
* The HTTP headers from the incoming request
|
|
29
|
+
*/
|
|
30
|
+
headers: Record<string, string>;
|
|
31
|
+
/**
|
|
32
|
+
* The HTTP method of the incoming request (e.g. "POST")
|
|
33
|
+
*/
|
|
34
|
+
method: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Throw this error from your webhook execute handler to signal that
|
|
39
|
+
* signature verification failed. After 5 consecutive verification
|
|
40
|
+
* failures, the platform will short-circuit and reject all incoming
|
|
41
|
+
* requests for this webhook without executing the handler.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```ts
|
|
45
|
+
* worker.webhook("onGithubPush", {
|
|
46
|
+
* title: "GitHub Push",
|
|
47
|
+
* description: "Handles GitHub push events",
|
|
48
|
+
* execute: async (events) => {
|
|
49
|
+
* const secret = process.env.GITHUB_WEBHOOK_SECRET;
|
|
50
|
+
* if (!secret) {
|
|
51
|
+
* throw new WebhookVerificationError("GITHUB_WEBHOOK_SECRET not set");
|
|
52
|
+
* }
|
|
53
|
+
* for (const event of events) {
|
|
54
|
+
* if (!verifyGitHubSignature(event.rawBody, event.headers, secret)) {
|
|
55
|
+
* throw new WebhookVerificationError("Invalid GitHub signature");
|
|
56
|
+
* }
|
|
57
|
+
* // ... handle verified event
|
|
58
|
+
* }
|
|
59
|
+
* },
|
|
60
|
+
* });
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
export class WebhookVerificationError extends ExecutionError {
|
|
64
|
+
constructor(cause?: unknown) {
|
|
65
|
+
super(cause ?? "Webhook signature verification failed");
|
|
66
|
+
this.name = "WebhookVerificationError";
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Configuration for a webhook capability
|
|
72
|
+
*/
|
|
73
|
+
export interface WebhookConfiguration {
|
|
74
|
+
/**
|
|
75
|
+
* Title of the webhook - shown in the UI when viewing webhooks
|
|
76
|
+
*/
|
|
77
|
+
title: string;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Description of what this webhook does - shown in the UI
|
|
81
|
+
*/
|
|
82
|
+
description: string;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* The function that executes when the webhook is triggered.
|
|
86
|
+
* Receives an array of events (currently always a single event,
|
|
87
|
+
* but may be batched in the future).
|
|
88
|
+
*
|
|
89
|
+
* To signal a verification failure, throw a `WebhookVerificationError`.
|
|
90
|
+
* After 5 consecutive verification failures, the platform will
|
|
91
|
+
* short-circuit and stop executing the handler.
|
|
92
|
+
*
|
|
93
|
+
* @param events - The incoming webhook events
|
|
94
|
+
* @param context - The capability execution context (Notion client, etc.)
|
|
95
|
+
* @returns A promise that resolves when the webhook processing completes
|
|
96
|
+
*/
|
|
97
|
+
execute: (
|
|
98
|
+
events: WebhookEvent[],
|
|
99
|
+
context: CapabilityContext,
|
|
100
|
+
) => Promise<void> | void;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export type WebhookCapability = ReturnType<typeof createWebhookCapability>;
|
|
104
|
+
|
|
105
|
+
export function createWebhookCapability(
|
|
106
|
+
key: string,
|
|
107
|
+
config: WebhookConfiguration,
|
|
108
|
+
) {
|
|
109
|
+
return {
|
|
110
|
+
_tag: "webhook" as const,
|
|
111
|
+
key,
|
|
112
|
+
config: {
|
|
113
|
+
title: config.title,
|
|
114
|
+
description: config.description,
|
|
115
|
+
},
|
|
116
|
+
async handler(
|
|
117
|
+
events: WebhookEvent[],
|
|
118
|
+
options?: HandlerOptions,
|
|
119
|
+
): Promise<{ status: "success" } | undefined> {
|
|
120
|
+
try {
|
|
121
|
+
const capabilityContext = createCapabilityContext();
|
|
122
|
+
await config.execute(events, capabilityContext);
|
|
123
|
+
|
|
124
|
+
if (options?.concreteOutput) {
|
|
125
|
+
return { status: "success" };
|
|
126
|
+
} else {
|
|
127
|
+
writeOutput({ _tag: "success", value: { status: "success" } });
|
|
128
|
+
}
|
|
129
|
+
} catch (err) {
|
|
130
|
+
const error =
|
|
131
|
+
err instanceof ExecutionError ? err : new ExecutionError(err);
|
|
132
|
+
|
|
133
|
+
if (!options?.concreteOutput) {
|
|
134
|
+
writeOutput({
|
|
135
|
+
_tag: "error",
|
|
136
|
+
error: {
|
|
137
|
+
name: error.name,
|
|
138
|
+
message: error.message,
|
|
139
|
+
trace: error.stack,
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,20 @@
|
|
|
1
1
|
export { emojiIcon, imageIcon, notionIcon, place } from "./builder.js";
|
|
2
|
+
export type {
|
|
3
|
+
AiConnectorArchetype,
|
|
4
|
+
AiConnectorCapability,
|
|
5
|
+
AiConnectorChange,
|
|
6
|
+
AiConnectorChangeUpsert,
|
|
7
|
+
AiConnectorChatAuthor,
|
|
8
|
+
AiConnectorChatChannel,
|
|
9
|
+
AiConnectorChatMessage,
|
|
10
|
+
AiConnectorChatRecord,
|
|
11
|
+
AiConnectorConfiguration,
|
|
12
|
+
AiConnectorExecutionResult,
|
|
13
|
+
AiConnectorMode,
|
|
14
|
+
AiConnectorProjectBlock,
|
|
15
|
+
AiConnectorProjectRecord,
|
|
16
|
+
AiConnectorRecordByArchetype,
|
|
17
|
+
} from "./capabilities/ai_connector.js";
|
|
2
18
|
export type {
|
|
3
19
|
AutomationCapability,
|
|
4
20
|
AutomationConfiguration,
|
|
@@ -22,6 +38,12 @@ export type {
|
|
|
22
38
|
SyncMode,
|
|
23
39
|
} from "./capabilities/sync.js";
|
|
24
40
|
export type { ToolCapability, ToolConfiguration } from "./capabilities/tool.js";
|
|
41
|
+
export type {
|
|
42
|
+
WebhookCapability,
|
|
43
|
+
WebhookConfiguration,
|
|
44
|
+
WebhookEvent,
|
|
45
|
+
} from "./capabilities/webhook.js";
|
|
46
|
+
export { WebhookVerificationError } from "./capabilities/webhook.js";
|
|
25
47
|
export type { AnyJSONSchema, JSONSchema } from "./json-schema.js";
|
|
26
48
|
export type { Infer, SchemaBuilder } from "./schema-builder.js";
|
|
27
49
|
export { getSchema, j } from "./schema-builder.js";
|
package/src/pacer_internal.ts
CHANGED
|
@@ -14,6 +14,11 @@ export type PacerState = {
|
|
|
14
14
|
pacers: Record<string, PacerEntry>;
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
+
export type PacerDeclaration = {
|
|
18
|
+
readonly key: string;
|
|
19
|
+
readonly config: { allowedRequests: number; intervalMs: number };
|
|
20
|
+
};
|
|
21
|
+
|
|
17
22
|
let pacerState: PacerState = { pacers: {} };
|
|
18
23
|
|
|
19
24
|
export function setPacerState(state: PacerState): void {
|
|
@@ -24,6 +29,35 @@ export function getPacerState(): PacerState {
|
|
|
24
29
|
return pacerState;
|
|
25
30
|
}
|
|
26
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Initialize pacer state from runtime context, falling back to the worker's
|
|
34
|
+
* own declarations for local execution.
|
|
35
|
+
* @internal
|
|
36
|
+
*/
|
|
37
|
+
export function initPacerState(
|
|
38
|
+
runtimePacers: Record<string, PacerEntry> | undefined,
|
|
39
|
+
declarations?: readonly PacerDeclaration[],
|
|
40
|
+
): void {
|
|
41
|
+
const pacers = runtimePacers ?? {};
|
|
42
|
+
if (
|
|
43
|
+
Object.keys(pacers).length === 0 &&
|
|
44
|
+
declarations &&
|
|
45
|
+
declarations.length > 0
|
|
46
|
+
) {
|
|
47
|
+
const localPacers: Record<string, PacerEntry> = {};
|
|
48
|
+
for (const decl of declarations) {
|
|
49
|
+
localPacers[decl.key] = {
|
|
50
|
+
lastScheduledAtMs: 0,
|
|
51
|
+
allowedRequests: decl.config.allowedRequests,
|
|
52
|
+
intervalMs: decl.config.intervalMs,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
setPacerState({ pacers: localPacers });
|
|
56
|
+
} else {
|
|
57
|
+
setPacerState({ pacers });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
27
61
|
/**
|
|
28
62
|
* Core wait logic for pacing API requests.
|
|
29
63
|
* Sleeps if needed to stay under the rate limit.
|
package/src/schema.ts
CHANGED
|
@@ -68,10 +68,10 @@ export type PropertyConfiguration =
|
|
|
68
68
|
| {
|
|
69
69
|
type: "relation";
|
|
70
70
|
/**
|
|
71
|
-
* The
|
|
72
|
-
* This must match the
|
|
71
|
+
* The key of the related database declaration.
|
|
72
|
+
* This must match the key passed to `worker.database()`.
|
|
73
73
|
*/
|
|
74
|
-
|
|
74
|
+
relatedDatabaseKey: string;
|
|
75
75
|
config: { twoWay: false } | { twoWay: true; relatedPropertyName: string };
|
|
76
76
|
};
|
|
77
77
|
|
|
@@ -203,32 +203,49 @@ export function place(): PropertyConfiguration {
|
|
|
203
203
|
}
|
|
204
204
|
|
|
205
205
|
/**
|
|
206
|
-
* Creates a relation property definition that references another
|
|
207
|
-
* The related database must be
|
|
206
|
+
* Creates a relation property definition that references another database.
|
|
207
|
+
* The related database must be declared in the same worker.
|
|
208
208
|
*
|
|
209
|
-
* @param
|
|
209
|
+
* @param relatedDatabaseKey - The key of the related database declaration.
|
|
210
210
|
* @example
|
|
211
211
|
* ```typescript
|
|
212
|
-
*
|
|
212
|
+
* const projects = worker.database("projects", {
|
|
213
|
+
* type: "managed",
|
|
214
|
+
* initialTitle: "Projects",
|
|
215
|
+
* primaryKeyProperty: "Project ID",
|
|
216
|
+
* schema: {
|
|
217
|
+
* properties: {
|
|
218
|
+
* "Project Name": Schema.title(),
|
|
219
|
+
* "Project ID": Schema.richText(),
|
|
220
|
+
* },
|
|
221
|
+
* },
|
|
222
|
+
* });
|
|
213
223
|
*
|
|
214
|
-
*
|
|
224
|
+
* const tasks = worker.database("tasks", {
|
|
225
|
+
* type: "managed",
|
|
226
|
+
* initialTitle: "Tasks",
|
|
227
|
+
* primaryKeyProperty: "Task ID",
|
|
215
228
|
* schema: {
|
|
216
229
|
* properties: {
|
|
217
230
|
* "Task Title": Schema.title(),
|
|
218
|
-
* "Project": Schema.relation("
|
|
231
|
+
* "Project": Schema.relation("projects"),
|
|
219
232
|
* },
|
|
220
233
|
* },
|
|
221
|
-
*
|
|
234
|
+
* });
|
|
235
|
+
*
|
|
236
|
+
* export const tasksSync = worker.sync("tasksSync", {
|
|
237
|
+
* database: tasks,
|
|
238
|
+
* execute: async () => ({ changes: [], hasMore: false }),
|
|
222
239
|
* });
|
|
223
240
|
* ```
|
|
224
241
|
*/
|
|
225
242
|
export function relation(
|
|
226
|
-
|
|
243
|
+
relatedDatabaseKey: string,
|
|
227
244
|
config?: { twoWay: false } | { twoWay: true; relatedPropertyName: string },
|
|
228
245
|
): PropertyConfiguration {
|
|
229
246
|
return {
|
|
230
247
|
type: "relation",
|
|
231
|
-
|
|
248
|
+
relatedDatabaseKey,
|
|
232
249
|
config: config ?? { twoWay: false },
|
|
233
250
|
};
|
|
234
251
|
}
|