@withone/connect 0.18.0 → 0.19.1

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
@@ -57,7 +57,7 @@ The button, the routes and the calls are the same either way. Only what your ser
57
57
  | Expires | Nothing | The access token per your app's Token lifetime; the refresh token after 30 days |
58
58
  | You also run | Nothing | A daily refresh job |
59
59
  | When a user revokes | The next call throws `reconnect_required` | The next refresh throws `refresh_failed` |
60
- | A call outside the grant | `403` with `blockedByGrant: true` | `403` (`blockedByGrant` stays `false`) |
60
+ | A call outside the grant | `403` with `blockedByGrant: true` | The same |
61
61
  | Pick it when | You are starting out | You need the bearer token itself, or your app already stores tokens |
62
62
 
63
63
  The mode is the credential you pass to `createOneConnect`: `connectKey` and `userStore` for key mode, `tokenStore` for token mode. Sections 1 to 6 use key mode; [section 7](#7--token-mode) shows what changes for token mode.
@@ -84,6 +84,41 @@ ONE_REDIRECT_URI=https://yourapp.com/api/one/callback # exactly the registered
84
84
  ONE_PERMISSION_SET=… # optional: the Permission set ID
85
85
  ```
86
86
 
87
+ ### Or from code
88
+
89
+ Describe the app in `one.connect.ts` and create it with the [One CLI](https://www.npmjs.com/package/@withone/cli) (2.10.0 or later). Running it again updates the same app.
90
+
91
+ ```ts
92
+ // one.connect.ts
93
+ import { defineConnectApp } from "@withone/connect/config";
94
+
95
+ export default defineConnectApp({
96
+ name: "Mastra",
97
+ logoUrl: "https://mastra.ai/logo.png",
98
+ redirectUris: ["http://localhost:3000/api/one/callback"],
99
+ ask: {
100
+ gmail: { access: "custom", actions: ["sendEmail", "listAndViewEmailThreads"] },
101
+ "google-calendar": { access: "read-only" },
102
+ linear: { access: "read-write" },
103
+ },
104
+ });
105
+ ```
106
+
107
+ ```bash
108
+ one connect types # optional: tool slugs and action names autocomplete
109
+ one connect create # opens your browser to approve it, then writes .env
110
+ ```
111
+
112
+ The CLI opens a page in your signed-in browser showing the app as your users will see it. You approve it there; a new app's client secret and connect key are made on that page and shown once, so they never pass through the terminal. The CLI then writes `ONE_CLIENT_ID`, `ONE_PERMISSION_SET` and `ONE_REDIRECT_URI` to `.env`. `access` is `read-only`, `read-write`, `full-access`, or `custom` with `actions`, named as `one connect types` wrote them or by id. On Node older than 22.18, name the file `one.connect.mjs` and type it with `/** @type {import("@withone/connect/config").ConnectAppConfig} */`.
113
+
114
+ | Prop | |
115
+ |---|---|
116
+ | `name`, `redirectUris` | Required. Every callback URL, character for character. |
117
+ | `reason` | One sentence on the card telling users why, at most 200 characters. |
118
+ | `description`, `logoUrl`, `homepageUrl` | Optional. |
119
+ | `branding` | `panelColor`, `buttonColor` (`#RRGGBB`), `cobrandEmail`. |
120
+ | `ask` | Per tool slug: `access` is `read-only`, `read-write`, `full-access`, or `custom` with `actions`. Omit it to let users choose from everything they have connected. |
121
+
87
122
  ## 2 · The server client
88
123
 
89
124
  Your server holds one **connect key** for the app and saves one id per user who connects. Nothing expires and nothing is refreshed: One checks the user's consent on every call.
@@ -484,6 +519,14 @@ The job renews both tokens when either is within 3 days of expiring. Without it,
484
519
 
485
520
  `useOneConnect` and `createConnectFlow` take `authorizeUrl`, `connectTheme`, `connector`, `returnTo`, `onSuccess`, `onError` and `onCancel`.
486
521
 
522
+ ### Config · `@withone/connect/config`
523
+
524
+ | Export | |
525
+ |---|---|
526
+ | `defineConnectApp(config)` | Returns `config` unchanged; types `one.connect.ts`. |
527
+ | `ConnectAppConfig`, `ConnectToolAsk`, `ConnectAccess`, `ConnectAppBranding` | The config's types. |
528
+ | `ConnectRegistry` | Filled in by `one connect types` so slugs and action names autocomplete. |
529
+
487
530
  ## Support
488
531
 
489
532
  - Bugs and questions: [GitHub issues](https://github.com/withoneai/connect/issues).
@@ -0,0 +1,71 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * A Connect app described as code, for `one connect create`.
5
+ *
6
+ * // one.connect.ts
7
+ * import { defineConnectApp } from "@withone/connect/config";
8
+ *
9
+ * export default defineConnectApp({
10
+ * name: "Mastra",
11
+ * redirectUris: ["http://localhost:3000/api/one/callback"],
12
+ * ask: {
13
+ * gmail: { access: "custom", actions: ["sendEmail"] },
14
+ * "google-calendar": { access: "read-only" },
15
+ * linear: { access: "read-write" },
16
+ * },
17
+ * });
18
+ *
19
+ * Types and one identity helper; nothing here calls One. `one connect
20
+ * create` reads the file and opens a page in your signed-in browser, where
21
+ * you approve the app (or, on a re-run, its new version). A new app's
22
+ * client secret and connect key are made and shown there once; neither is
23
+ * ever part of this file or passes through the CLI.
24
+ */
25
+
26
+ /**
27
+ * Filled in by `one connect types`, which writes a declaration file naming
28
+ * every tool and, for the tools your `ask` names, every action:
29
+ *
30
+ * declare module "@withone/connect/config" {
31
+ * interface ConnectRegistry {
32
+ * tools: "gmail" | "google-calendar" | "linear" | …;
33
+ * actions: { gmail: "sendEmail" | "listAndViewEmailThreads" | … };
34
+ * }
35
+ * }
36
+ *
37
+ * Until it runs, any tool slug and any action name type-check.
38
+ */
39
+
40
+ /** A tool's slug, as One's catalog names it: "gmail", "google-calendar". */
41
+
42
+ /** An action named for the config: its title in camel case ("sendEmail"),
43
+ * from the names `one connect types` wrote for that tool. */
44
+
45
+ /** An action as `actions` takes it: by name, or by its id from One's
46
+ * catalog (`one actions search` prints it). */
47
+
48
+ /**
49
+ * How much of a tool the app asks for, in the dashboard's words.
50
+ * - `read-only`: reads only.
51
+ * - `read-write`: reads, creates and updates; never deletes.
52
+ * - `full-access`: everything the tool offers.
53
+ */
54
+
55
+ /** One tool's ask: a level, or a hand-picked list of actions. */
56
+
57
+ /** A colour as `#RRGGBB`. */
58
+
59
+ /** How the connect card and its sign-in email look. Omit it to keep
60
+ * One's own look. */
61
+
62
+ /**
63
+ * Returns `config` unchanged. It exists for the editor: the argument is
64
+ * checked against {@link ConnectAppConfig}, and after `one connect types`
65
+ * tool slugs and action names autocomplete.
66
+ */
67
+ function defineConnectApp(config) {
68
+ return config;
69
+ }
70
+
71
+ exports.defineConnectApp = defineConnectApp;
@@ -0,0 +1,102 @@
1
+ /**
2
+ * A Connect app described as code, for `one connect create`.
3
+ *
4
+ * // one.connect.ts
5
+ * import { defineConnectApp } from "@withone/connect/config";
6
+ *
7
+ * export default defineConnectApp({
8
+ * name: "Mastra",
9
+ * redirectUris: ["http://localhost:3000/api/one/callback"],
10
+ * ask: {
11
+ * gmail: { access: "custom", actions: ["sendEmail"] },
12
+ * "google-calendar": { access: "read-only" },
13
+ * linear: { access: "read-write" },
14
+ * },
15
+ * });
16
+ *
17
+ * Types and one identity helper; nothing here calls One. `one connect
18
+ * create` reads the file and opens a page in your signed-in browser, where
19
+ * you approve the app (or, on a re-run, its new version). A new app's
20
+ * client secret and connect key are made and shown there once; neither is
21
+ * ever part of this file or passes through the CLI.
22
+ */
23
+ /**
24
+ * Filled in by `one connect types`, which writes a declaration file naming
25
+ * every tool and, for the tools your `ask` names, every action:
26
+ *
27
+ * declare module "@withone/connect/config" {
28
+ * interface ConnectRegistry {
29
+ * tools: "gmail" | "google-calendar" | "linear" | …;
30
+ * actions: { gmail: "sendEmail" | "listAndViewEmailThreads" | … };
31
+ * }
32
+ * }
33
+ *
34
+ * Until it runs, any tool slug and any action name type-check.
35
+ */
36
+ export interface ConnectRegistry {
37
+ }
38
+ /** A tool's slug, as One's catalog names it: "gmail", "google-calendar". */
39
+ export type ConnectToolSlug = ConnectRegistry extends {
40
+ tools: infer T extends string;
41
+ } ? T : string;
42
+ /** An action named for the config: its title in camel case ("sendEmail"),
43
+ * from the names `one connect types` wrote for that tool. */
44
+ export type ConnectActionName<T extends string> = ConnectRegistry extends {
45
+ actions: infer A;
46
+ } ? T extends keyof A ? A[T] & string : string : string;
47
+ /** An action as `actions` takes it: by name, or by its id from One's
48
+ * catalog (`one actions search` prints it). */
49
+ export type ConnectActionRef<T extends string> = ConnectActionName<T> | `conn_mod_def::${string}`;
50
+ /**
51
+ * How much of a tool the app asks for, in the dashboard's words.
52
+ * - `read-only`: reads only.
53
+ * - `read-write`: reads, creates and updates; never deletes.
54
+ * - `full-access`: everything the tool offers.
55
+ */
56
+ export type ConnectAccess = "read-only" | "read-write" | "full-access";
57
+ /** One tool's ask: a level, or a hand-picked list of actions. */
58
+ export type ConnectToolAsk<T extends string = string> = {
59
+ access: ConnectAccess;
60
+ } | {
61
+ access: "custom";
62
+ actions: readonly [ConnectActionRef<T>, ...ConnectActionRef<T>[]];
63
+ };
64
+ /** A colour as `#RRGGBB`. */
65
+ export type ConnectHexColor = `#${string}`;
66
+ /** How the connect card and its sign-in email look. Omit it to keep
67
+ * One's own look. */
68
+ export interface ConnectAppBranding {
69
+ /** The card's side panel. */
70
+ panelColor: ConnectHexColor;
71
+ /** Authorize, Continue and the other primary buttons. */
72
+ buttonColor: ConnectHexColor;
73
+ /** Whether the sign-in email carries the app's name, logo and colours.
74
+ * `false` when omitted. */
75
+ cobrandEmail?: boolean;
76
+ }
77
+ export interface ConnectAppConfig {
78
+ /** Shown on the connect card and in the user's list of apps. */
79
+ name: string;
80
+ /** Every callback URL the app's routes run on, character for character:
81
+ * local first is fine, then production. */
82
+ redirectUris: readonly [string, ...string[]];
83
+ description?: string;
84
+ /** A public https URL to a square image. */
85
+ logoUrl?: string;
86
+ homepageUrl?: string;
87
+ /** One sentence on the card telling users why the app asks, at most
88
+ * 200 characters. */
89
+ reason?: string;
90
+ branding?: ConnectAppBranding;
91
+ /** The tools the app asks for, keyed by slug. Omit it to let users
92
+ * choose from everything they have connected. */
93
+ ask?: {
94
+ [T in ConnectToolSlug]?: ConnectToolAsk<T>;
95
+ };
96
+ }
97
+ /**
98
+ * Returns `config` unchanged. It exists for the editor: the argument is
99
+ * checked against {@link ConnectAppConfig}, and after `one connect types`
100
+ * tool slugs and action names autocomplete.
101
+ */
102
+ export declare function defineConnectApp<const C extends ConnectAppConfig>(config: C): C;
@@ -0,0 +1,69 @@
1
+ /**
2
+ * A Connect app described as code, for `one connect create`.
3
+ *
4
+ * // one.connect.ts
5
+ * import { defineConnectApp } from "@withone/connect/config";
6
+ *
7
+ * export default defineConnectApp({
8
+ * name: "Mastra",
9
+ * redirectUris: ["http://localhost:3000/api/one/callback"],
10
+ * ask: {
11
+ * gmail: { access: "custom", actions: ["sendEmail"] },
12
+ * "google-calendar": { access: "read-only" },
13
+ * linear: { access: "read-write" },
14
+ * },
15
+ * });
16
+ *
17
+ * Types and one identity helper; nothing here calls One. `one connect
18
+ * create` reads the file and opens a page in your signed-in browser, where
19
+ * you approve the app (or, on a re-run, its new version). A new app's
20
+ * client secret and connect key are made and shown there once; neither is
21
+ * ever part of this file or passes through the CLI.
22
+ */
23
+
24
+ /**
25
+ * Filled in by `one connect types`, which writes a declaration file naming
26
+ * every tool and, for the tools your `ask` names, every action:
27
+ *
28
+ * declare module "@withone/connect/config" {
29
+ * interface ConnectRegistry {
30
+ * tools: "gmail" | "google-calendar" | "linear" | …;
31
+ * actions: { gmail: "sendEmail" | "listAndViewEmailThreads" | … };
32
+ * }
33
+ * }
34
+ *
35
+ * Until it runs, any tool slug and any action name type-check.
36
+ */
37
+
38
+ /** A tool's slug, as One's catalog names it: "gmail", "google-calendar". */
39
+
40
+ /** An action named for the config: its title in camel case ("sendEmail"),
41
+ * from the names `one connect types` wrote for that tool. */
42
+
43
+ /** An action as `actions` takes it: by name, or by its id from One's
44
+ * catalog (`one actions search` prints it). */
45
+
46
+ /**
47
+ * How much of a tool the app asks for, in the dashboard's words.
48
+ * - `read-only`: reads only.
49
+ * - `read-write`: reads, creates and updates; never deletes.
50
+ * - `full-access`: everything the tool offers.
51
+ */
52
+
53
+ /** One tool's ask: a level, or a hand-picked list of actions. */
54
+
55
+ /** A colour as `#RRGGBB`. */
56
+
57
+ /** How the connect card and its sign-in email look. Omit it to keep
58
+ * One's own look. */
59
+
60
+ /**
61
+ * Returns `config` unchanged. It exists for the editor: the argument is
62
+ * checked against {@link ConnectAppConfig}, and after `one connect types`
63
+ * tool slugs and action names autocomplete.
64
+ */
65
+ function defineConnectApp(config) {
66
+ return config;
67
+ }
68
+
69
+ export { defineConnectApp };
package/dist/types.d.ts CHANGED
@@ -77,9 +77,9 @@ export interface ConnectButtonLogo {
77
77
  name: string;
78
78
  imageUrl: string;
79
79
  }
80
- /** @deprecated Renamed to `ConnectButtonLogoInput`; removed in 0.19.0. */
80
+ /** @deprecated Renamed to `ConnectButtonLogoInput`; removed in 0.20.0. */
81
81
  export type ConnectButtonPlatformInput = ConnectButtonLogoInput;
82
- /** @deprecated Renamed to `ConnectButtonLogo`; removed in 0.19.0. */
82
+ /** @deprecated Renamed to `ConnectButtonLogo`; removed in 0.20.0. */
83
83
  export type ConnectButtonPlatform = ConnectButtonLogo;
84
84
  export type ConnectButtonVariant = "default" | "accent" | "block";
85
85
  export type ConnectButtonSize = "sm" | "md" | "lg";
@@ -94,7 +94,7 @@ export interface ConnectButtonProps {
94
94
  * into a "+N" chip. Decoration only; the permission set decides what
95
95
  * One asks for. */
96
96
  logos?: ConnectButtonLogoInput[];
97
- /** @deprecated Renamed to `logos`; removed in 0.19.0. */
97
+ /** @deprecated Renamed to `logos`; removed in 0.20.0. */
98
98
  platforms?: ConnectButtonLogoInput[];
99
99
  /** Whether this user has a live grant, from your server. When set, it
100
100
  * decides the Connected state. When omitted, the button shows
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@withone/connect",
3
- "version": "0.18.0",
3
+ "version": "0.19.1",
4
4
  "description": "One Connect for your app: the button your users press, the two backend routes as one import, and a server client that calls One with the grant. Users keep their connections in One; your app holds only what they granted.",
5
5
  "files": [
6
6
  "dist",
@@ -105,6 +105,11 @@
105
105
  "import": "./dist/node.esm.js",
106
106
  "require": "./dist/node.cjs.js"
107
107
  },
108
+ "./config": {
109
+ "types": "./dist/config.d.ts",
110
+ "import": "./dist/config.esm.js",
111
+ "require": "./dist/config.cjs.js"
112
+ },
108
113
  "./package.json": "./package.json"
109
114
  },
110
115
  "peerDependencies": {
@@ -30,7 +30,7 @@ the server keeps differs.
30
30
  | Server keeps | one connect key for the app, one id per user | an access and a refresh token per user |
31
31
  | Expires | nothing | yes; the app runs a daily refresh job |
32
32
  | User revoked | next call throws `reconnect_required` | next refresh throws `refresh_failed` |
33
- | Call outside the grant | `403`, `blockedByGrant: true` | `403`, `blockedByGrant` stays `false` |
33
+ | Call outside the grant | `403`, `blockedByGrant: true` | the same |
34
34
 
35
35
  Use **key mode** unless the human asks for token mode, the app needs the
36
36
  bearer token itself, or the app already passes a `tokenStore`. Do not switch
@@ -39,7 +39,66 @@ key mode; section 10 lists what changes for token mode.
39
39
 
40
40
  ## 2 - Ask the human for these
41
41
 
42
- They create the app in the One dashboard: Developers -> Connect -> New app.
42
+ If the human has the One CLI (2.10.0 or later), create the app from code
43
+ instead of asking for its values.
44
+
45
+ **Before you start.** The CLI must be logged in: `one login` (or
46
+ `one init`) gives the CLI its own One API key, kept in `~/.one`. Never put
47
+ that key in the app's `.env`; the app never needs it. Install the SDK in the
48
+ project first (`npm i @withone/connect`, 0.19.0 or later): the config file
49
+ imports it.
50
+
51
+ **1. Write `one.connect.ts`** at the project root:
52
+
53
+ ```ts
54
+ import { defineConnectApp } from "@withone/connect/config";
55
+
56
+ export default defineConnectApp({
57
+ name: "Acme Assistant", // shown on the connect card
58
+ redirectUris: ["http://localhost:3000/api/one/callback"], // every callback URL, exact
59
+ reason: "Acme Assistant reads your mail and plans your week", // optional, at most 200 characters
60
+ description: "Your inbox and calendar, planned", // optional
61
+ logoUrl: "https://acme.app/logo.png", // optional, square, public https
62
+ homepageUrl: "https://acme.app", // optional
63
+ branding: { panelColor: "#0A0C0B", buttonColor: "#0A0C0B", cobrandEmail: false }, // optional
64
+ ask: { // optional: omit to let users pick from everything they have
65
+ gmail: { access: "custom", actions: ["sendEmail", "listAndViewEmailThreads"] },
66
+ "google-calendar": { access: "full-access" },
67
+ notion: { access: "read-write" },
68
+ },
69
+ });
70
+ ```
71
+
72
+ Each tool in `ask` is keyed by its slug and takes `access`: `read-only`,
73
+ `read-write` (no deletes), `full-access`, or `custom` with `actions`.
74
+
75
+ **2. Find slugs and action names** with the CLI, which reads One's catalog
76
+ with its own key:
77
+
78
+ ```bash
79
+ one connect types # writes one-connect-env.d.ts: every slug, and action names for the tools in ask
80
+ one --agent platforms # every tool slug
81
+ one --agent actions find gmail "send an email" --no-knowledge # an action's id and title
82
+ ```
83
+
84
+ Use the names `one connect types` wrote, or an action's id
85
+ (`conn_mod_def::…`). A wrong name stops `connect create` before anything
86
+ is made.
87
+
88
+ **3. Run `one --agent connect create`.** It opens a page in the human's
89
+ browser and waits up to 10 minutes, so run it in the background or with a
90
+ long timeout; the link goes to stderr at once, so pass it on if no browser
91
+ opened. The human sees the app as their users will, and approves it. For a
92
+ new app that page makes `ONE_CLIENT_SECRET` and `ONE_CONNECT_KEY` and shows
93
+ them once: tell the human to copy both into `.env`. Once approved, the CLI
94
+ writes `ONE_CLIENT_ID`, `ONE_PERMISSION_SET` and `ONE_REDIRECT_URI` to
95
+ `.env` and prints `"outcome": "created"`.
96
+
97
+ **To change the app later,** edit `one.connect.ts` and run
98
+ `one --agent connect create` again. The human approves the new version in
99
+ the browser (`"outcome": "updated"`); the secrets stay as they are.
100
+
101
+ Otherwise they create the app in the One dashboard: Developers -> Connect -> New app.
43
102
 
44
103
  | Value | Notes |
45
104
  |---|---|
@@ -337,8 +396,8 @@ const accessToken = await oneConnect.getAccessToken(userId); // when the app nee
337
396
  - When the user revokes access, the next refresh throws `refresh_failed`
338
397
  (not `reconnect_required`) and the tokens are cleared. Ask them to connect
339
398
  again.
340
- - `blockedByGrant` stays `false` in token mode; treat any `403` as outside
341
- the grant.
399
+ - A call outside the grant comes back as in key mode: `403`,
400
+ `blockedByGrant: true`, `refusal: "grant"`.
342
401
 
343
402
  Tokens expire, and two things keep them fresh. Only the first is automatic:
344
403
 
package/src/config.ts ADDED
@@ -0,0 +1,120 @@
1
+ /**
2
+ * A Connect app described as code, for `one connect create`.
3
+ *
4
+ * // one.connect.ts
5
+ * import { defineConnectApp } from "@withone/connect/config";
6
+ *
7
+ * export default defineConnectApp({
8
+ * name: "Mastra",
9
+ * redirectUris: ["http://localhost:3000/api/one/callback"],
10
+ * ask: {
11
+ * gmail: { access: "custom", actions: ["sendEmail"] },
12
+ * "google-calendar": { access: "read-only" },
13
+ * linear: { access: "read-write" },
14
+ * },
15
+ * });
16
+ *
17
+ * Types and one identity helper; nothing here calls One. `one connect
18
+ * create` reads the file and opens a page in your signed-in browser, where
19
+ * you approve the app (or, on a re-run, its new version). A new app's
20
+ * client secret and connect key are made and shown there once; neither is
21
+ * ever part of this file or passes through the CLI.
22
+ */
23
+
24
+ /**
25
+ * Filled in by `one connect types`, which writes a declaration file naming
26
+ * every tool and, for the tools your `ask` names, every action:
27
+ *
28
+ * declare module "@withone/connect/config" {
29
+ * interface ConnectRegistry {
30
+ * tools: "gmail" | "google-calendar" | "linear" | …;
31
+ * actions: { gmail: "sendEmail" | "listAndViewEmailThreads" | … };
32
+ * }
33
+ * }
34
+ *
35
+ * Until it runs, any tool slug and any action name type-check.
36
+ */
37
+ export interface ConnectRegistry {}
38
+
39
+ /** A tool's slug, as One's catalog names it: "gmail", "google-calendar". */
40
+ export type ConnectToolSlug = ConnectRegistry extends {
41
+ tools: infer T extends string;
42
+ }
43
+ ? T
44
+ : string;
45
+
46
+ /** An action named for the config: its title in camel case ("sendEmail"),
47
+ * from the names `one connect types` wrote for that tool. */
48
+ export type ConnectActionName<T extends string> = ConnectRegistry extends {
49
+ actions: infer A;
50
+ }
51
+ ? T extends keyof A
52
+ ? A[T] & string
53
+ : string
54
+ : string;
55
+
56
+ /** An action as `actions` takes it: by name, or by its id from One's
57
+ * catalog (`one actions search` prints it). */
58
+ export type ConnectActionRef<T extends string> =
59
+ ConnectActionName<T> | `conn_mod_def::${string}`;
60
+
61
+ /**
62
+ * How much of a tool the app asks for, in the dashboard's words.
63
+ * - `read-only`: reads only.
64
+ * - `read-write`: reads, creates and updates; never deletes.
65
+ * - `full-access`: everything the tool offers.
66
+ */
67
+ export type ConnectAccess = "read-only" | "read-write" | "full-access";
68
+
69
+ /** One tool's ask: a level, or a hand-picked list of actions. */
70
+ export type ConnectToolAsk<T extends string = string> =
71
+ | { access: ConnectAccess }
72
+ | {
73
+ access: "custom";
74
+ actions: readonly [ConnectActionRef<T>, ...ConnectActionRef<T>[]];
75
+ };
76
+
77
+ /** A colour as `#RRGGBB`. */
78
+ export type ConnectHexColor = `#${string}`;
79
+
80
+ /** How the connect card and its sign-in email look. Omit it to keep
81
+ * One's own look. */
82
+ export interface ConnectAppBranding {
83
+ /** The card's side panel. */
84
+ panelColor: ConnectHexColor;
85
+ /** Authorize, Continue and the other primary buttons. */
86
+ buttonColor: ConnectHexColor;
87
+ /** Whether the sign-in email carries the app's name, logo and colours.
88
+ * `false` when omitted. */
89
+ cobrandEmail?: boolean;
90
+ }
91
+
92
+ export interface ConnectAppConfig {
93
+ /** Shown on the connect card and in the user's list of apps. */
94
+ name: string;
95
+ /** Every callback URL the app's routes run on, character for character:
96
+ * local first is fine, then production. */
97
+ redirectUris: readonly [string, ...string[]];
98
+ description?: string;
99
+ /** A public https URL to a square image. */
100
+ logoUrl?: string;
101
+ homepageUrl?: string;
102
+ /** One sentence on the card telling users why the app asks, at most
103
+ * 200 characters. */
104
+ reason?: string;
105
+ branding?: ConnectAppBranding;
106
+ /** The tools the app asks for, keyed by slug. Omit it to let users
107
+ * choose from everything they have connected. */
108
+ ask?: { [T in ConnectToolSlug]?: ConnectToolAsk<T> };
109
+ }
110
+
111
+ /**
112
+ * Returns `config` unchanged. It exists for the editor: the argument is
113
+ * checked against {@link ConnectAppConfig}, and after `one connect types`
114
+ * tool slugs and action names autocomplete.
115
+ */
116
+ export function defineConnectApp<const C extends ConnectAppConfig>(
117
+ config: C,
118
+ ): C {
119
+ return config;
120
+ }
package/src/types.ts CHANGED
@@ -83,9 +83,9 @@ export interface ConnectButtonLogo {
83
83
  imageUrl: string;
84
84
  }
85
85
 
86
- /** @deprecated Renamed to `ConnectButtonLogoInput`; removed in 0.19.0. */
86
+ /** @deprecated Renamed to `ConnectButtonLogoInput`; removed in 0.20.0. */
87
87
  export type ConnectButtonPlatformInput = ConnectButtonLogoInput;
88
- /** @deprecated Renamed to `ConnectButtonLogo`; removed in 0.19.0. */
88
+ /** @deprecated Renamed to `ConnectButtonLogo`; removed in 0.20.0. */
89
89
  export type ConnectButtonPlatform = ConnectButtonLogo;
90
90
 
91
91
  export type ConnectButtonVariant = "default" | "accent" | "block";
@@ -102,7 +102,7 @@ export interface ConnectButtonProps {
102
102
  * into a "+N" chip. Decoration only; the permission set decides what
103
103
  * One asks for. */
104
104
  logos?: ConnectButtonLogoInput[];
105
- /** @deprecated Renamed to `logos`; removed in 0.19.0. */
105
+ /** @deprecated Renamed to `logos`; removed in 0.20.0. */
106
106
  platforms?: ConnectButtonLogoInput[];
107
107
  /** Whether this user has a live grant, from your server. When set, it
108
108
  * decides the Connected state. When omitted, the button shows