@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/dist/workflow.js
CHANGED
|
@@ -15,8 +15,9 @@ import { ExecutionError } from "./error.js";
|
|
|
15
15
|
import { writeOutput } from "./output.js";
|
|
16
16
|
import { resolveRuntimeInput } from "./runtime-input.js";
|
|
17
17
|
import { readRunMetadata } from "./runtime-metadata.js";
|
|
18
|
-
import { createWorkflowEvents } from "./events.generated.js";
|
|
18
|
+
import { createWorkflowEvents, validateManualWorkflowInput } from "./events.generated.js";
|
|
19
19
|
import { createWorkflowStepState } from "./workflow-state.js";
|
|
20
|
+
const WEBHOOK_TRIGGER_TYPE = "webhooks.webhook";
|
|
20
21
|
const input = {
|
|
21
22
|
text(options) {
|
|
22
23
|
return { type: "text", ...options };
|
|
@@ -56,18 +57,46 @@ function workflow(configuration) {
|
|
|
56
57
|
const connectionDeclarations = configuration.connections === void 0 ? void 0 : structuredClone(configuration.connections);
|
|
57
58
|
const triggers = typeof configuration.triggers === "function" ? configuration.triggers({ events: createWorkflowEvents() }) : configuration.triggers;
|
|
58
59
|
validateTriggerConnections(triggers, requirements);
|
|
60
|
+
const manualTriggers = triggers.filter(
|
|
61
|
+
(trigger) => trigger.type === "workflow.manual"
|
|
62
|
+
);
|
|
63
|
+
if (manualTriggers.length > 1) {
|
|
64
|
+
throw new Error("Workflows support at most one manual trigger");
|
|
65
|
+
}
|
|
66
|
+
if (configuration.verify !== void 0 && !triggers.some((trigger) => trigger.type === WEBHOOK_TRIGGER_TYPE)) {
|
|
67
|
+
throw new Error(
|
|
68
|
+
`Workflow "${configuration.name}" declares a verify handler but has no webhook trigger. Add events.webhook() or remove verify.`
|
|
69
|
+
);
|
|
70
|
+
}
|
|
59
71
|
return {
|
|
60
72
|
_tag: "workflow",
|
|
61
73
|
config: {
|
|
62
74
|
name: configuration.name,
|
|
63
75
|
description: configuration.description,
|
|
64
|
-
triggers,
|
|
76
|
+
triggers: manifestTriggers(triggers, configuration.verify !== void 0),
|
|
65
77
|
...configuration.connections === void 0 ? {} : { connections: requirements },
|
|
66
78
|
...configuration.access === void 0 ? {} : { access: accessRequirements }
|
|
67
79
|
},
|
|
68
|
-
async handler(
|
|
80
|
+
async handler(input2, options) {
|
|
69
81
|
try {
|
|
70
|
-
|
|
82
|
+
input2 = await resolveRuntimeInput(input2);
|
|
83
|
+
if (isVerifyInvocation(input2)) {
|
|
84
|
+
if (configuration.verify === void 0) {
|
|
85
|
+
throw new Error(
|
|
86
|
+
`Workflow "${configuration.name}" received a verify request but does not declare a verify handler`
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
const response = await configuration.verify(
|
|
90
|
+
input2.request,
|
|
91
|
+
createCapabilityContext()
|
|
92
|
+
);
|
|
93
|
+
if (options?.concreteOutput) {
|
|
94
|
+
return response;
|
|
95
|
+
}
|
|
96
|
+
writeOutput({ _tag: "success", value: response });
|
|
97
|
+
return void 0;
|
|
98
|
+
}
|
|
99
|
+
const event = input2;
|
|
71
100
|
const runMetadata = readRunMetadata();
|
|
72
101
|
const baseContext = createCapabilityContext();
|
|
73
102
|
const step = createStep(runMetadata);
|
|
@@ -82,6 +111,15 @@ function workflow(configuration) {
|
|
|
82
111
|
step,
|
|
83
112
|
wait: createWait(step)
|
|
84
113
|
};
|
|
114
|
+
if (event.type === "workflow.manual") {
|
|
115
|
+
const manualTrigger = manualTriggers[0];
|
|
116
|
+
if (manualTrigger === void 0) {
|
|
117
|
+
throw new Error(
|
|
118
|
+
"Received workflow.manual for a workflow without a manual trigger"
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
validateManualWorkflowInput(manualTrigger, event.input);
|
|
122
|
+
}
|
|
85
123
|
await configuration.handler(event, capabilityContext);
|
|
86
124
|
if (options?.concreteOutput) {
|
|
87
125
|
return { status: "success" };
|
|
@@ -114,6 +152,14 @@ function workflow(configuration) {
|
|
|
114
152
|
}
|
|
115
153
|
};
|
|
116
154
|
}
|
|
155
|
+
function manifestTriggers(triggers, hasVerify) {
|
|
156
|
+
return triggers.map(
|
|
157
|
+
(trigger) => hasVerify && trigger.type === WEBHOOK_TRIGGER_TYPE ? { ...trigger, hasVerify: true } : trigger
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
function isVerifyInvocation(input2) {
|
|
161
|
+
return typeof input2 === "object" && input2 !== null && "request" in input2;
|
|
162
|
+
}
|
|
117
163
|
const WORKFLOW_STEP_DIRECTORY_ENV_KEY = "NOTION_WORKFLOW_STEP_DIRECTORY";
|
|
118
164
|
const MAX_WORKFLOW_STEP_KEY_LENGTH = 43;
|
|
119
165
|
const WORKFLOW_STEP_KEY_PATTERN = /^[A-Za-z0-9_-]+$/;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@notionhq/apps",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.37",
|
|
4
4
|
"description": "An SDK for building workflow apps for Notion",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|
|
@@ -83,7 +83,11 @@
|
|
|
83
83
|
"types": "./dist/react.d.ts",
|
|
84
84
|
"default": "./dist/react.js"
|
|
85
85
|
},
|
|
86
|
-
"./nds.css": "./dist/nds.css"
|
|
86
|
+
"./nds.css": "./dist/nds.css",
|
|
87
|
+
"./schema-builder": {
|
|
88
|
+
"types": "./dist/schema-builder.d.ts",
|
|
89
|
+
"default": "./dist/schema-builder.js"
|
|
90
|
+
}
|
|
87
91
|
},
|
|
88
92
|
"publishConfig": {
|
|
89
93
|
"access": "public"
|
|
@@ -108,6 +112,7 @@
|
|
|
108
112
|
"dependencies": {
|
|
109
113
|
"@notionhq/custom-blocks": "latest",
|
|
110
114
|
"ajv": "^8.17.1",
|
|
115
|
+
"ajv-formats": "^3.0.1",
|
|
111
116
|
"rolldown": "^1.1.5"
|
|
112
117
|
},
|
|
113
118
|
"devDependencies": {
|
package/skills/workflow/SKILL.md
CHANGED
|
@@ -108,6 +108,39 @@ placeholders to `.env.example` when configuration is required. Return only
|
|
|
108
108
|
JSON-serializable step values, throw on failed requests and missing required
|
|
109
109
|
configuration, and do not log secrets or private payloads.
|
|
110
110
|
|
|
111
|
+
## Manual workflow inputs
|
|
112
|
+
|
|
113
|
+
Provide up to one `events.manual` to allow triggering the workflow manually from the UI or CLI for testing purposes.
|
|
114
|
+
|
|
115
|
+
Specify the inputs accepted using `@notionhq/apps/schema-builder`.
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
import { workflow } from "@notionhq/apps/workflow";
|
|
119
|
+
import { events } from "@notionhq/apps/events";
|
|
120
|
+
import { j } from "@notionhq/apps/schema-builder";
|
|
121
|
+
|
|
122
|
+
export default workflow({
|
|
123
|
+
name: "Prepare releases",
|
|
124
|
+
description: "Prepares the supplied releases.",
|
|
125
|
+
triggers: [
|
|
126
|
+
events.manual({
|
|
127
|
+
inputSchema: j.object({
|
|
128
|
+
releases: j.array(
|
|
129
|
+
j.object({
|
|
130
|
+
version: j.string(),
|
|
131
|
+
dryRun: j.boolean().nullable().describe("Whether to preview the release"),
|
|
132
|
+
}),
|
|
133
|
+
),
|
|
134
|
+
}),
|
|
135
|
+
}),
|
|
136
|
+
],
|
|
137
|
+
handler: async (event, context) => {
|
|
138
|
+
// event.input: { releases: { version: string; dryRun: boolean | null }[] }
|
|
139
|
+
await context.step("Log releases", () => console.log(event.input.releases));
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
```
|
|
143
|
+
|
|
111
144
|
## Resources created with the App
|
|
112
145
|
|
|
113
146
|
Use [Notion as Code](../notion-as-code/SKILL.md) for pages, databases, and custom agents
|
package/src/cli/build.test.ts
CHANGED
|
@@ -480,6 +480,7 @@ describe("buildApp", () => {
|
|
|
480
480
|
const { manifest } = await buildApp(root);
|
|
481
481
|
expect(manifest.capabilities).toEqual([
|
|
482
482
|
expect.objectContaining({ type: "workflow", key: "onPageCreated" }),
|
|
483
|
+
expect.objectContaining({ type: "workflow", key: "prepareReleases" }),
|
|
483
484
|
]);
|
|
484
485
|
await expect(fs.promises.readFile(provisioningPath)).rejects.toMatchObject({
|
|
485
486
|
code: "ENOENT",
|
|
@@ -538,12 +539,67 @@ describe("buildApp", () => {
|
|
|
538
539
|
triggers: [{ type: "notion.page.created" }],
|
|
539
540
|
},
|
|
540
541
|
},
|
|
542
|
+
{
|
|
543
|
+
type: "workflow",
|
|
544
|
+
key: "prepareReleases",
|
|
545
|
+
config: {
|
|
546
|
+
name: "Prepare releases",
|
|
547
|
+
description: "Prepares the supplied releases.",
|
|
548
|
+
triggers: [
|
|
549
|
+
{
|
|
550
|
+
type: "workflow.manual",
|
|
551
|
+
inputJsonSchema: {
|
|
552
|
+
type: "object",
|
|
553
|
+
additionalProperties: false,
|
|
554
|
+
required: ["releases"],
|
|
555
|
+
properties: {
|
|
556
|
+
releases: {
|
|
557
|
+
type: "array",
|
|
558
|
+
items: {
|
|
559
|
+
type: "object",
|
|
560
|
+
additionalProperties: false,
|
|
561
|
+
required: ["version", "dryRun"],
|
|
562
|
+
properties: {
|
|
563
|
+
version: { type: "string" },
|
|
564
|
+
dryRun: {
|
|
565
|
+
anyOf: [
|
|
566
|
+
{ type: "boolean" },
|
|
567
|
+
{ type: "null" },
|
|
568
|
+
],
|
|
569
|
+
},
|
|
570
|
+
},
|
|
571
|
+
},
|
|
572
|
+
},
|
|
573
|
+
},
|
|
574
|
+
},
|
|
575
|
+
},
|
|
576
|
+
],
|
|
577
|
+
},
|
|
578
|
+
},
|
|
541
579
|
],
|
|
542
580
|
});
|
|
543
581
|
expect(path.basename(bundlePath)).toBe("worker.js");
|
|
544
582
|
expect(JSON.parse(await fs.promises.readFile(manifestPath, "utf8"))).toEqual(manifest);
|
|
545
583
|
});
|
|
546
584
|
|
|
585
|
+
it("records a workflow's verify handler in the manifest", async () => {
|
|
586
|
+
const { manifestPath } = await buildApp(fixture("webhook-verify"));
|
|
587
|
+
|
|
588
|
+
expect(JSON.parse(await fs.promises.readFile(manifestPath, "utf8"))).toMatchObject({
|
|
589
|
+
capabilities: [
|
|
590
|
+
{
|
|
591
|
+
type: "workflow",
|
|
592
|
+
key: "onIncoming",
|
|
593
|
+
config: {
|
|
594
|
+
name: "Incoming Webhook",
|
|
595
|
+
description: "Verifies and processes an incoming webhook",
|
|
596
|
+
triggers: [{ type: "webhooks.webhook", hasVerify: true }],
|
|
597
|
+
},
|
|
598
|
+
},
|
|
599
|
+
],
|
|
600
|
+
});
|
|
601
|
+
});
|
|
602
|
+
|
|
547
603
|
it("collects relation databases and a shared pacer once", async () => {
|
|
548
604
|
const { manifest } = await buildApp(fixture("shared-primary-key"));
|
|
549
605
|
|
package/src/cli/codegen.test.ts
CHANGED
|
@@ -20,6 +20,20 @@ describe("generateEntry", () => {
|
|
|
20
20
|
expect(entry).toContain("export async function run(");
|
|
21
21
|
});
|
|
22
22
|
|
|
23
|
+
it("resolves the webhook verify invocation type to the workflow registry", () => {
|
|
24
|
+
const entry = generateEntry([
|
|
25
|
+
{
|
|
26
|
+
type: "workflow",
|
|
27
|
+
tag: "workflow",
|
|
28
|
+
key: "onIncoming",
|
|
29
|
+
sourcePath: "src/workflows/onIncoming.ts",
|
|
30
|
+
runtime: "worker",
|
|
31
|
+
},
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
expect(entry).toContain(`workflowWebhookVerify: "workflow",`);
|
|
35
|
+
});
|
|
36
|
+
|
|
23
37
|
it("registers syncs in their own capability group", () => {
|
|
24
38
|
const entry = generateEntry([
|
|
25
39
|
{
|
package/src/cli/codegen.ts
CHANGED
|
@@ -37,16 +37,24 @@ export const capabilities: Record<string, Record<string, { handler: Handler }>>
|
|
|
37
37
|
${registry}
|
|
38
38
|
};
|
|
39
39
|
|
|
40
|
+
// The platform dispatches a workflow's synchronous webhook verify run under its
|
|
41
|
+
// own invocation type. It resolves to the same capability, which tells a verify
|
|
42
|
+
// invocation from a trigger event by the shape of its input.
|
|
43
|
+
const invocationTypes: Record<string, string | undefined> = {
|
|
44
|
+
workflowWebhookVerify: "workflow",
|
|
45
|
+
};
|
|
46
|
+
|
|
40
47
|
export async function run(
|
|
41
48
|
type: string,
|
|
42
49
|
key: string,
|
|
43
50
|
context?: unknown,
|
|
44
51
|
options?: { concreteOutput?: true },
|
|
45
52
|
): Promise<unknown> {
|
|
46
|
-
const
|
|
53
|
+
const capabilityType = invocationTypes[type] ?? type;
|
|
54
|
+
const capability = capabilities[capabilityType]?.[key];
|
|
47
55
|
|
|
48
56
|
if (!capability) {
|
|
49
|
-
throw new Error(\`Capability "\${
|
|
57
|
+
throw new Error(\`Capability "\${capabilityType}/\${key}" not found\`);
|
|
50
58
|
}
|
|
51
59
|
|
|
52
60
|
return capability.handler(context, options);
|
package/src/cli/discover.test.ts
CHANGED
|
@@ -27,6 +27,13 @@ describe("discoverCapabilities", () => {
|
|
|
27
27
|
key: "onPageCreated",
|
|
28
28
|
sourcePath: path.join("src", "workflows", "onPageCreated.ts"),
|
|
29
29
|
},
|
|
30
|
+
{
|
|
31
|
+
type: "workflow",
|
|
32
|
+
tag: "workflow",
|
|
33
|
+
runtime: "worker",
|
|
34
|
+
key: "prepareReleases",
|
|
35
|
+
sourcePath: path.join("src", "workflows", "prepareReleases.ts"),
|
|
36
|
+
},
|
|
30
37
|
]);
|
|
31
38
|
});
|
|
32
39
|
|
package/src/events.generated.ts
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
// Regenerate with: pnpm run generate
|
|
3
3
|
|
|
4
4
|
import type { WorkflowConnectionDeclarations, WorkflowConnectionKeys } from "./connections.js";
|
|
5
|
+
import { compileWorkflowInputSchema } from "./workflow-schema.js";
|
|
6
|
+
import { j, type SchemaBuilder } from "./schema-builder.js";
|
|
5
7
|
import { scheduled, notionPageCreated, notionPageUpdated } from "./configured-events.js";
|
|
6
8
|
import type { NotionPageCreatedTrigger, NotionPageUpdatedTrigger, PageUpdatedOptions, RecurrenceTrigger } from "./configured-events.js";
|
|
7
9
|
import type { DataSourceHandle } from "./notion-as-code/database.js";
|
|
@@ -82,6 +84,15 @@ export type {
|
|
|
82
84
|
export type { NotionPageCreatedTrigger, NotionPageUpdatedTrigger, PageUpdatedOptions, RecurrenceTrigger } from "./configured-events.js";
|
|
83
85
|
export type { RecurrenceSchedule } from "./notion-as-code/recurrence.js";
|
|
84
86
|
|
|
87
|
+
declare const manualWorkflowInputSchema: unique symbol;
|
|
88
|
+
const manualInputValidators = new WeakMap<object, (input: unknown) => void>();
|
|
89
|
+
|
|
90
|
+
export type ManualWorkflowTrigger<TInput extends Record<string, unknown> = Record<string, unknown>> = {
|
|
91
|
+
type: "workflow.manual";
|
|
92
|
+
inputJsonSchema: Record<string, unknown>;
|
|
93
|
+
readonly [manualWorkflowInputSchema]?: TInput;
|
|
94
|
+
};
|
|
95
|
+
|
|
85
96
|
/** Triggers immediately after a new message is posted in Slack (includes new threads and replies) */
|
|
86
97
|
export type SlackMessageTrigger<TKey extends string = string> = {
|
|
87
98
|
type: "slack.message";
|
|
@@ -194,8 +205,27 @@ export type GoogleDriveOauthFilesChangedTrigger<TKey extends string = string> =
|
|
|
194
205
|
connectionKey?: TKey;
|
|
195
206
|
};
|
|
196
207
|
|
|
208
|
+
function manual(): ManualWorkflowTrigger<Record<string, never>>;
|
|
209
|
+
function manual<TInput extends Record<string, unknown>>(options: {
|
|
210
|
+
inputSchema: SchemaBuilder<TInput, "object">;
|
|
211
|
+
}): ManualWorkflowTrigger<TInput>;
|
|
212
|
+
function manual(options?: {
|
|
213
|
+
inputSchema: SchemaBuilder<Record<string, unknown>, "object">;
|
|
214
|
+
}): ManualWorkflowTrigger {
|
|
215
|
+
const { jsonSchema, validate } = compileWorkflowInputSchema(
|
|
216
|
+
options === undefined ? j.object({}) : options.inputSchema,
|
|
217
|
+
);
|
|
218
|
+
const trigger: ManualWorkflowTrigger = {
|
|
219
|
+
type: "workflow.manual",
|
|
220
|
+
inputJsonSchema: jsonSchema,
|
|
221
|
+
};
|
|
222
|
+
manualInputValidators.set(trigger, validate);
|
|
223
|
+
return trigger;
|
|
224
|
+
}
|
|
225
|
+
|
|
197
226
|
/** Creators for each event that can trigger a workflow. */
|
|
198
227
|
export const events = {
|
|
228
|
+
manual,
|
|
199
229
|
/**
|
|
200
230
|
* Declare that a workflow can run on `slack.message` events.
|
|
201
231
|
*
|
|
@@ -414,6 +444,8 @@ export const events = {
|
|
|
414
444
|
|
|
415
445
|
/** Trigger creators restricted to the workflow's declared provider keys. */
|
|
416
446
|
export type WorkflowEventCreators<TConnections extends WorkflowConnectionDeclarations> = {
|
|
447
|
+
manual(): ManualWorkflowTrigger<Record<string, never>>;
|
|
448
|
+
manual<TInput extends Record<string, unknown>>(options: { inputSchema: SchemaBuilder<TInput, "object"> }): ManualWorkflowTrigger<TInput>;
|
|
417
449
|
slackMessage(options?: { connectionKey: WorkflowConnectionKeys<TConnections, "slack"> }): SlackMessageTrigger<WorkflowConnectionKeys<TConnections, "slack">>;
|
|
418
450
|
slackReactionAdded(options?: { connectionKey: WorkflowConnectionKeys<TConnections, "slack"> }): SlackReactionAddedTrigger<WorkflowConnectionKeys<TConnections, "slack">>;
|
|
419
451
|
slackAppMention(options?: { connectionKey: WorkflowConnectionKeys<TConnections, "slack"> }): SlackAppMentionTrigger<WorkflowConnectionKeys<TConnections, "slack">>;
|
|
@@ -439,6 +471,7 @@ export type WorkflowEventCreators<TConnections extends WorkflowConnectionDeclara
|
|
|
439
471
|
|
|
440
472
|
/** Triggers whose connection keys belong to the declared provider. */
|
|
441
473
|
export type WorkflowTriggerForConnections<TConnections extends WorkflowConnectionDeclarations> =
|
|
474
|
+
| ManualWorkflowTrigger
|
|
442
475
|
| SlackMessageTrigger<WorkflowConnectionKeys<TConnections, "slack">>
|
|
443
476
|
| SlackReactionAddedTrigger<WorkflowConnectionKeys<TConnections, "slack">>
|
|
444
477
|
| SlackAppMentionTrigger<WorkflowConnectionKeys<TConnections, "slack">>
|
|
@@ -468,6 +501,7 @@ export function createWorkflowEvents<TConnections extends WorkflowConnectionDecl
|
|
|
468
501
|
|
|
469
502
|
/** Event that can trigger a workflow. */
|
|
470
503
|
export type WorkflowTrigger =
|
|
504
|
+
| ManualWorkflowTrigger
|
|
471
505
|
| SlackMessageTrigger
|
|
472
506
|
| SlackReactionAddedTrigger
|
|
473
507
|
| SlackAppMentionTrigger
|
|
@@ -492,6 +526,7 @@ export type WorkflowTrigger =
|
|
|
492
526
|
|
|
493
527
|
/** All workflow trigger type strings. */
|
|
494
528
|
export const WORKFLOW_TRIGGER_TYPES = [
|
|
529
|
+
"workflow.manual",
|
|
495
530
|
"slack.message",
|
|
496
531
|
"slack.reaction.added",
|
|
497
532
|
"slack.app.mention",
|
|
@@ -542,3 +577,15 @@ export type WorkflowEventMap = {
|
|
|
542
577
|
"googleDriveOauth.filesChanged": GoogleDriveOauthFilesChangedEvent;
|
|
543
578
|
recurrence: RecurrenceEvent;
|
|
544
579
|
};
|
|
580
|
+
|
|
581
|
+
/** @internal */
|
|
582
|
+
export function validateManualWorkflowInput(
|
|
583
|
+
trigger: ManualWorkflowTrigger,
|
|
584
|
+
input: unknown,
|
|
585
|
+
): void {
|
|
586
|
+
const validate = manualInputValidators.get(trigger);
|
|
587
|
+
if (validate === undefined) {
|
|
588
|
+
throw new Error("Manual workflow trigger is missing its input schema");
|
|
589
|
+
}
|
|
590
|
+
validate(input);
|
|
591
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/** Fluent JSON Schema builders following the classic Workers schema-builder API. */
|
|
2
|
+
const SCHEMA: unique symbol = Symbol("schema");
|
|
3
|
+
const KIND: unique symbol = Symbol("kind");
|
|
4
|
+
declare const VALUE: unique symbol;
|
|
5
|
+
|
|
6
|
+
export type StringFormat =
|
|
7
|
+
| "date-time"
|
|
8
|
+
| "date"
|
|
9
|
+
| "time"
|
|
10
|
+
| "duration"
|
|
11
|
+
| "email"
|
|
12
|
+
| "hostname"
|
|
13
|
+
| "ipv4"
|
|
14
|
+
| "ipv6"
|
|
15
|
+
| "uuid";
|
|
16
|
+
|
|
17
|
+
type Kind = "object" | "value";
|
|
18
|
+
type JsonSchema = {
|
|
19
|
+
title?: string;
|
|
20
|
+
description?: string;
|
|
21
|
+
} & (
|
|
22
|
+
| {
|
|
23
|
+
type: "string" | "number" | "integer" | "boolean" | "null";
|
|
24
|
+
enum?: readonly (string | number)[];
|
|
25
|
+
format?: StringFormat;
|
|
26
|
+
}
|
|
27
|
+
| { type: "array"; items: JsonSchema; minItems?: 0 | 1 }
|
|
28
|
+
| {
|
|
29
|
+
type: "object";
|
|
30
|
+
properties: Record<string, JsonSchema>;
|
|
31
|
+
required: string[];
|
|
32
|
+
additionalProperties: false;
|
|
33
|
+
}
|
|
34
|
+
| { anyOf: JsonSchema[] }
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
export interface SchemaBuilder<T, K extends Kind = Kind> {
|
|
38
|
+
readonly [SCHEMA]: JsonSchema;
|
|
39
|
+
readonly [KIND]: K;
|
|
40
|
+
readonly [VALUE]?: T;
|
|
41
|
+
describe(text: string): SchemaBuilder<T, K>;
|
|
42
|
+
label(text: string): SchemaBuilder<T, K>;
|
|
43
|
+
nullable(): SchemaBuilder<T | null, "value">;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export type Infer<S> = S extends SchemaBuilder<infer T> ? T : never;
|
|
47
|
+
|
|
48
|
+
/** Returns a copy so callers cannot mutate the builder's validation contract. */
|
|
49
|
+
export function getSchema(builder: SchemaBuilder<unknown>): JsonSchema {
|
|
50
|
+
if (typeof builder !== "object" || builder === null || !(SCHEMA in builder)) {
|
|
51
|
+
throw new Error("Expected a schema builder; use j.object() for manual inputs");
|
|
52
|
+
}
|
|
53
|
+
return structuredClone(builder[SCHEMA]);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function makeBuilder<T, K extends Kind>(schema: JsonSchema, kind: K): SchemaBuilder<T, K> {
|
|
57
|
+
return {
|
|
58
|
+
[SCHEMA]: schema,
|
|
59
|
+
[KIND]: kind,
|
|
60
|
+
describe(description) {
|
|
61
|
+
return makeBuilder<T, K>({ ...schema, description }, kind);
|
|
62
|
+
},
|
|
63
|
+
label(title) {
|
|
64
|
+
return makeBuilder<T, K>({ ...schema, title }, kind);
|
|
65
|
+
},
|
|
66
|
+
nullable() {
|
|
67
|
+
const { title, description, ...inner } = schema;
|
|
68
|
+
return makeBuilder<T | null, "value">(
|
|
69
|
+
{
|
|
70
|
+
anyOf: [inner, { type: "null" }],
|
|
71
|
+
...(title === undefined ? {} : { title }),
|
|
72
|
+
...(description === undefined ? {} : { description }),
|
|
73
|
+
},
|
|
74
|
+
"value",
|
|
75
|
+
);
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function string(): SchemaBuilder<string, "value"> {
|
|
81
|
+
return makeBuilder({ type: "string" }, "value");
|
|
82
|
+
}
|
|
83
|
+
function formattedString(format: StringFormat): SchemaBuilder<string, "value"> {
|
|
84
|
+
return makeBuilder({ type: "string", format }, "value");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function number(): SchemaBuilder<number, "value"> {
|
|
88
|
+
return makeBuilder({ type: "number" }, "value");
|
|
89
|
+
}
|
|
90
|
+
function integer(): SchemaBuilder<number, "value"> {
|
|
91
|
+
return makeBuilder({ type: "integer" }, "value");
|
|
92
|
+
}
|
|
93
|
+
function boolean(): SchemaBuilder<boolean, "value"> {
|
|
94
|
+
return makeBuilder({ type: "boolean" }, "value");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function enumBuilder<const T extends string>(first: T, ...rest: T[]): SchemaBuilder<T, "value">;
|
|
98
|
+
function enumBuilder<const T extends number>(first: T, ...rest: T[]): SchemaBuilder<T, "value">;
|
|
99
|
+
function enumBuilder(
|
|
100
|
+
first: string | number,
|
|
101
|
+
...rest: (string | number)[]
|
|
102
|
+
): SchemaBuilder<string | number, "value"> {
|
|
103
|
+
return makeBuilder(
|
|
104
|
+
{ type: typeof first === "string" ? "string" : "number", enum: [first, ...rest] },
|
|
105
|
+
"value",
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function array<T>(
|
|
110
|
+
items: SchemaBuilder<T>,
|
|
111
|
+
options?: { minItems?: 0 | 1 },
|
|
112
|
+
): SchemaBuilder<T[], "value"> {
|
|
113
|
+
return makeBuilder({ type: "array", items: getSchema(items), ...options }, "value");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function object<P extends Record<string, SchemaBuilder<unknown>>>(
|
|
117
|
+
properties: P,
|
|
118
|
+
): SchemaBuilder<{ -readonly [K in keyof P]: Infer<P[K]> }, "object"> {
|
|
119
|
+
return makeBuilder(
|
|
120
|
+
{
|
|
121
|
+
type: "object",
|
|
122
|
+
properties: Object.fromEntries(
|
|
123
|
+
Object.entries(properties).map(([key, value]) => [key, getSchema(value)]),
|
|
124
|
+
),
|
|
125
|
+
required: Object.keys(properties),
|
|
126
|
+
additionalProperties: false,
|
|
127
|
+
},
|
|
128
|
+
"object",
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export const j = {
|
|
133
|
+
string,
|
|
134
|
+
number,
|
|
135
|
+
integer,
|
|
136
|
+
boolean,
|
|
137
|
+
enum: enumBuilder,
|
|
138
|
+
array,
|
|
139
|
+
object,
|
|
140
|
+
datetime: () => formattedString("date-time"),
|
|
141
|
+
date: () => formattedString("date"),
|
|
142
|
+
time: () => formattedString("time"),
|
|
143
|
+
duration: () => formattedString("duration"),
|
|
144
|
+
email: () => formattedString("email"),
|
|
145
|
+
hostname: () => formattedString("hostname"),
|
|
146
|
+
ipv4: () => formattedString("ipv4"),
|
|
147
|
+
ipv6: () => formattedString("ipv6"),
|
|
148
|
+
uuid: () => formattedString("uuid"),
|
|
149
|
+
};
|