attio 0.0.1-experimental.20241008.2 → 0.0.1-experimental.20241010.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.
@@ -7,14 +7,13 @@ const addConnectionSchema = z.object({
7
7
  app_id: z.string(),
8
8
  connection_definition_id: z.string(),
9
9
  });
10
- export async function addConnectionDefinition({ token, devSlug, appId, key, label, description, allowMultiple, global, connectionType, clientId, clientSecret, authorizeUrl, accessTokenUrl, major, scopes, }) {
10
+ export async function addConnectionDefinition({ token, devSlug, appId, key, label, description, global, connectionType, clientId, clientSecret, authorizeUrl, accessTokenUrl, major, scopes, }) {
11
11
  const connectionDefinitionId = uuid();
12
12
  const body = {
13
13
  connection_type: connectionType,
14
14
  slug: key,
15
15
  label,
16
16
  description,
17
- allow_multiple: allowMultiple,
18
17
  global,
19
18
  major,
20
19
  };
@@ -1,11 +1,31 @@
1
1
  import React from "react";
2
2
  import { Icon } from "../icon";
3
3
  /**
4
- * Action Button
4
+ * An “action button” is a button that can appear in the Attio UI,
5
+ * typically on a record page.
6
+ *
7
+ * You must “register” the component that renders this component
8
+ * using `registerRecordAction()`.
5
9
  */
6
10
  export declare function Action(props: {
11
+ /**
12
+ * A function to execute when the button is clicked (or tapped)
13
+ */
7
14
  onTrigger: () => void;
15
+ /**
16
+ * An icon to display in the button, either an `AttioIcon` or
17
+ * a string `.png` referencing a file in your app’s `assets`
18
+ * directory.
19
+ *
20
+ * If no `icon` prop is provided, it will default to your app’s icon.
21
+ */
8
22
  icon?: Icon;
23
+ /**
24
+ * Whether or not the button is disabled. Defaults to `false`.
25
+ */
9
26
  disabled?: boolean;
27
+ /**
28
+ * The text content of the button.
29
+ */
10
30
  children: React.ReactNode;
11
31
  }): React.JSX.Element;
@@ -1,4 +1,9 @@
1
1
  import React from "react";
2
+ /**
3
+ * A step in a multi-step component.
4
+ *
5
+ * @deprecated This is not ready for use yet.
6
+ */
2
7
  export interface Step {
3
8
  title: string;
4
9
  content: React.ReactNode;
@@ -6,6 +11,8 @@ export interface Step {
6
11
  }
7
12
  /**
8
13
  * A multi-step component
14
+ *
15
+ * @deprecated This is not ready for use yet.
9
16
  */
10
17
  export declare function Multistep(props: {
11
18
  initialStep?: number;
@@ -1,6 +1,9 @@
1
1
  import React from "react";
2
2
  /**
3
- * A component for laying children out horizontally.
3
+ * A component for laying out children horizontally.
4
+ *
5
+ * On the web, it can fit up to three children horizontally,
6
+ * and on mobile, all children will be presented vertically.
4
7
  */
5
8
  export declare function Row(props: {
6
9
  children: React.ReactNode;
@@ -1,8 +1,16 @@
1
1
  import React from "react";
2
2
  /**
3
- * A section of a page
3
+ * A section of a page with a title.
4
4
  */
5
5
  export declare function Section(props: {
6
+ /**
7
+ * The title of the section.
8
+ *
9
+ * It will be displayed in a stronger font than the content of the section.
10
+ */
6
11
  title: string;
12
+ /**
13
+ * The content of the section, either text or other components.
14
+ */
7
15
  children?: React.ReactNode;
8
16
  }): React.JSX.Element;
@@ -1,8 +1,14 @@
1
1
  import React from "react";
2
2
  /**
3
- * A text block
3
+ * A text block, whose content can optionally be aligned.
4
4
  */
5
5
  export declare function TextBlock(props: {
6
+ /**
7
+ * How to align the content of the text block.
8
+ */
6
9
  align?: "flex-start" | "flex-end" | "center" | "stretch" | "baseline" | "initial" | "inherit";
10
+ /**
11
+ * The content of the text block. Usually text, but can also be other components.
12
+ */
7
13
  children: React.ReactNode;
8
14
  }): React.JSX.Element;
@@ -1,4 +1,4 @@
1
- export { useDialog } from "./use-dialog.js";
2
1
  export { useAsyncCache, AsyncCacheConfig, AsyncFunction } from "./use-async-cache.js";
2
+ export { useDialog } from "./use-dialog.js";
3
3
  export { useQuery } from "./use-query.js";
4
4
  export { useRecord } from "./use-record.js";
@@ -12,8 +12,57 @@ export interface AsyncCacheConfig {
12
12
  [K: string]: AsyncFunction<Array<any>, any> | [AsyncFunction<Array<any>, any>, ...Array<any>];
13
13
  }
14
14
  /**
15
- * A hook that returns, and caches, the results of calling multiple async functions,
16
- * typically to load data.
15
+ * A hook that returns, and caches, the results of calling one or more async functions,
16
+ * typically to load data from a third party via server functions.
17
+ *
18
+ * The component it is used in will suspend until ALL async functions have resolved.
19
+ *
20
+ * ## EXAMPLE USAGE
21
+ *
22
+ * ----
23
+ * ```ts
24
+ * // load-widgets.server.ts
25
+ * export default async function loadWidgets() : Promise<Array<Widget>> {
26
+ * // fetch widgets from somewhere
27
+ * }
28
+ * ```
29
+ * ----
30
+ * ```ts
31
+ * // load-sprockets.server.ts
32
+ * export default async function loadSprockets(
33
+ * material: Material,
34
+ * max?: number
35
+ * ): Promise<Array<Sprocket>> {
36
+ * // fetch sprockets from somewhere
37
+ * }
38
+ * ```
39
+ * ----
40
+ * ```ts
41
+ * import { useAsyncCache } from "attio/client"
42
+ *
43
+ * import loadWidgets from "./load-widgets.server.ts"
44
+ * import loadSprockets from "./load-sprockets.server.ts"
45
+ *
46
+ * ...
47
+ *
48
+ * // inside component:
49
+ *
50
+ * const results = useAsyncCache({
51
+ * // loadWidgets takes zero parameters and can thus be called without an array
52
+ * widgets: loadWidgets,
53
+ * // loadSprockets takes a `material` parameter, so we call it like this
54
+ * ironSprockets: [loadSprockets, "iron"],
55
+ * copperSprockets: [loadSprockets, "copper", 42],
56
+ * })
57
+ *
58
+ * const { widgets, ironSprockets, copperSprockets } = results.values
59
+ *
60
+ * ...
61
+ *
62
+ * // inside an event handler, perhaps, to refetch _only_ the iron sprockets
63
+ * results.invalidate("ironSprockets")
64
+ * ```
65
+ * ----
17
66
  */
18
67
  export declare function useAsyncCache<Config extends AsyncCacheConfig>(config: Config): {
19
68
  values: {
@@ -1,17 +1,71 @@
1
+ import React from "react";
2
+ /**
3
+ * Dialog component
4
+ *
5
+ * IMPORTANT: The <Dialog/> component should _always_ be rendered in your app.
6
+ * Do not render it conditionally.
7
+ */
8
+ interface Dialog extends React.FC<{
9
+ /**
10
+ * A required title for the dialog.
11
+ */
12
+ title: string;
13
+ /**
14
+ * An optional callback that will be called when the dialog is closed.
15
+ */
16
+ onClose?: () => void;
17
+ /**
18
+ * The content of the dialog.
19
+ */
20
+ children: React.ReactNode;
21
+ }> {
22
+ }
1
23
  /**
2
24
  * This hook allows you to create a modal dialog.
3
25
  *
26
+ * ## EXAMPLE USAGE
27
+ *
28
+ * ----
29
+ * ```tsx
30
+ * import { useDialog } from "attio/client"
31
+ *
32
+ * ...
33
+ *
34
+ * // inside component:
35
+ * const dialog = useDialog()
36
+ *
37
+ * return (
38
+ * <>
39
+ * <Action onTrigger={() => dialog.open()}>Do Stuff</Action>
40
+ * <dialog.Dialog>
41
+ * Here is my useful UI
42
+ * </dialog.Dialog>
43
+ * </>
44
+ * )
45
+ * ```
46
+ * ----
47
+ *
4
48
  * @returns An object with the following properties:
5
49
  * - `Dialog`: A React component that you should render with the dialog contents as its children.
6
50
  * - `open`: Opens the rendered dialog.
7
51
  * - `close`: Closes the rendered dialog.
8
52
  */
9
53
  export declare function useDialog(): {
10
- Dialog: React.ComponentType<{
11
- title: string;
12
- onClose?: () => void;
13
- children: React.ReactNode;
14
- }>;
54
+ /**
55
+ * The dialog component to render.
56
+ *
57
+ * IMPORTANT: The <Dialog/> component should _always_ be rendered in your app.
58
+ * Do not render it conditionally.
59
+ */
60
+ Dialog: Dialog;
61
+ /**
62
+ * A function to imperatively open the dialog. Often called via an
63
+ * `onTrigger` prop on an `<Action/>` button.
64
+ */
15
65
  open: () => void;
66
+ /**
67
+ * A function to imperatively close the dialog.
68
+ */
16
69
  close: () => void;
17
70
  };
71
+ export {};
@@ -1,6 +1,47 @@
1
1
  import { Query } from "../run-query";
2
- export declare function useQuery<Variables extends Record<string, any>, Result>(
3
- /** GraphQL query */
4
- query: Query<Variables, Result>,
5
- /** GraphQL query variables */
6
- variableValues?: Variables): Result;
2
+ /**
3
+ * A hook that runs a GraphQL query and returns the result.
4
+ *
5
+ * Your component will suspend until the query completes.
6
+ *
7
+ * If you import the query from a `.graphql` file, and run `attio dev`,
8
+ * your query variables and result will be strongly typed.
9
+ *
10
+ * ## EXAMPLE USAGE
11
+ *
12
+ * ----
13
+ * ```graphql
14
+ * # get-person.graphql
15
+ * query getPerson($id: String!) {
16
+ * workspace {
17
+ * person(id: $id) {
18
+ * name {
19
+ * first
20
+ * last
21
+ * }
22
+ * }
23
+ * }
24
+ * }
25
+ * ```
26
+ * ----
27
+ * ```tsx
28
+ * // Component.tsx
29
+ * import { useQuery, useRecord } from "attio/client"
30
+ * import getPersonQuery from "./get-person.graphql"
31
+ *
32
+ * ...
33
+ *
34
+ * // in component:
35
+ * const { recordId } = useRecord()
36
+ * const result = useQuery(getPersonQuery, { id: recordId })
37
+ *
38
+ * const firstName : string | null = result.workspace?.person?.name?.first
39
+ * const lastName : string | null = result.workspace?.person?.name?.last
40
+ * ```
41
+ * ----
42
+ *
43
+ * @param query - The GraphQL query to run.
44
+ * @param variableValues - The variables to pass to the query.
45
+ * @returns The result of the query.
46
+ */
47
+ export declare function useQuery<Variables extends Record<string, any> | never, Result>(query: Query<Variables, Result>, variableValues?: Variables extends never ? never : Variables): Result;
@@ -3,7 +3,12 @@ import { ObjectSlug } from "../object-slug.js";
3
3
  * Get information about the current record.
4
4
  */
5
5
  export declare function useRecord(): {
6
+ /**
7
+ * The ID of the current record.
8
+ */
6
9
  recordId: string;
10
+ /**
11
+ * The object type of the current record.
12
+ */
7
13
  object: ObjectSlug;
8
- epithet: string;
9
14
  };
@@ -1,10 +1,28 @@
1
1
  import { ObjectSlug } from "./object-slug";
2
2
  /**
3
- * Registers an action for a record in Attio.
3
+ * Registers a component that renders an <Action/> button for a record in Attio.
4
+ *
5
+ * You should use this in your `app.tsx` file to register where you want your
6
+ * app to surface inside the Attio UI.
7
+ *
8
+ * ## EXAMPLE USAGE
9
+ *
10
+ * ----
11
+ * ```tsx
12
+ * // app.tsx
13
+ * import { registerRecordAction } from "attio/client";
14
+ * import { SendInvoiceButton } from "./send-invoice-button";
15
+ * import React from "react";
16
+ *
17
+ * registerRecordAction("send-invoice", <SendInvoiceButton />);
18
+ * ```
19
+ * ----
4
20
  */
5
21
  export declare function registerRecordAction(
6
22
  /**
7
23
  * The unique identifier for the action.
24
+ *
25
+ * It is only used internally; never shown to the user.
8
26
  */
9
27
  id: string,
10
28
  /**
@@ -1,8 +1,52 @@
1
- /**
2
- * Imperatively runs a GraphQL query.
3
- */
4
- export declare function runQuery(query: string, variableValues?: Record<string, any>): Promise<any>;
5
- export type Query<Variables extends Record<string, any>, Result> = {
1
+ export type Query<Variables extends Record<string, any> | never, Result> = {
6
2
  __data: Result;
7
3
  __variables: Variables;
8
4
  } & string;
5
+ /**
6
+ * Imperatively runs a GraphQL query.
7
+ *
8
+ * If you import the query from a `.graphql` file, and run `attio dev`,
9
+ * your query variables and result will be strongly typed.
10
+ *
11
+ * ## EXAMPLE USAGE
12
+ *
13
+ * ----
14
+ * ```graphql
15
+ * # get-person.graphql
16
+ * query getPerson($id: String!) {
17
+ * workspace {
18
+ * person(id: $id) {
19
+ * name {
20
+ * first
21
+ * last
22
+ * }
23
+ * }
24
+ * }
25
+ * }
26
+ * ```
27
+ * ----
28
+ * ```tsx
29
+ * // get-person-name.ts
30
+ * import { runQuery } from "attio/client"
31
+ * import getPersonQuery from "./get-person.graphql"
32
+ *
33
+ * ...
34
+ *
35
+ * export async function getPersonName(id: string) : Promise<string> {
36
+ * const person = await runQuery(getPersonQuery, { id })
37
+ * const firstName: string | null = person.workspace?.person?.name?.first
38
+ * const lastName: string | null = person.workspace?.person?.name?.last
39
+ * return `${firstName ?? ""} ${lastName ?? ""}`
40
+ * }
41
+ * ```
42
+ * ----
43
+ *
44
+ * @param query - The GraphQL query to run.
45
+ * @param variableValues - The values of the variables to pass to the query.
46
+ * @returns A promise that resolves to the query result.
47
+ */
48
+ export declare function runQuery<Variables extends Record<string, any>, Result>(
49
+ /** GraphQL query */
50
+ query: Query<Variables, Result> | string,
51
+ /** GraphQL query variables */
52
+ variableValues?: Variables): Promise<Result>;
@@ -29,7 +29,6 @@ export const options = z.object({
29
29
  .enum(["secret", "oauth2-code"])
30
30
  .optional()
31
31
  .describe(option({ description: "The type of connection to create" })),
32
- allowMultiple: z.boolean().optional().default(false),
33
32
  authorizeUrl: z
34
33
  .string()
35
34
  .url("Invalid URL, e.g. https://authorization-server.com/authorize")
@@ -62,7 +61,7 @@ export const options = z.object({
62
61
  .default(false)
63
62
  .describe(option({ description: "Run in development mode (additional debugging info)" })),
64
63
  });
65
- export default function AddConnection({ options: { key, label, description, type: connectionType, authorizeUrl, accessTokenUrl, allowMultiple, scopes, clientId, clientSecret, dev, }, }) {
64
+ export default function AddConnection({ options: { key, label, description, type: connectionType, authorizeUrl, accessTokenUrl, scopes, clientId, clientSecret, dev, }, }) {
66
65
  const [snapshot, send] = useMachine(addConnectionMachine, {
67
66
  input: {
68
67
  key,
@@ -71,7 +70,6 @@ export default function AddConnection({ options: { key, label, description, type
71
70
  connectionType,
72
71
  authorizeUrl,
73
72
  accessTokenUrl,
74
- allowMultiple,
75
73
  scopes,
76
74
  clientId,
77
75
  clientSecret,
@@ -203,34 +201,6 @@ export default function AddConnection({ options: { key, label, description, type
203
201
  { value: false, label: "An individual user" },
204
202
  { value: true, label: "The entire workspace" },
205
203
  ], onSelect: (global) => send({ type: "Choose Global", global }) })))),
206
- snapshot.matches("Ask for allow multiple") && (React.createElement(React.Fragment, null,
207
- React.createElement(Box, null,
208
- React.createElement(Text, null, snapshot.context.global
209
- ? `${snapshot.context.connectionType === "secret"
210
- ? "Secrets"
211
- : "Tokens"} should be:`
212
- : `A user should be able to:`)),
213
- React.createElement(Box, null,
214
- React.createElement(Select, { items: [
215
- {
216
- value: false,
217
- label: snapshot.context.global
218
- ? "Limited to one per workspace"
219
- : `Create only one ${snapshot.context.connectionType === "secret"
220
- ? "secret"
221
- : "token"} that remains private`,
222
- },
223
- {
224
- value: true,
225
- label: snapshot.context.global
226
- ? `Unlimited; users can add multiple ${snapshot.context.connectionType === "secret"
227
- ? "secrets"
228
- : "token"} to each workspace`
229
- : `Create multiple ${snapshot.context.connectionType === "secret"
230
- ? "secrets"
231
- : "tokens"} and share them with other users`,
232
- },
233
- ], onSelect: (allowMultiple) => send({ type: "Choose Allow Multiple", allowMultiple }) })))),
234
204
  snapshot.matches("Ask for Authorize URL") && (React.createElement(React.Fragment, null,
235
205
  React.createElement(Box, null,
236
206
  React.createElement(Text, null, "e.g. https://authorization-server.com/oauth/authorize")),
@@ -124,7 +124,9 @@ function generateOperationFunction(graphqlFileName, operationName, variableDefin
124
124
  return `${def.variable.name.value}: ${varTypeString}`;
125
125
  })
126
126
  .join(", ");
127
- input = `export interface ${typeName}Variables { ${variables} }`;
127
+ input = variables
128
+ ? `export interface ${typeName}Variables { ${variables} }`
129
+ : `export type ${typeName}Variables = never`;
128
130
  return `
129
131
  declare module "./${graphqlFileName}" {
130
132
  ${input}
@@ -17,7 +17,7 @@ export const addConnectionMachine = setup({
17
17
  input: {},
18
18
  },
19
19
  actors: {
20
- "createConnectionDefinition": fromCallback(({ sendBack, input: { developer: { token, slug: devSlug }, config, connectionType: connection_type, key, label, description, allowMultiple, global, authorizeUrl, accessTokenUrl, clientId, clientSecret, scopes, }, }) => {
20
+ "createConnectionDefinition": fromCallback(({ sendBack, input: { developer: { token, slug: devSlug }, config, connectionType: connection_type, key, label, description, global, authorizeUrl, accessTokenUrl, clientId, clientSecret, scopes, }, }) => {
21
21
  const add = async () => {
22
22
  try {
23
23
  await addConnectionDefinition({
@@ -29,7 +29,6 @@ export const addConnectionMachine = setup({
29
29
  key,
30
30
  label,
31
31
  description,
32
- allowMultiple,
33
32
  global,
34
33
  accessTokenUrl,
35
34
  authorizeUrl,
@@ -188,9 +187,6 @@ export const addConnectionMachine = setup({
188
187
  setGlobal: assign({
189
188
  global: (_, params) => params.global,
190
189
  }),
191
- setAllowMultiple: assign({
192
- allowMultiple: (_, params) => params.allowMultiple,
193
- }),
194
190
  setAppConfig: assign({
195
191
  config: (_, params) => params.config,
196
192
  }),
@@ -215,7 +211,6 @@ export const addConnectionMachine = setup({
215
211
  "have client id": (_, params) => params.connectionType === "secret" ||
216
212
  options.shape.clientId.unwrap().safeParse(params.clientId?.trim()).success,
217
213
  "have global": (_, params) => params.global !== undefined,
218
- "have allow multiple": (_, params) => params.allowMultiple !== undefined,
219
214
  "have client secret": (_, params) => params.connectionType === "secret" ||
220
215
  options.shape.clientSecret.unwrap().safeParse(params.clientSecret?.trim()).success,
221
216
  "is oauth": (_, params) => params.connectionType === "oauth2-code",
@@ -277,7 +272,6 @@ export const addConnectionMachine = setup({
277
272
  clientId: context.clientId,
278
273
  clientSecret: context.clientSecret,
279
274
  global: context.global,
280
- allowMultiple: context.allowMultiple,
281
275
  label: context.label,
282
276
  description: context.description,
283
277
  key: context.key,
@@ -310,7 +304,7 @@ export const addConnectionMachine = setup({
310
304
  actions: { type: "setInvalidAuthorizeUrl", params: ({ context }) => context },
311
305
  },
312
306
  ],
313
- "Previous": "Ask for allow multiple",
307
+ "Previous": "Ask for global",
314
308
  "Next": {
315
309
  target: "Ask for Access Token URL",
316
310
  guard: { type: "have authorize url", params: ({ context }) => context },
@@ -564,25 +558,14 @@ export const addConnectionMachine = setup({
564
558
  ],
565
559
  },
566
560
  "Do we have global?": {
567
- always: [
568
- {
569
- target: "Do we have allow multiple?",
570
- guard: { type: "have global", params: ({ context }) => context },
571
- },
572
- {
573
- target: "Ask for global",
574
- },
575
- ],
576
- },
577
- "Do we have allow multiple?": {
578
561
  always: [
579
562
  {
580
563
  target: "Split on connection type",
581
- guard: { type: "have allow multiple", params: ({ context }) => context },
564
+ guard: { type: "have global", params: ({ context }) => context },
582
565
  reenter: true,
583
566
  },
584
567
  {
585
- target: "Ask for allow multiple",
568
+ target: "Ask for global",
586
569
  },
587
570
  ],
588
571
  },
@@ -629,28 +612,16 @@ export const addConnectionMachine = setup({
629
612
  "Ask for global": {
630
613
  on: {
631
614
  "Choose Global": {
632
- target: "Do we have allow multiple?",
615
+ target: "Split on connection type",
633
616
  actions: { type: "setGlobal", params: ({ event }) => event },
617
+ reenter: true,
634
618
  },
635
619
  "Previous": "Ask for description",
636
620
  "Next": {
637
- target: "Ask for allow multiple",
638
- guard: { type: "have global", params: ({ context }) => context },
639
- },
640
- },
641
- },
642
- "Ask for allow multiple": {
643
- on: {
644
- "Choose Allow Multiple": {
645
621
  target: "Split on connection type",
646
- actions: { type: "setAllowMultiple", params: ({ event }) => event },
622
+ guard: { type: "have global", params: ({ context }) => context },
647
623
  reenter: true,
648
624
  },
649
- "Previous": "Ask for global",
650
- "Next": {
651
- target: "Split on connection type",
652
- guard: { type: "have allow multiple", params: ({ context }) => context },
653
- },
654
625
  },
655
626
  },
656
627
  },
@@ -117,16 +117,7 @@ export const devMachine = setup({
117
117
  upload().catch((error) => sendBack({ type: "Upload Error", error }));
118
118
  }),
119
119
  "watch": fromCallback(({ sendBack }) => {
120
- const watcher = chokidar.watch([
121
- "src/app.tsx",
122
- "src/app.jsx",
123
- "src/app.ts",
124
- "src/app.js",
125
- "src/assets",
126
- "src/webhooks",
127
- "src/events",
128
- ".env",
129
- ], {
120
+ const watcher = chokidar.watch(["src", ".env"], {
130
121
  ignored: "**/*.graphql.d.ts",
131
122
  });
132
123
  watcher.on("ready", () => watcher.on("all", () => {