attio 0.0.1-experimental.20241003.1 → 0.0.1-experimental.20241007.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,11 +7,11 @@ 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, slug, label, description, allowMultiple, global, connectionType, clientId, clientSecret, authorizeUrl, accessTokenUrl, major, scopes, }) {
10
+ export async function addConnectionDefinition({ token, devSlug, appId, key, label, description, allowMultiple, global, connectionType, clientId, clientSecret, authorizeUrl, accessTokenUrl, major, scopes, }) {
11
11
  const connectionDefinitionId = uuid();
12
12
  const body = {
13
13
  connection_type: connectionType,
14
- slug,
14
+ slug: key,
15
15
  label,
16
16
  description,
17
17
  allow_multiple: allowMultiple,
@@ -11,10 +11,10 @@ import { Select } from "../../components/Select.js";
11
11
  import { addConnectionMachine, connectionTypes } from "../../machines/add-connection-machine.js";
12
12
  export const description = "Create a new connection for your Attio app";
13
13
  export const options = z.object({
14
- slug: z
14
+ key: z
15
15
  .string()
16
16
  .optional()
17
- .describe(option({ description: "A unique slug for your connection" })),
17
+ .describe(option({ description: "A unique key for your connection" })),
18
18
  label: z
19
19
  .string()
20
20
  .optional()
@@ -29,6 +29,7 @@ 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),
32
33
  authorizeUrl: z
33
34
  .string()
34
35
  .url("Invalid URL, e.g. https://authorization-server.com/authorize")
@@ -61,15 +62,16 @@ export const options = z.object({
61
62
  .default(false)
62
63
  .describe(option({ description: "Run in development mode (additional debugging info)" })),
63
64
  });
64
- export default function AddConnection({ options: { slug, label, description, type: connectionType, authorizeUrl, accessTokenUrl, scopes, clientId, clientSecret, dev, }, }) {
65
+ export default function AddConnection({ options: { key, label, description, type: connectionType, authorizeUrl, accessTokenUrl, allowMultiple, scopes, clientId, clientSecret, dev, }, }) {
65
66
  const [snapshot, send] = useMachine(addConnectionMachine, {
66
67
  input: {
67
- slug,
68
+ key,
68
69
  label,
69
70
  description,
70
71
  connectionType,
71
72
  authorizeUrl,
72
73
  accessTokenUrl,
74
+ allowMultiple,
73
75
  scopes,
74
76
  clientId,
75
77
  clientSecret,
@@ -81,19 +83,107 @@ export default function AddConnection({ options: { slug, label, description, typ
81
83
  React.createElement(Logo, null),
82
84
  React.createElement(Box, { flexDirection: "column" },
83
85
  snapshot.matches("Show config instructions") && (React.createElement(InitialInstructions, { reason: snapshot.context.configError })),
84
- snapshot.matches("Ask for slug") && (React.createElement(React.Fragment, null,
85
- React.createElement(Box, null,
86
- React.createElement(Text, null, "Unique Slug: "),
87
- React.createElement(TextInput, { value: snapshot.context.slug ?? "", onChange: (slug) => send({ type: "Update Slug", slug }), onSubmit: () => send({ type: "Submit" }) })))),
86
+ React.createElement(Box, { flexDirection: "column", marginBottom: 1 },
87
+ snapshot.context.connectionType && (React.createElement(Box, null,
88
+ React.createElement(Text, null, "Connection Type: "),
89
+ React.createElement(Text, { color: "green" }, snapshot.context.connectionType === "oauth2-code"
90
+ ? "OAuth2 Code"
91
+ : "Secret"))),
92
+ snapshot.context.label && !snapshot.matches("Ask for label") && (React.createElement(Box, null,
93
+ React.createElement(Text, null, "Label: "),
94
+ React.createElement(Text, { color: "green" }, snapshot.context.label))),
95
+ snapshot.context.key && !snapshot.matches("Ask for key") && (React.createElement(Box, null,
96
+ React.createElement(Text, null, "Connection Key: "),
97
+ React.createElement(Text, { color: "green" }, snapshot.context.key))),
98
+ snapshot.context.description && !snapshot.matches("Ask for description") && (React.createElement(Box, null,
99
+ React.createElement(Text, null, "Description: "),
100
+ React.createElement(Text, { color: "green" }, snapshot.context.description))),
101
+ snapshot.context.global !== undefined &&
102
+ !snapshot.matches("Ask for global") && (React.createElement(Box, null,
103
+ React.createElement(Text, null,
104
+ snapshot.context.connectionType === "secret"
105
+ ? "Secret"
106
+ : "Token",
107
+ " ",
108
+ "will be created for:",
109
+ " "),
110
+ React.createElement(Text, { color: "green" }, snapshot.context.global
111
+ ? "The entire workspace"
112
+ : "An individual user"))),
113
+ snapshot.context.connectionType === "oauth2-code" && (React.createElement(React.Fragment, null,
114
+ snapshot.context.authorizeUrl &&
115
+ !snapshot.matches("Ask for Authorize URL") && (React.createElement(Box, null,
116
+ React.createElement(Text, null, "Authorize URL: "),
117
+ React.createElement(Text, { color: "green" }, snapshot.context.authorizeUrl))),
118
+ snapshot.context.accessTokenUrl &&
119
+ !snapshot.matches("Ask for Access Token URL") && (React.createElement(Box, null,
120
+ React.createElement(Text, null, "Access Token URL: "),
121
+ React.createElement(Text, { color: "green" }, snapshot.context.accessTokenUrl))),
122
+ snapshot.context.scopes && !snapshot.matches("Ask for Scopes") && (React.createElement(Box, null,
123
+ React.createElement(Text, null, "Scopes: "),
124
+ React.createElement(Text, { color: "green" }, snapshot.context.scopes))),
125
+ snapshot.context.clientId &&
126
+ !snapshot.matches("Ask for Client ID") && (React.createElement(Box, null,
127
+ React.createElement(Text, null, "Client ID: "),
128
+ React.createElement(Text, { color: "green" }, snapshot.context.clientId)))))),
129
+ snapshot.matches("Ask for key") && (React.createElement(React.Fragment, null,
130
+ React.createElement(Box, { flexDirection: "column" },
131
+ React.createElement(Box, { flexDirection: "column", margin: 1, borderStyle: "round", padding: 1 },
132
+ React.createElement(Text, null,
133
+ "import ",
134
+ "{",
135
+ " connections ",
136
+ "}",
137
+ " from \"attio/server\""),
138
+ React.createElement(Box, { marginY: 1 },
139
+ React.createElement(Text, null, "...")),
140
+ React.createElement(Text, null,
141
+ "const response = await fetch(\"https://api.yourdomain.com/your-endpoint\", ",
142
+ "{"),
143
+ React.createElement(Text, null,
144
+ " ",
145
+ "method: \"GET\","),
146
+ React.createElement(Text, null,
147
+ " ",
148
+ "headers: ",
149
+ "{"),
150
+ React.createElement(Text, null,
151
+ " ",
152
+ "\"Content-Type\": \"application/json\","),
153
+ React.createElement(Box, { flexWrap: "wrap" },
154
+ React.createElement(Text, null,
155
+ " ",
156
+ "Authorization: `Bearer $",
157
+ "{",
158
+ "connections"),
159
+ React.createElement(Text, null, snapshot.context.key?.includes("-") ? '["' : "."),
160
+ React.createElement(Text, { color: "yellowBright" }, snapshot.context.key),
161
+ snapshot.context.key?.includes("-") && React.createElement(Text, null, "\"]"),
162
+ React.createElement(Text, null,
163
+ "}",
164
+ "`,")),
165
+ React.createElement(Box, { marginLeft: 41 + (snapshot.context.key?.includes("-") ? 1 : 0) },
166
+ React.createElement(Text, { color: "cyan" }, "^".repeat(snapshot.context.key?.length ?? 0))),
167
+ React.createElement(Text, null, " }"),
168
+ React.createElement(Text, null,
169
+ "}",
170
+ ");")),
171
+ React.createElement(Box, null,
172
+ React.createElement(Text, null, "Unique Connection Key: "),
173
+ React.createElement(TextInput, { value: snapshot.context.key ?? "", onChange: (key) => send({ type: "Update Key", key }), onSubmit: () => send({ type: "Submit" }) }))))),
88
174
  snapshot.matches("Ask for label") && (React.createElement(React.Fragment, null,
89
175
  React.createElement(Box, null,
90
- React.createElement(Text, null, "Provide a label for your connection. This will be displayed to users. (You can edit this later)")),
91
- React.createElement(Box, null,
176
+ React.createElement(Text, null, "Provide a label for your connection. This is what your users will see.")),
177
+ React.createElement(Box, { marginTop: 1 },
178
+ React.createElement(Text, null, "> "),
92
179
  React.createElement(TextInput, { value: snapshot.context.label ?? "", onChange: (label) => send({ type: "Update Label", label }), onSubmit: () => send({ type: "Submit" }) })))),
93
180
  snapshot.matches("Ask for description") && (React.createElement(React.Fragment, null,
94
181
  React.createElement(Box, null,
95
- React.createElement(Text, null, "Provide an optional detailed description for your connection. This will be displayed to users. (You can edit this later)")),
96
- React.createElement(Box, null,
182
+ React.createElement(Text, null, "Provide an optional detailed description for your connection.")),
183
+ React.createElement(Box, { marginTop: 1 },
184
+ React.createElement(Text, null, "This will be displayed to users. (You can edit this later)")),
185
+ React.createElement(Box, { marginTop: 1 },
186
+ React.createElement(Text, null, "> "),
97
187
  React.createElement(TextInput, { value: snapshot.context.description ?? "", onChange: (description) => send({ type: "Update Description", description }), onSubmit: () => send({ type: "Submit" }) })))),
98
188
  snapshot.matches("Ask for connection type") && (React.createElement(React.Fragment, null,
99
189
  React.createElement(Box, null,
@@ -102,30 +192,61 @@ export default function AddConnection({ options: { slug, label, description, typ
102
192
  React.createElement(Select, { items: connectionTypes, onSelect: (connectionType) => send({ type: "Choose Connection Type", connectionType }) })))),
103
193
  snapshot.matches("Ask for global") && (React.createElement(React.Fragment, null,
104
194
  React.createElement(Box, null,
105
- React.createElement(Text, null, "Is this a global connection?")),
195
+ React.createElement(Text, null,
196
+ "A",
197
+ " ",
198
+ snapshot.context.connectionType === "secret" ? "secret" : "token",
199
+ " ",
200
+ "will be created for:")),
106
201
  React.createElement(Box, null,
107
202
  React.createElement(Select, { items: [
108
- { value: true, label: "Yes" },
109
- { value: false, label: "No" },
203
+ { value: false, label: "An individual user" },
204
+ { value: true, label: "The entire workspace" },
110
205
  ], onSelect: (global) => send({ type: "Choose Global", global }) })))),
111
206
  snapshot.matches("Ask for allow multiple") && (React.createElement(React.Fragment, null,
112
207
  React.createElement(Box, null,
113
- React.createElement(Text, null, "Should this connection be allowed to be used multiple times?")),
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:`)),
114
213
  React.createElement(Box, null,
115
214
  React.createElement(Select, { items: [
116
- { value: true, label: "Yes" },
117
- { value: false, label: "No" },
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
+ },
118
233
  ], onSelect: (allowMultiple) => send({ type: "Choose Allow Multiple", allowMultiple }) })))),
119
234
  snapshot.matches("Ask for Authorize URL") && (React.createElement(React.Fragment, null,
120
235
  React.createElement(Box, null,
236
+ React.createElement(Text, null, "e.g. https://authorization-server.com/oauth/authorize")),
237
+ React.createElement(Box, { marginTop: 1 },
121
238
  React.createElement(Text, null, "OAuth Authorize URL: "),
122
239
  React.createElement(TextInput, { value: snapshot.context.authorizeUrl ?? "", onChange: (authorizeUrl) => send({ type: "Update Authorize URL", authorizeUrl }), onSubmit: () => send({ type: "Submit" }) })))),
123
240
  snapshot.matches("Ask for Access Token URL") && (React.createElement(React.Fragment, null,
124
241
  React.createElement(Box, null,
242
+ React.createElement(Text, null, "e.g. https://authorization-server.com/oauth/token")),
243
+ React.createElement(Box, { marginTop: 1 },
125
244
  React.createElement(Text, null, "OAuth Access Token URL: "),
126
245
  React.createElement(TextInput, { value: snapshot.context.accessTokenUrl ?? "", onChange: (accessTokenUrl) => send({ type: "Update Access Token URL", accessTokenUrl }), onSubmit: () => send({ type: "Submit" }) })))),
127
246
  snapshot.matches("Ask for Scopes") && (React.createElement(React.Fragment, null,
128
247
  React.createElement(Box, null,
248
+ React.createElement(Text, null, "e.g. profile,email,read,write")),
249
+ React.createElement(Box, { marginTop: 1 },
129
250
  React.createElement(Text, null, "OAuth Scopes: (separated by commas) "),
130
251
  React.createElement(TextInput, { value: snapshot.context.scopes ?? "", onChange: (scopes) => send({ type: "Update Scopes", scopes }), onSubmit: () => send({ type: "Submit" }) })))),
131
252
  snapshot.matches("Ask for Client ID") && (React.createElement(React.Fragment, null,
@@ -24,7 +24,7 @@ function findGeneratedFiles(dir) {
24
24
  for (const entry of entries) {
25
25
  const fullPath = path.join(dir, entry.name);
26
26
  if (entry.isDirectory()) {
27
- files.push(...findGraphQLFiles(fullPath));
27
+ files.push(...findGeneratedFiles(fullPath));
28
28
  }
29
29
  else if (entry.isFile() && path.extname(entry.name) === ".graphql.d.ts") {
30
30
  files.push(fullPath);
@@ -44,7 +44,7 @@ function generateTypeDefinition(type) {
44
44
  return generateTypeDefinition(type.ofType);
45
45
  }
46
46
  if (isListType(type)) {
47
- return `${generateTypeDefinition(type.ofType)}`;
47
+ return `(${generateTypeDefinition(type.ofType)})[]`;
48
48
  }
49
49
  if (isObjectType(type) || isInputObjectType(type)) {
50
50
  return type.name;
@@ -84,12 +84,18 @@ function generateTypeDefinitionFromSelectionSet(selectionSet, parentType) {
84
84
  if (selection.selectionSet) {
85
85
  if (isObjectType(getNamedType(fieldType))) {
86
86
  fieldTypeDefinition = generateTypeDefinitionFromSelectionSet(selection.selectionSet, getNamedType(fieldType));
87
+ if (isListType(fieldType)) {
88
+ fieldTypeDefinition = `(${fieldTypeDefinition})[]`;
89
+ }
87
90
  }
88
91
  }
89
92
  else {
90
93
  fieldTypeDefinition = generateTypeDefinition(fieldType);
94
+ if (isListType(fieldType) && !isNonNullType(fieldType.ofType)) {
95
+ fieldTypeDefinition = `(${generateTypeDefinition(fieldType.ofType)} | null)[]`;
96
+ }
91
97
  }
92
- return `${selection.name.value}: ${fieldTypeDefinition}${isListType(fieldType) ? "[]" : ""}`;
98
+ return `${selection.name.value}: ${fieldTypeDefinition} ${isNonNullType(fieldType) ? "" : " | null"}`;
93
99
  }
94
100
  return "";
95
101
  })
@@ -1,3 +1,4 @@
1
+ import readline from "readline";
1
2
  import { assign, setup, fromCallback } from "xstate";
2
3
  import { addConnectionDefinition } from "../api/add-connection-definition.js";
3
4
  import { fetchConnections } from "../api/fetch-connections.js";
@@ -16,7 +17,7 @@ export const addConnectionMachine = setup({
16
17
  input: {},
17
18
  },
18
19
  actors: {
19
- createConnectionDefinition: fromCallback(({ sendBack, input: { developer: { token, slug: devSlug }, config, connectionType: connection_type, slug, 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, allowMultiple, global, authorizeUrl, accessTokenUrl, clientId, clientSecret, scopes, }, }) => {
20
21
  const add = async () => {
21
22
  try {
22
23
  await addConnectionDefinition({
@@ -25,7 +26,7 @@ export const addConnectionMachine = setup({
25
26
  appId: config.id,
26
27
  major: config.major,
27
28
  connectionType: connection_type,
28
- slug,
29
+ key,
29
30
  label,
30
31
  description,
31
32
  allowMultiple,
@@ -50,18 +51,45 @@ export const addConnectionMachine = setup({
50
51
  }),
51
52
  loadDeveloperConfig,
52
53
  loadAppConfig,
53
- validateSlug: fromCallback(({ sendBack, input: { slug, developer, config } }) => {
54
+ "keyboard-navigation": fromCallback(({ sendBack }) => {
55
+ readline.emitKeypressEvents(process.stdin);
56
+ if (process.stdin.isTTY) {
57
+ process.stdin.setRawMode(true);
58
+ }
59
+ const handleKeyPress = (_, key) => {
60
+ if (key.meta) {
61
+ switch (key.name) {
62
+ case "down":
63
+ sendBack({ type: "Next" });
64
+ break;
65
+ case "up":
66
+ sendBack({ type: "Previous" });
67
+ break;
68
+ default:
69
+ break;
70
+ }
71
+ }
72
+ };
73
+ process.stdin.on("keypress", handleKeyPress);
74
+ return () => {
75
+ process.stdin.removeListener("keypress", handleKeyPress);
76
+ if (process.stdin.isTTY) {
77
+ process.stdin.setRawMode(false);
78
+ }
79
+ };
80
+ }),
81
+ "validateKey": fromCallback(({ sendBack, input: { key, developer, config } }) => {
54
82
  fetchConnections({
55
83
  token: developer.token,
56
84
  devSlug: developer.slug,
57
85
  appId: config.id,
58
86
  major: config.major,
59
87
  }).then((connections) => {
60
- if (connections[slug]) {
61
- sendBack({ type: "Slug Taken" });
88
+ if (connections[key]) {
89
+ sendBack({ type: "Key Taken" });
62
90
  }
63
91
  else {
64
- sendBack({ type: "Valid Slug" });
92
+ sendBack({ type: "Valid Key" });
65
93
  }
66
94
  });
67
95
  }),
@@ -70,6 +98,21 @@ export const addConnectionMachine = setup({
70
98
  clearError: assign({
71
99
  error: () => undefined,
72
100
  }),
101
+ initializeLabel: assign({
102
+ label: (_, params) => {
103
+ if (params.label?.trim())
104
+ return params.label;
105
+ switch (params.connectionType) {
106
+ case "oauth2-code":
107
+ return "Connection";
108
+ case "secret":
109
+ case undefined:
110
+ return "Access Token";
111
+ default:
112
+ return params.connectionType;
113
+ }
114
+ },
115
+ }),
73
116
  setError: assign({
74
117
  error: (_, params) => params.error,
75
118
  }),
@@ -94,8 +137,8 @@ export const addConnectionMachine = setup({
94
137
  setDeveloperConfig: assign({
95
138
  developer: (_, params) => params,
96
139
  }),
97
- setSlug: assign({
98
- slug: (_, params) => slugifyExtension(params.slug, false),
140
+ setKey: assign({
141
+ key: (_, params) => slugifyExtension(params.key, false),
99
142
  }),
100
143
  setInvalidAuthorizeUrl: assign({
101
144
  error: (_, params) => {
@@ -151,13 +194,16 @@ export const addConnectionMachine = setup({
151
194
  setAppConfig: assign({
152
195
  config: (_, params) => params.config,
153
196
  }),
154
- setSlugTaken: assign({
155
- error: () => "Slug taken",
197
+ setKeyTaken: assign({
198
+ error: () => "Key taken",
199
+ }),
200
+ slugifyLabel: assign({
201
+ key: (_, params) => slugifyExtension(params.label, true),
156
202
  }),
157
203
  },
158
204
  guards: {
159
205
  "have connection type": (_, params) => Boolean(params.connectionType),
160
- "have slug": (_, params) => Boolean(params.slug),
206
+ "have key": (_, params) => Boolean(params.key),
161
207
  "have label": (_, params) => Boolean(params.label),
162
208
  "have description": (_, params) => Boolean(params.description),
163
209
  "have authorize url": (_, params) => params.connectionType === "secret" ||
@@ -208,12 +254,11 @@ export const addConnectionMachine = setup({
208
254
  target: "Error",
209
255
  actions: { type: "setError", params: ({ event }) => event },
210
256
  },
211
- "App Config Loaded": [
212
- {
213
- target: "Do we have a slug?",
214
- actions: { type: "setAppConfig", params: ({ event }) => event },
215
- },
216
- ],
257
+ "App Config Loaded": {
258
+ target: "Do we have a connection type?",
259
+ actions: { type: "setAppConfig", params: ({ event }) => event },
260
+ reenter: true,
261
+ },
217
262
  },
218
263
  },
219
264
  "Error": {
@@ -235,7 +280,7 @@ export const addConnectionMachine = setup({
235
280
  allowMultiple: context.allowMultiple,
236
281
  label: context.label,
237
282
  description: context.description,
238
- slug: context.slug,
283
+ key: context.key,
239
284
  }),
240
285
  },
241
286
  on: {
@@ -258,12 +303,18 @@ export const addConnectionMachine = setup({
258
303
  {
259
304
  target: "Do we have an access token url?",
260
305
  guard: { type: "have authorize url", params: ({ context }) => context },
306
+ actions: "clearError",
261
307
  },
262
308
  {
263
309
  target: "Ask for Authorize URL",
264
310
  actions: { type: "setInvalidAuthorizeUrl", params: ({ context }) => context },
265
311
  },
266
312
  ],
313
+ "Previous": "Ask for allow multiple",
314
+ "Next": {
315
+ target: "Ask for Access Token URL",
316
+ guard: { type: "have authorize url", params: ({ context }) => context },
317
+ },
267
318
  },
268
319
  },
269
320
  "Ask for Access Token URL": {
@@ -272,6 +323,7 @@ export const addConnectionMachine = setup({
272
323
  {
273
324
  target: "Do we have scopes?",
274
325
  guard: { type: "have access token url", params: ({ context }) => context },
326
+ actions: "clearError",
275
327
  },
276
328
  {
277
329
  target: "Ask for Access Token URL",
@@ -281,6 +333,11 @@ export const addConnectionMachine = setup({
281
333
  "Update Access Token URL": {
282
334
  actions: { type: "setAccessTokenUrl", params: ({ event }) => event },
283
335
  },
336
+ "Previous": "Ask for Authorize URL",
337
+ "Next": {
338
+ target: "Ask for Scopes",
339
+ guard: { type: "have access token url", params: ({ context }) => context },
340
+ },
284
341
  },
285
342
  },
286
343
  "Ask for Scopes": {
@@ -292,12 +349,18 @@ export const addConnectionMachine = setup({
292
349
  {
293
350
  target: "Do we have a client id?",
294
351
  guard: { type: "have scopes", params: ({ context }) => context },
352
+ actions: "clearError",
295
353
  },
296
354
  {
297
355
  target: "Ask for Scopes",
298
356
  actions: { type: "setInvalidScopes", params: ({ context }) => context },
299
357
  },
300
358
  ],
359
+ "Previous": "Ask for Access Token URL",
360
+ "Next": {
361
+ target: "Ask for Client ID",
362
+ guard: { type: "have scopes", params: ({ context }) => context },
363
+ },
301
364
  },
302
365
  },
303
366
  "Ask for Client ID": {
@@ -309,12 +372,18 @@ export const addConnectionMachine = setup({
309
372
  {
310
373
  target: "Do we have a client secret?",
311
374
  guard: { type: "have client id", params: ({ context }) => context },
375
+ actions: "clearError",
312
376
  },
313
377
  {
314
378
  target: "Ask for Client ID",
315
379
  actions: { type: "setInvalidClientId", params: ({ context }) => context },
316
380
  },
317
381
  ],
382
+ "Previous": "Ask for Scopes",
383
+ "Next": {
384
+ target: "Ask for Client Secret",
385
+ guard: { type: "have client id", params: ({ context }) => context },
386
+ },
318
387
  },
319
388
  },
320
389
  "Ask for Client Secret": {
@@ -326,8 +395,14 @@ export const addConnectionMachine = setup({
326
395
  {
327
396
  target: "Creating connection definition",
328
397
  guard: { type: "have client secret", params: ({ context }) => context },
398
+ actions: "clearError",
399
+ },
400
+ {
401
+ target: "Ask for Client Secret",
402
+ actions: { type: "setInvalidClientSecret", params: ({ context }) => context },
329
403
  },
330
404
  ],
405
+ "Previous": "Ask for Client ID",
331
406
  },
332
407
  },
333
408
  "Do we have an authorize url?": {
@@ -385,49 +460,58 @@ export const addConnectionMachine = setup({
385
460
  },
386
461
  ],
387
462
  },
388
- "Do we have a slug?": {
463
+ "Do we have a key?": {
389
464
  always: [
390
465
  {
391
- target: "Validating Slug",
392
- guard: { type: "have slug", params: ({ context }) => context },
466
+ target: "Validating Key",
467
+ guard: { type: "have key", params: ({ context }) => context },
393
468
  },
394
469
  {
395
- target: "Ask for slug",
470
+ target: "Ask for key",
471
+ actions: {
472
+ type: "slugifyLabel",
473
+ params: ({ context }) => ({ label: context.label }),
474
+ },
396
475
  },
397
476
  ],
398
477
  },
399
- "Validating Slug": {
478
+ "Validating Key": {
400
479
  on: {
401
- "Slug Taken": {
402
- target: "Ask for slug",
403
- actions: "setSlugTaken",
480
+ "Key Taken": {
481
+ target: "Ask for key",
482
+ actions: "setKeyTaken",
404
483
  },
405
- "Valid Slug": {
406
- target: "Do we have a label?",
484
+ "Valid Key": {
485
+ target: "Do we have a description?",
486
+ reenter: true,
487
+ actions: "clearError",
407
488
  },
408
489
  },
409
490
  invoke: {
410
- src: "validateSlug",
491
+ src: "validateKey",
411
492
  input: ({ context }) => ({
412
- slug: context.slug,
493
+ key: context.key,
413
494
  developer: context.developer,
414
495
  config: context.config,
415
496
  }),
416
497
  },
417
498
  },
418
- "Ask for slug": {
499
+ "Ask for key": {
419
500
  on: {
420
- "Update Slug": {
421
- actions: { type: "setSlug", params: ({ event }) => event },
501
+ "Update Key": {
502
+ actions: { type: "setKey", params: ({ event }) => event },
422
503
  },
423
- "Submit": "Validating Slug",
504
+ "Submit": "Validating Key",
505
+ "Previous": "Ask for label",
506
+ "Next": "Validating Key",
424
507
  },
425
508
  },
426
509
  "Do we have a connection type?": {
427
510
  always: [
428
511
  {
429
- target: "Split on connection type",
512
+ target: "Do we have a label?",
430
513
  guard: { type: "have connection type", params: ({ context }) => context },
514
+ reenter: true,
431
515
  },
432
516
  "Ask for connection type",
433
517
  ],
@@ -435,8 +519,13 @@ export const addConnectionMachine = setup({
435
519
  "Ask for connection type": {
436
520
  on: {
437
521
  "Choose Connection Type": {
438
- target: "Split on connection type",
439
- actions: { type: "setConnectionType", params: ({ event }) => event },
522
+ target: "Do we have a label?",
523
+ actions: [{ type: "setConnectionType", params: ({ event }) => event }],
524
+ reenter: true,
525
+ },
526
+ "Next": {
527
+ target: "Ask for label",
528
+ guard: { type: "have connection type", params: ({ context }) => context },
440
529
  },
441
530
  },
442
531
  },
@@ -454,8 +543,9 @@ export const addConnectionMachine = setup({
454
543
  "Do we have a label?": {
455
544
  always: [
456
545
  {
457
- target: "Do we have a description?",
546
+ target: "Do we have a key?",
458
547
  guard: { type: "have label", params: ({ context }) => context },
548
+ reenter: true,
459
549
  },
460
550
  {
461
551
  target: "Ask for label",
@@ -487,8 +577,9 @@ export const addConnectionMachine = setup({
487
577
  "Do we have allow multiple?": {
488
578
  always: [
489
579
  {
490
- target: "Do we have a connection type?",
580
+ target: "Split on connection type",
491
581
  guard: { type: "have allow multiple", params: ({ context }) => context },
582
+ reenter: true,
492
583
  },
493
584
  {
494
585
  target: "Ask for allow multiple",
@@ -498,13 +589,25 @@ export const addConnectionMachine = setup({
498
589
  "Ask for label": {
499
590
  on: {
500
591
  "Submit": {
501
- target: "Do we have a description?",
592
+ target: "Do we have a key?",
502
593
  guard: { type: "have label", params: ({ context }) => context },
594
+ reenter: true,
503
595
  },
504
596
  "Update Label": {
505
597
  actions: { type: "setLabel", params: ({ event }) => event },
506
598
  },
599
+ "Previous": "Ask for connection type",
600
+ "Next": {
601
+ target: "Ask for key",
602
+ guard: { type: "have label", params: ({ context }) => context },
603
+ },
507
604
  },
605
+ entry: [
606
+ {
607
+ type: "initializeLabel",
608
+ params: ({ context }) => context,
609
+ },
610
+ ],
508
611
  },
509
612
  "Ask for description": {
510
613
  on: {
@@ -514,6 +617,12 @@ export const addConnectionMachine = setup({
514
617
  "Submit": {
515
618
  target: "Do we have global?",
516
619
  guard: { type: "have description", params: ({ context }) => context },
620
+ actions: "clearError",
621
+ },
622
+ "Previous": "Ask for key",
623
+ "Next": {
624
+ target: "Ask for global",
625
+ guard: { type: "have description", params: ({ context }) => context },
517
626
  },
518
627
  },
519
628
  },
@@ -523,16 +632,30 @@ export const addConnectionMachine = setup({
523
632
  target: "Do we have allow multiple?",
524
633
  actions: { type: "setGlobal", params: ({ event }) => event },
525
634
  },
635
+ "Previous": "Ask for description",
636
+ "Next": {
637
+ target: "Ask for allow multiple",
638
+ guard: { type: "have global", params: ({ context }) => context },
639
+ },
526
640
  },
527
641
  },
528
642
  "Ask for allow multiple": {
529
643
  on: {
530
644
  "Choose Allow Multiple": {
531
- target: "Do we have a connection type?",
645
+ target: "Split on connection type",
532
646
  actions: { type: "setAllowMultiple", params: ({ event }) => event },
647
+ reenter: true,
648
+ },
649
+ "Previous": "Ask for global",
650
+ "Next": {
651
+ target: "Split on connection type",
652
+ guard: { type: "have allow multiple", params: ({ context }) => context },
533
653
  },
534
654
  },
535
655
  },
536
656
  },
537
657
  initial: "Loading Developer Config",
658
+ invoke: {
659
+ src: "keyboard-navigation",
660
+ },
538
661
  });
@@ -1,21 +1,26 @@
1
1
  import net from "net";
2
- export async function findAvailablePort(startPort) {
3
- return new Promise((resolve, reject) => {
4
- const server = net.createServer();
5
- const tryPort = (port) => {
6
- server.once("error", (err) => {
7
- if (err.code === "EADDRINUSE") {
8
- tryPort(port + 1);
9
- }
10
- else {
11
- reject(err);
12
- }
13
- });
14
- server.once("listening", () => {
15
- server.close(() => resolve(port));
16
- });
17
- server.listen(port);
18
- };
19
- tryPort(startPort);
2
+ async function isPortAvailable(port) {
3
+ return new Promise((resolve) => {
4
+ const tester = net.createConnection(port, "127.0.0.1");
5
+ tester.once("error", (err) => {
6
+ if (err.code === "ECONNREFUSED") {
7
+ resolve(true);
8
+ }
9
+ else {
10
+ resolve(false);
11
+ }
12
+ });
13
+ tester.once("connect", () => {
14
+ tester.end();
15
+ resolve(false);
16
+ });
20
17
  });
21
18
  }
19
+ export async function findAvailablePort(startPort, maxAttempts = 100) {
20
+ for (let port = startPort; port < startPort + maxAttempts; port++) {
21
+ if (await isPortAvailable(port)) {
22
+ return port;
23
+ }
24
+ }
25
+ throw new Error(`Could not find an available port after ${maxAttempts} attempts`);
26
+ }
@@ -1,4 +1,4 @@
1
- export const slugifyExtension = (string, trim = true) => {
1
+ export function slugifyExtension(string, trim = true) {
2
2
  const slug = (trim ? string.trim() : string)
3
3
  .toLowerCase()
4
4
  .replace(/[\s._@]/g, "-")
@@ -11,4 +11,4 @@ export const slugifyExtension = (string, trim = true) => {
11
11
  return slug
12
12
  .replace(/^-+/g, "")
13
13
  .replace(/(?!-+$)--+/g, "-");
14
- };
14
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "attio",
3
- "version": "0.0.1-experimental.20241003.1",
3
+ "version": "0.0.1-experimental.20241007.1",
4
4
  "bin": "lib/attio.js",
5
5
  "type": "module",
6
6
  "files": [
package/schema.graphql CHANGED
@@ -88,6 +88,7 @@ type PeopleRecord implements Record {
88
88
  last_email_interaction: Interaction
89
89
  first_interaction: Interaction
90
90
  last_interaction: Interaction
91
+ next_interaction: Interaction
91
92
  associated_deals: [DealsRecord!]
92
93
  associated_users: [UsersRecord!]
93
94
  list_entries: [Record!]
@@ -106,7 +107,7 @@ interface Record {
106
107
  url: String!
107
108
  }
108
109
 
109
- union AttributeValue = RecordReferenceValue | MultiRecordReferenceValue | PersonalNameValue | MultiPersonalNameValue | TextValue | MultiTextValue | DateValue | MultiDateValue | TimestampValue | MultiTimestampValue | NumberValue | MultiNumberValue | EmailAddressValue | MultiEmailAddressValue | DomainValue | MultiDomainValue | LocationValue | MultiLocationValue | InteractionValue | MultiInteractionValue | SelectValue | MultiSelectValue | PipelineValue | MultiPipelineValue | CheckboxValue | MultiCheckboxValue | RatingValue | MultiRatingValue | PhoneNumberValue | MultiPhoneNumberValue | CurrencyValue | MultiCurrencyValue | ActorReferenceValue | MultiActorReferenceValue
110
+ union AttributeValue = RecordReferenceValue | MultiRecordReferenceValue | PersonalNameValue | TextValue | DateValue | TimestampValue | NumberValue | MultiEmailAddressValue | DomainValue | MultiDomainValue | LocationValue | InteractionValue | SelectValue | MultiSelectValue | PipelineValue | CheckboxValue | RatingValue | PhoneNumberValue | MultiPhoneNumberValue | CurrencyValue | ActorReferenceValue | MultiActorReferenceValue
110
111
 
111
112
  type RecordReferenceValue {
112
113
  value: Record
@@ -126,46 +127,22 @@ type PersonalName {
126
127
  full: String
127
128
  }
128
129
 
129
- type MultiPersonalNameValue {
130
- values: [PersonalName]
131
- }
132
-
133
130
  type TextValue {
134
131
  value: String
135
132
  }
136
133
 
137
- type MultiTextValue {
138
- values: [String]
139
- }
140
-
141
134
  type DateValue {
142
135
  value: String
143
136
  }
144
137
 
145
- type MultiDateValue {
146
- values: [String]
147
- }
148
-
149
138
  type TimestampValue {
150
139
  value: String
151
140
  }
152
141
 
153
- type MultiTimestampValue {
154
- values: [String]
155
- }
156
-
157
142
  type NumberValue {
158
143
  value: Float
159
144
  }
160
145
 
161
- type MultiNumberValue {
162
- values: [Float]
163
- }
164
-
165
- type EmailAddressValue {
166
- value: String
167
- }
168
-
169
146
  type MultiEmailAddressValue {
170
147
  values: [String]
171
148
  }
@@ -195,10 +172,6 @@ type Location {
195
172
  longitude: String
196
173
  }
197
174
 
198
- type MultiLocationValue {
199
- values: [Location]
200
- }
201
-
202
175
  type InteractionValue {
203
176
  value: Interaction
204
177
  }
@@ -207,10 +180,6 @@ type Interaction {
207
180
  timestamp: String!
208
181
  }
209
182
 
210
- type MultiInteractionValue {
211
- values: [Interaction]
212
- }
213
-
214
183
  type SelectValue {
215
184
  value: SelectOption
216
185
  }
@@ -233,26 +202,14 @@ type PipelineStage {
233
202
  id: String!
234
203
  }
235
204
 
236
- type MultiPipelineValue {
237
- values: [PipelineStage]
238
- }
239
-
240
205
  type CheckboxValue {
241
206
  value: Boolean!
242
207
  }
243
208
 
244
- type MultiCheckboxValue {
245
- values: [Boolean!]
246
- }
247
-
248
209
  type RatingValue {
249
210
  value: Int!
250
211
  }
251
212
 
252
- type MultiRatingValue {
253
- values: [Int!]
254
- }
255
-
256
213
  type PhoneNumberValue {
257
214
  value: String
258
215
  }
@@ -270,10 +227,6 @@ type Money {
270
227
  currency: String!
271
228
  }
272
229
 
273
- type MultiCurrencyValue {
274
- values: [Money]
275
- }
276
-
277
230
  type ActorReferenceValue {
278
231
  value: Actor
279
232
  }
@@ -328,6 +281,7 @@ type CompaniesRecord implements Record {
328
281
  last_email_interaction: Interaction
329
282
  first_interaction: Interaction
330
283
  last_interaction: Interaction
284
+ next_interaction: Interaction
331
285
  associated_deals: [DealsRecord!]
332
286
  associated_workspaces: [WorkspacesRecord!]
333
287
  list_entries: [Record!]