@notionhq/apps 0.0.35 → 0.0.37
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/README.md +11 -0
- package/dist/cli/codegen.d.ts.map +1 -1
- package/dist/cli/codegen.js +10 -2
- package/dist/custom-block.d.ts +1 -1
- package/dist/events.generated.d.ts +19 -3
- package/dist/events.generated.d.ts.map +1 -1
- package/dist/events.generated.js +25 -1
- package/dist/schema-builder.d.ts +68 -0
- package/dist/schema-builder.d.ts.map +1 -0
- package/dist/schema-builder.js +90 -0
- package/dist/workflow-schema.d.ts +2 -0
- package/dist/workflow-schema.d.ts.map +1 -0
- package/dist/workflow-schema.js +38 -0
- package/dist/workflow-schema.test.d.ts +2 -0
- package/dist/workflow-schema.test.d.ts.map +1 -0
- package/dist/workflow.d.ts +103 -4
- package/dist/workflow.d.ts.map +1 -1
- package/dist/workflow.js +50 -4
- package/package.json +7 -2
- package/skills/workflow/SKILL.md +33 -0
- package/src/cli/build.test.ts +56 -0
- package/src/cli/codegen.test.ts +14 -0
- package/src/cli/codegen.ts +10 -2
- package/src/cli/discover.test.ts +7 -0
- package/src/events.generated.ts +47 -0
- package/src/schema-builder.ts +149 -0
- package/src/workflow-schema.test.ts +160 -0
- package/src/workflow-schema.ts +42 -0
- package/src/workflow-types.test.ts +65 -0
- package/src/workflow.test.ts +306 -1
- package/src/workflow.ts +196 -9
package/src/workflow.ts
CHANGED
|
@@ -23,9 +23,11 @@ import { ExecutionError } from "./error.js";
|
|
|
23
23
|
import { writeOutput } from "./output.js";
|
|
24
24
|
import { resolveRuntimeInput } from "./runtime-input.js";
|
|
25
25
|
import { readRunMetadata, type RunMetadata } from "./runtime-metadata.js";
|
|
26
|
-
import { createWorkflowEvents } from "./events.generated.js";
|
|
26
|
+
import { createWorkflowEvents, validateManualWorkflowInput } from "./events.generated.js";
|
|
27
27
|
import { createWorkflowStepState, type WorkflowState } from "./workflow-state.js";
|
|
28
28
|
import type {
|
|
29
|
+
ManualWorkflowTrigger,
|
|
30
|
+
WebhookTrigger,
|
|
29
31
|
WorkflowEventMap,
|
|
30
32
|
WorkflowTrigger,
|
|
31
33
|
WorkflowEventCreators,
|
|
@@ -37,6 +39,9 @@ type HandlerOptions = {
|
|
|
37
39
|
concreteOutput?: true;
|
|
38
40
|
};
|
|
39
41
|
|
|
42
|
+
/** The only trigger whose deliveries a verify handler can answer. */
|
|
43
|
+
const WEBHOOK_TRIGGER_TYPE: WebhookTrigger["type"] = "webhooks.webhook";
|
|
44
|
+
|
|
40
45
|
type WorkflowWaitDurationValues = {
|
|
41
46
|
milliseconds?: number;
|
|
42
47
|
seconds?: number;
|
|
@@ -200,7 +205,15 @@ export type {
|
|
|
200
205
|
export { access } from "./workflow-access.js";
|
|
201
206
|
export type WorkflowEvent = WorkflowEventMap[keyof WorkflowEventMap];
|
|
202
207
|
|
|
203
|
-
export type WorkflowEventForTrigger<T extends WorkflowTrigger> =
|
|
208
|
+
export type WorkflowEventForTrigger<T extends WorkflowTrigger> =
|
|
209
|
+
T extends ManualWorkflowTrigger<infer TInput>
|
|
210
|
+
? {
|
|
211
|
+
type: "workflow.manual";
|
|
212
|
+
input: TInput;
|
|
213
|
+
}
|
|
214
|
+
: T extends { type: keyof WorkflowEventMap }
|
|
215
|
+
? WorkflowEventMap[T["type"]]
|
|
216
|
+
: never;
|
|
204
217
|
|
|
205
218
|
export type WorkflowEventForTriggers<T extends readonly WorkflowTrigger[]> =
|
|
206
219
|
WorkflowEventForTrigger<T[number]>;
|
|
@@ -215,6 +228,73 @@ export {
|
|
|
215
228
|
type OAuthConnection,
|
|
216
229
|
} from "./connections.js";
|
|
217
230
|
|
|
231
|
+
/**
|
|
232
|
+
* The inbound HTTP request passed to a webhook verify handler.
|
|
233
|
+
*/
|
|
234
|
+
export type WebhookVerifyRequest = {
|
|
235
|
+
/** Uppercase HTTP method. Deliveries are POSTs; GET/HEAD cover challenge probes. */
|
|
236
|
+
method: "GET" | "HEAD" | "POST";
|
|
237
|
+
/** The full webhook URL as received. */
|
|
238
|
+
url: string;
|
|
239
|
+
/** Query string parameters. */
|
|
240
|
+
query: Record<string, string>;
|
|
241
|
+
/** Request headers with lowercased names. */
|
|
242
|
+
headers: Record<string, string>;
|
|
243
|
+
/** Raw request body. Empty string for bodyless methods like GET and HEAD. */
|
|
244
|
+
rawBody: string;
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* The HTTP response a verify handler produces. `deliver`, when present,
|
|
249
|
+
* explicitly controls whether the request is enqueued for the workflow
|
|
250
|
+
* handler. Otherwise delivery is derived from the status: 2xx enqueues and 4xx
|
|
251
|
+
* does not.
|
|
252
|
+
*/
|
|
253
|
+
export type WebhookVerifyResponse = {
|
|
254
|
+
/** Response status code. Only 2xx and 4xx statuses are allowed. */
|
|
255
|
+
status: number;
|
|
256
|
+
/** Optional response body, at most 8KB. */
|
|
257
|
+
body?: string;
|
|
258
|
+
/** Response content type. Defaults to "text/plain". */
|
|
259
|
+
contentType?: "application/json" | "text/plain";
|
|
260
|
+
/**
|
|
261
|
+
* Whether to enqueue the request for the workflow handler. Defaults to true
|
|
262
|
+
* for 2xx responses and false for 4xx responses.
|
|
263
|
+
*/
|
|
264
|
+
deliver?: boolean;
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* A webhook verify handler. Runs synchronously at Notion's webhook ingress,
|
|
269
|
+
* before the HTTP response is sent, in the app's own sandbox — the same
|
|
270
|
+
* runtime as the workflow handler, with the full Node.js standard library.
|
|
271
|
+
* Read signing secrets from `process.env` and use `node:crypto` for
|
|
272
|
+
* signature checks.
|
|
273
|
+
*
|
|
274
|
+
* Keep it fast: the run must fit a small wall-clock budget (a few seconds,
|
|
275
|
+
* including sandbox startup), so avoid network calls — including
|
|
276
|
+
* `context.notion` API calls, which typically won't fit the budget.
|
|
277
|
+
*/
|
|
278
|
+
export type WebhookVerifyHandler = (
|
|
279
|
+
request: WebhookVerifyRequest,
|
|
280
|
+
context: CapabilityContext,
|
|
281
|
+
) => WebhookVerifyResponse | Promise<WebhookVerifyResponse>;
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* A verify invocation as dispatched by the platform: a single `request`
|
|
285
|
+
* object rather than the event a workflow invocation carries.
|
|
286
|
+
*/
|
|
287
|
+
export type WebhookVerifyInvocation = {
|
|
288
|
+
request: WebhookVerifyRequest;
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* A trigger as the manifest records it. A declared `verify` handler is flagged
|
|
293
|
+
* on the webhook trigger itself, which is where the platform reads it to decide
|
|
294
|
+
* whether to run `verify` at webhook ingress.
|
|
295
|
+
*/
|
|
296
|
+
export type WorkflowManifestTrigger = WorkflowTrigger | (WebhookTrigger & { hasVerify: true });
|
|
297
|
+
|
|
218
298
|
/**
|
|
219
299
|
* Configuration passed to {@link workflow}.
|
|
220
300
|
*/
|
|
@@ -252,6 +332,40 @@ export type WorkflowConfiguration<
|
|
|
252
332
|
*/
|
|
253
333
|
connections?: TConnections;
|
|
254
334
|
|
|
335
|
+
/**
|
|
336
|
+
* Optional synchronous verification handler, run at Notion's webhook
|
|
337
|
+
* ingress before the HTTP response is sent. Use it for providers that
|
|
338
|
+
* require a synchronous verification response: signature checks with
|
|
339
|
+
* specific status codes, challenge echoes, or handshake requests.
|
|
340
|
+
*
|
|
341
|
+
* Only workflows with an `events.webhook()` trigger may declare one.
|
|
342
|
+
*
|
|
343
|
+
* The handler controls the HTTP response. Return `deliver: true` to enqueue
|
|
344
|
+
* the request for the workflow handler, or `deliver: false` to answer the
|
|
345
|
+
* provider without enqueueing it. When omitted, 2xx enqueues and 4xx does
|
|
346
|
+
* not. `verify` runs in the app's own sandbox with full Node.js — read
|
|
347
|
+
* secrets from `process.env` and use `node:crypto` for signature checks. It
|
|
348
|
+
* receives the capability context as its second argument, without the run
|
|
349
|
+
* metadata and `step` a workflow run carries.
|
|
350
|
+
*
|
|
351
|
+
* Without `verify`, the webhook keeps the current behavior: every request
|
|
352
|
+
* is accepted with `202` and processed asynchronously.
|
|
353
|
+
*
|
|
354
|
+
* @example
|
|
355
|
+
* ```ts
|
|
356
|
+
* verify: (request) => {
|
|
357
|
+
* const signature = createHmac("sha256", process.env.WEBHOOK_SECRET ?? "")
|
|
358
|
+
* .update(request.rawBody)
|
|
359
|
+
* .digest("hex");
|
|
360
|
+
* const expected = Buffer.from(`sha256=${signature}`);
|
|
361
|
+
* const actual = Buffer.from(request.headers["x-hub-signature-256"] ?? "");
|
|
362
|
+
* const verified = expected.length === actual.length && timingSafeEqual(expected, actual);
|
|
363
|
+
* return verified ? { status: 200 } : { status: 401, body: "Invalid signature" };
|
|
364
|
+
* },
|
|
365
|
+
* ```
|
|
366
|
+
*/
|
|
367
|
+
verify?: WebhookVerifyHandler;
|
|
368
|
+
|
|
255
369
|
handler: (
|
|
256
370
|
event: WorkflowEventForTriggers<TTriggers>,
|
|
257
371
|
context: WorkflowContext<TConnections, TAccess>,
|
|
@@ -283,14 +397,14 @@ export type Workflow<
|
|
|
283
397
|
config: {
|
|
284
398
|
name: string;
|
|
285
399
|
description: string;
|
|
286
|
-
triggers:
|
|
400
|
+
triggers: WorkflowManifestTrigger[];
|
|
287
401
|
connections?: readonly WorkflowConnectionRequirement[];
|
|
288
402
|
access?: WorkflowAccessRequirements;
|
|
289
403
|
};
|
|
290
404
|
handler: (
|
|
291
|
-
|
|
405
|
+
input: WorkflowEventForTriggers<TTriggers> | WebhookVerifyInvocation,
|
|
292
406
|
options?: HandlerOptions,
|
|
293
|
-
) => Promise<WorkflowHandlerResult | undefined>;
|
|
407
|
+
) => Promise<WorkflowHandlerResult | WebhookVerifyResponse | undefined>;
|
|
294
408
|
};
|
|
295
409
|
|
|
296
410
|
/**
|
|
@@ -354,22 +468,61 @@ export function workflow<
|
|
|
354
468
|
? configuration.triggers({ events: createWorkflowEvents<TConnections>() })
|
|
355
469
|
: configuration.triggers;
|
|
356
470
|
validateTriggerConnections(triggers, requirements);
|
|
471
|
+
const manualTriggers = triggers.filter(
|
|
472
|
+
(trigger): trigger is ManualWorkflowTrigger => trigger.type === "workflow.manual",
|
|
473
|
+
);
|
|
474
|
+
if (manualTriggers.length > 1) {
|
|
475
|
+
throw new Error("Workflows support at most one manual trigger");
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
if (
|
|
479
|
+
configuration.verify !== undefined &&
|
|
480
|
+
!triggers.some((trigger) => trigger.type === WEBHOOK_TRIGGER_TYPE)
|
|
481
|
+
) {
|
|
482
|
+
throw new Error(
|
|
483
|
+
`Workflow "${configuration.name}" declares a verify handler but has no webhook trigger. ` +
|
|
484
|
+
`Add events.webhook() or remove verify.`,
|
|
485
|
+
);
|
|
486
|
+
}
|
|
357
487
|
|
|
358
488
|
return {
|
|
359
489
|
_tag: "workflow",
|
|
360
490
|
config: {
|
|
361
491
|
name: configuration.name,
|
|
362
492
|
description: configuration.description,
|
|
363
|
-
triggers,
|
|
493
|
+
triggers: manifestTriggers(triggers, configuration.verify !== undefined),
|
|
364
494
|
...(configuration.connections === undefined ? {} : { connections: requirements }),
|
|
365
495
|
...(configuration.access === undefined ? {} : { access: accessRequirements }),
|
|
366
496
|
},
|
|
367
497
|
async handler(
|
|
368
|
-
|
|
498
|
+
input: WorkflowEventForTriggers<TTriggers> | WebhookVerifyInvocation,
|
|
369
499
|
options?: HandlerOptions,
|
|
370
|
-
): Promise<WorkflowHandlerResult | undefined> {
|
|
500
|
+
): Promise<WorkflowHandlerResult | WebhookVerifyResponse | undefined> {
|
|
371
501
|
try {
|
|
372
|
-
|
|
502
|
+
input = await resolveRuntimeInput(input);
|
|
503
|
+
|
|
504
|
+
if (isVerifyInvocation(input)) {
|
|
505
|
+
if (configuration.verify === undefined) {
|
|
506
|
+
throw new Error(
|
|
507
|
+
`Workflow "${configuration.name}" received a verify request but does not ` +
|
|
508
|
+
`declare a verify handler`,
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const response = await configuration.verify(
|
|
513
|
+
input.request,
|
|
514
|
+
createCapabilityContext(),
|
|
515
|
+
);
|
|
516
|
+
|
|
517
|
+
if (options?.concreteOutput) {
|
|
518
|
+
return response;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
writeOutput({ _tag: "success", value: response });
|
|
522
|
+
return undefined;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const event = input;
|
|
373
526
|
const runMetadata = readRunMetadata();
|
|
374
527
|
const baseContext = createCapabilityContext();
|
|
375
528
|
const step = createStep(runMetadata);
|
|
@@ -384,6 +537,15 @@ export function workflow<
|
|
|
384
537
|
step,
|
|
385
538
|
wait: createWait(step),
|
|
386
539
|
};
|
|
540
|
+
if (event.type === "workflow.manual") {
|
|
541
|
+
const manualTrigger = manualTriggers[0];
|
|
542
|
+
if (manualTrigger === undefined) {
|
|
543
|
+
throw new Error(
|
|
544
|
+
"Received workflow.manual for a workflow without a manual trigger",
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
validateManualWorkflowInput(manualTrigger, event.input);
|
|
548
|
+
}
|
|
387
549
|
await configuration.handler(event, capabilityContext);
|
|
388
550
|
|
|
389
551
|
if (options?.concreteOutput) {
|
|
@@ -423,6 +585,31 @@ export function workflow<
|
|
|
423
585
|
};
|
|
424
586
|
}
|
|
425
587
|
|
|
588
|
+
/**
|
|
589
|
+
* The triggers as the manifest records them. The manifest only records that a
|
|
590
|
+
* verify handler exists, flagged on the webhook trigger; the handler itself
|
|
591
|
+
* ships in the app bundle and runs via a verify invocation.
|
|
592
|
+
*/
|
|
593
|
+
function manifestTriggers(
|
|
594
|
+
triggers: readonly WorkflowTrigger[],
|
|
595
|
+
hasVerify: boolean,
|
|
596
|
+
): WorkflowManifestTrigger[] {
|
|
597
|
+
return triggers.map((trigger) =>
|
|
598
|
+
hasVerify && trigger.type === WEBHOOK_TRIGGER_TYPE
|
|
599
|
+
? { ...trigger, hasVerify: true as const }
|
|
600
|
+
: trigger,
|
|
601
|
+
);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* Whether an invocation is the platform's synchronous verify dispatch. The
|
|
606
|
+
* platform sends workflow invocations as a trigger event and verify
|
|
607
|
+
* invocations as a `{ request }` object, so the shape disambiguates.
|
|
608
|
+
*/
|
|
609
|
+
function isVerifyInvocation(input: unknown): input is WebhookVerifyInvocation {
|
|
610
|
+
return typeof input === "object" && input !== null && "request" in input;
|
|
611
|
+
}
|
|
612
|
+
|
|
426
613
|
/** Context passed to a workflow step. */
|
|
427
614
|
export type StepContext = {
|
|
428
615
|
/**
|