@notionhq/apps 0.0.6 → 0.0.9

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 CHANGED
@@ -1,5 +1,8 @@
1
1
  # Apps SDK
2
2
 
3
+ [![CI](https://github.com/makenotion/apps-sdk/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/makenotion/apps-sdk/actions/workflows/ci.yml?query=branch%3Amain)
4
+ [![Publish](https://github.com/makenotion/apps-sdk/actions/workflows/publish.yml/badge.svg?branch=main)](https://github.com/makenotion/apps-sdk/actions/workflows/publish.yml?query=branch%3Amain)
5
+
3
6
  ## Development
4
7
 
5
8
  1. Install [mise](https://mise.jdx.dev/getting-started.html)
@@ -11,9 +14,29 @@ The SDK requires Node 26 or newer. It supports workflows and database syncs.
11
14
 
12
15
  ```ts
13
16
  import { triggers } from "@notionhq/apps/triggers";
14
- import { createWorkflow } from "@notionhq/apps/workflow";
17
+ import { connections, createWorkflow } from "@notionhq/apps/workflow";
15
18
  ```
16
19
 
20
+ Add a connection requirement to a workflow when it needs Calendar:
21
+
22
+ ```ts
23
+ export default createWorkflow({
24
+ name: "Schedule follow-up",
25
+ description: "Schedules a follow-up after a page is created",
26
+ triggers: [triggers.notionPageCreated()],
27
+ connections: [connections.calendar()],
28
+ handler: async () => {},
29
+ });
30
+ ```
31
+
32
+ Declaring the same connection more than once causes the app build to fail.
33
+
34
+ ## Notion API access
35
+
36
+ Use `context.notion` for Notion API calls. Deployed apps receive Notion API
37
+ credentials automatically, so do not configure or push `NOTION_API_TOKEN`.
38
+ Local execution needs the token in `.env` before making a Notion API request.
39
+
17
40
  ## Database syncs
18
41
 
19
42
  Apps declare a database schema, and users attach a real Notion database to it. Apps SDK
package/dist/context.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Client } from "@notionhq/client";
2
- const FIX_STEPS = "\n\nTo fix this:\n1. Create a personal access token at https://app.notion.com/developers/tokens\n2. Add NOTION_API_TOKEN=<your-token> to your .env file\n3. For deployed apps, run: ntn apps env push";
2
+ const LOCAL_FIX_STEPS = "\n\nFor local execution:\n1. Create a personal access token at https://app.notion.com/developers/tokens\n2. Add NOTION_API_TOKEN=<your-token> to your .env file\n\nDeployed apps receive Notion API credentials automatically. Do not push NOTION_API_TOKEN.";
3
3
  function missingTokenMessage() {
4
- return "NOTION_API_TOKEN is not set. context.notion requires an API token to make requests from app capabilities to Notion." + FIX_STEPS;
4
+ return "NOTION_API_TOKEN is not set. context.notion requires an API token when an app capability runs locally." + LOCAL_FIX_STEPS;
5
5
  }
6
6
  function createUnauthenticatedNotionProxy() {
7
7
  return new Proxy({}, {
@@ -8,6 +8,15 @@ type HandlerOptions = {
8
8
  export type WorkflowEvent = WorkflowEventMap[keyof WorkflowEventMap];
9
9
  export type WorkflowEventForTrigger<T extends WorkflowTrigger> = WorkflowEventMap[T["type"]];
10
10
  export type WorkflowEventForTriggers<T extends readonly WorkflowTrigger[]> = WorkflowEventForTrigger<T[number]>;
11
+ /** A connection that a workflow needs before it can run. */
12
+ export type WorkflowConnection = {
13
+ type: "calendar";
14
+ };
15
+ /** Connection requirements supported by workflow capabilities. */
16
+ export declare const connections: {
17
+ /** Require an authorized Calendar connection. */
18
+ calendar(): WorkflowConnection;
19
+ };
11
20
  /**
12
21
  * Configuration passed to {@link createWorkflow}.
13
22
  */
@@ -26,6 +35,10 @@ export type WorkflowConfiguration<TTriggers extends readonly [WorkflowTrigger, .
26
35
  * Each trigger defines a specific event or condition that causes the workflow to run.
27
36
  */
28
37
  triggers: TTriggers;
38
+ /**
39
+ * Connections that must be set up before this workflow can run.
40
+ */
41
+ connections?: readonly WorkflowConnection[];
29
42
  handler: (event: WorkflowEventForTriggers<TTriggers>, context: WorkflowContext) => Promise<void> | void;
30
43
  };
31
44
  /**
@@ -43,6 +56,7 @@ export type Workflow<TTriggers extends readonly [WorkflowTrigger, ...WorkflowTri
43
56
  name: string;
44
57
  description: string;
45
58
  triggers: TTriggers;
59
+ connections?: readonly WorkflowConnection[];
46
60
  };
47
61
  handler: (event: WorkflowEventForTriggers<TTriggers>, options?: HandlerOptions) => Promise<{
48
62
  status: "success";
@@ -1 +1 @@
1
- {"version":3,"file":"workflow.d.ts","sourceRoot":"","sources":["../src/workflow.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAKtD,OAAO,EAAmB,KAAK,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAC1E,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAEjF,KAAK,cAAc,GAAG;IACrB,yEAAyE;IACzE,cAAc,CAAC,EAAE,IAAI,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG,gBAAgB,CAAC,MAAM,gBAAgB,CAAC,CAAC;AAErE,MAAM,MAAM,uBAAuB,CAAC,CAAC,SAAS,eAAe,IAAI,gBAAgB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;AAE7F,MAAM,MAAM,wBAAwB,CAAC,CAAC,SAAS,SAAS,eAAe,EAAE,IACxE,uBAAuB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;AAEpC;;GAEG;AACH,MAAM,MAAM,qBAAqB,CAChC,SAAS,SAAS,SAAS,CAAC,eAAe,EAAE,GAAG,eAAe,EAAE,CAAC,IAC/D;IACH;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;IAEpB;;;;OAIG;IACH,QAAQ,EAAE,SAAS,CAAC;IAEpB,OAAO,EAAE,CACR,KAAK,EAAE,wBAAwB,CAAC,SAAS,CAAC,EAC1C,OAAO,EAAE,eAAe,KACpB,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAC1B,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,QAAQ,CACnB,SAAS,SAAS,SAAS,CAAC,eAAe,EAAE,GAAG,eAAe,EAAE,CAAC,GAAG,SAAS;IAC7E,eAAe;IACf,GAAG,eAAe,EAAE;CACpB,IACE;IACH,IAAI,EAAE,UAAU,CAAC;IACjB,MAAM,EAAE;QACP,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,QAAQ,EAAE,SAAS,CAAC;KACpB,CAAC;IACF,OAAO,EAAE,CACR,KAAK,EAAE,wBAAwB,CAAC,SAAS,CAAC,EAC1C,OAAO,CAAC,EAAE,cAAc,KACpB,OAAO,CAAC;QAAE,MAAM,EAAE,SAAS,CAAA;KAAE,GAAG,SAAS,CAAC,CAAC;CAChD,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,cAAc,CAC7B,KAAK,CAAC,SAAS,SAAS,SAAS,CAAC,eAAe,EAAE,GAAG,eAAe,EAAE,CAAC,EACvE,aAAa,EAAE,qBAAqB,CAAC,SAAS,CAAC,GAAG,QAAQ,CAAC,SAAS,CAAC,CA6CtE;AAED,yCAAyC;AACzC,MAAM,MAAM,WAAW,GAAG;IACzB;;;OAGG;IACH,EAAE,EAAE,MAAM,CAAC;CACX,CAAC;AAEF,KAAK,kBAAkB,CAAC,CAAC,IAAI,CAAC,SAAS,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC;AAEvD,mCAAmC;AACnC,MAAM,MAAM,mBAAmB,GAAG;IACjC;;;;OAIG;IACH,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;CACvB,CAAC;AAEF,KAAK,YAAY,GAAG;IACnB,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,CAAC;IAChG,CAAC,CAAC,EACD,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,mBAAmB,EAC5B,EAAE,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAC1C,OAAO,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,CAAC;CAClC,CAAC;AAEF,4CAA4C;AAC5C,MAAM,MAAM,eAAe,GAAG,iBAAiB,GAC9C,WAAW,GAAG;IACb;;;;;;;;;;;;;;;;;;;OAmBG;IACH,IAAI,EAAE,YAAY,CAAC;CACnB,CAAC"}
1
+ {"version":3,"file":"workflow.d.ts","sourceRoot":"","sources":["../src/workflow.ts"],"names":[],"mappings":"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"}
package/dist/workflow.js CHANGED
@@ -6,13 +6,29 @@ import { ExecutionError } from "./error.js";
6
6
  import { writeOutput } from "./output.js";
7
7
  import { resolveRuntimeInput } from "./runtime-input.js";
8
8
  import { readRunMetadata } from "./runtime-metadata.js";
9
+ const connections = {
10
+ /** Require an authorized Calendar connection. */
11
+ calendar() {
12
+ return { type: "calendar" };
13
+ }
14
+ };
9
15
  function createWorkflow(configuration) {
16
+ if (configuration.connections !== void 0) {
17
+ const connectionTypes = /* @__PURE__ */ new Set();
18
+ for (const connection of configuration.connections) {
19
+ if (connectionTypes.has(connection.type)) {
20
+ throw new Error(`Duplicate workflow connection: ${connection.type}`);
21
+ }
22
+ connectionTypes.add(connection.type);
23
+ }
24
+ }
10
25
  return {
11
26
  _tag: "workflow",
12
27
  config: {
13
28
  name: configuration.name,
14
29
  description: configuration.description,
15
- triggers: configuration.triggers
30
+ triggers: configuration.triggers,
31
+ ...configuration.connections === void 0 ? {} : { connections: configuration.connections }
16
32
  },
17
33
  async handler(event, options) {
18
34
  try {
@@ -208,5 +224,6 @@ function isNodeError(error) {
208
224
  return error instanceof Error && "code" in error;
209
225
  }
210
226
  export {
227
+ connections,
211
228
  createWorkflow
212
229
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@notionhq/apps",
3
- "version": "0.0.6",
3
+ "version": "0.0.9",
4
4
  "description": "An SDK for building workflow apps for Notion",
5
5
  "license": "MIT",
6
6
  "bin": {
package/src/context.ts CHANGED
@@ -6,17 +6,17 @@ export type CapabilityContext = {
6
6
  notion: Client;
7
7
  };
8
8
 
9
- const FIX_STEPS =
10
- "\n\nTo fix this:\n" +
9
+ const LOCAL_FIX_STEPS =
10
+ "\n\nFor local execution:\n" +
11
11
  "1. Create a personal access token at https://app.notion.com/developers/tokens\n" +
12
- "2. Add NOTION_API_TOKEN=<your-token> to your .env file\n" +
13
- "3. For deployed apps, run: ntn apps env push";
12
+ "2. Add NOTION_API_TOKEN=<your-token> to your .env file\n\n" +
13
+ "Deployed apps receive Notion API credentials automatically. Do not push NOTION_API_TOKEN.";
14
14
 
15
15
  function missingTokenMessage(): string {
16
16
  return (
17
17
  "NOTION_API_TOKEN is not set. " +
18
- "context.notion requires an API token to make requests from app capabilities to Notion." +
19
- FIX_STEPS
18
+ "context.notion requires an API token when an app capability runs locally." +
19
+ LOCAL_FIX_STEPS
20
20
  );
21
21
  }
22
22
 
@@ -8,7 +8,7 @@ import { afterEach, beforeEach, describe, expect, expectTypeOf, it, vi, type Moc
8
8
  import { ExecutionError } from "./error.js";
9
9
  import type { RunMetadata } from "./runtime-metadata.js";
10
10
  import { triggers, type NotionPageCreatedEvent } from "./triggers.generated.js";
11
- import { createWorkflow, type StepContext, type WorkflowContext } from "./workflow.js";
11
+ import { connections, createWorkflow, type StepContext, type WorkflowContext } from "./workflow.js";
12
12
 
13
13
  let stdoutSpy: Mock<typeof process.stdout.write>;
14
14
  let checkpointDirectory: string | undefined;
@@ -738,6 +738,30 @@ describe("createWorkflow", () => {
738
738
  });
739
739
  });
740
740
 
741
+ it("declares a Calendar connection requirement", () => {
742
+ const workflow = createWorkflow({
743
+ name: "Schedule Follow-up",
744
+ description: "Schedules a follow-up after a page is created",
745
+ triggers: [triggers.notionPageCreated()],
746
+ connections: [connections.calendar()],
747
+ handler: () => {},
748
+ });
749
+
750
+ expect(workflow.config.connections).toEqual([{ type: "calendar" }]);
751
+ });
752
+
753
+ it("rejects duplicate connection requirements", () => {
754
+ expect(() =>
755
+ createWorkflow({
756
+ name: "Schedule Follow-up",
757
+ description: "Schedules a follow-up after a page is created",
758
+ triggers: [triggers.notionPageCreated()],
759
+ connections: [connections.calendar(), connections.calendar()],
760
+ handler: () => {},
761
+ }),
762
+ ).toThrowError("Duplicate workflow connection: calendar");
763
+ });
764
+
741
765
  it("writes a success envelope after the handler runs", async () => {
742
766
  const handler = vi.fn();
743
767
  const workflow = createWorkflow({
package/src/workflow.ts CHANGED
@@ -22,6 +22,19 @@ export type WorkflowEventForTrigger<T extends WorkflowTrigger> = WorkflowEventMa
22
22
  export type WorkflowEventForTriggers<T extends readonly WorkflowTrigger[]> =
23
23
  WorkflowEventForTrigger<T[number]>;
24
24
 
25
+ /** A connection that a workflow needs before it can run. */
26
+ export type WorkflowConnection = {
27
+ type: "calendar";
28
+ };
29
+
30
+ /** Connection requirements supported by workflow capabilities. */
31
+ export const connections = {
32
+ /** Require an authorized Calendar connection. */
33
+ calendar(): WorkflowConnection {
34
+ return { type: "calendar" };
35
+ },
36
+ };
37
+
25
38
  /**
26
39
  * Configuration passed to {@link createWorkflow}.
27
40
  */
@@ -45,6 +58,11 @@ export type WorkflowConfiguration<
45
58
  */
46
59
  triggers: TTriggers;
47
60
 
61
+ /**
62
+ * Connections that must be set up before this workflow can run.
63
+ */
64
+ connections?: readonly WorkflowConnection[];
65
+
48
66
  handler: (
49
67
  event: WorkflowEventForTriggers<TTriggers>,
50
68
  context: WorkflowContext,
@@ -68,6 +86,7 @@ export type Workflow<
68
86
  name: string;
69
87
  description: string;
70
88
  triggers: TTriggers;
89
+ connections?: readonly WorkflowConnection[];
71
90
  };
72
91
  handler: (
73
92
  event: WorkflowEventForTriggers<TTriggers>,
@@ -104,12 +123,25 @@ export type Workflow<
104
123
  export function createWorkflow<
105
124
  const TTriggers extends readonly [WorkflowTrigger, ...WorkflowTrigger[]],
106
125
  >(configuration: WorkflowConfiguration<TTriggers>): Workflow<TTriggers> {
126
+ if (configuration.connections !== undefined) {
127
+ const connectionTypes = new Set<string>();
128
+ for (const connection of configuration.connections) {
129
+ if (connectionTypes.has(connection.type)) {
130
+ throw new Error(`Duplicate workflow connection: ${connection.type}`);
131
+ }
132
+ connectionTypes.add(connection.type);
133
+ }
134
+ }
135
+
107
136
  return {
108
137
  _tag: "workflow",
109
138
  config: {
110
139
  name: configuration.name,
111
140
  description: configuration.description,
112
141
  triggers: configuration.triggers,
142
+ ...(configuration.connections === undefined
143
+ ? {}
144
+ : { connections: configuration.connections }),
113
145
  },
114
146
  async handler(
115
147
  event: WorkflowEventForTriggers<TTriggers>,