attio 0.0.1-experimental.20241002.1 → 0.0.1-experimental.20241003

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/lib/build.js CHANGED
@@ -13,6 +13,6 @@ const buildErrorSchema = z.object({
13
13
  }),
14
14
  });
15
15
  export const errorSchema = z.object({
16
- errors: z.array(buildErrorSchema),
17
- warnings: z.array(buildErrorSchema),
16
+ errors: z.array(buildErrorSchema).optional(),
17
+ warnings: z.array(buildErrorSchema).optional(),
18
18
  });
@@ -5,6 +5,7 @@ import { option } from "pastel";
5
5
  import React from "react";
6
6
  import { z } from "zod";
7
7
  import { renderBuildErrors } from "../components/BuildError.js";
8
+ import { CodeGenError } from "../components/CodeGenErrors.js";
8
9
  import { InitialInstructions } from "../components/InitialInstructions.js";
9
10
  import { ScrollBox } from "../components/ScrollBox.js";
10
11
  import { renderTypeScriptErrors } from "../components/TypeScriptErrors.js";
@@ -28,7 +29,8 @@ export default function Dev({ options: { debug } }) {
28
29
  const jsTime = snapshot.children.javascript?.getSnapshot().context.time;
29
30
  const tsErrors = snapshot.children.typescript?.getSnapshot().context.errors;
30
31
  const tsTime = snapshot.children.typescript?.getSnapshot().context.time;
31
- const hasErrors = Boolean(jsError || tsErrors);
32
+ const codeGenError = snapshot.children["code-gen"]?.getSnapshot().context.error;
33
+ const hasErrors = Boolean(jsError || tsErrors || codeGenError);
32
34
  const isNoConfig = snapshot.matches("No Config");
33
35
  React.useEffect(() => {
34
36
  if (isNoConfig) {
@@ -57,10 +59,7 @@ export default function Dev({ options: { debug } }) {
57
59
  React.createElement(Text, null,
58
60
  "Code Gen:",
59
61
  " ",
60
- JSON.stringify(snapshot.children["code-gen"]?.getSnapshot()?.value),
61
- " ",
62
- snapshot.children["code-gen"]?.getSnapshot().context.error
63
- ?.message)),
62
+ JSON.stringify(snapshot.children["code-gen"]?.getSnapshot()?.value))),
64
63
  snapshot.context.devVersion?.app_id && (React.createElement(Box, null,
65
64
  React.createElement(Text, null,
66
65
  "App ID: ",
@@ -101,5 +100,6 @@ export default function Dev({ options: { debug } }) {
101
100
  React.createElement(Text, null, "Press \"o\" to open GraphQL Explorer")))),
102
101
  hasErrors && (React.createElement(ScrollBox, { borderStyle: "round", padding: 1 },
103
102
  jsError && renderBuildErrors(jsError),
104
- tsErrors && renderTypeScriptErrors(tsErrors)))));
103
+ tsErrors && renderTypeScriptErrors(tsErrors),
104
+ codeGenError && React.createElement(CodeGenError, { error: codeGenError })))));
105
105
  }
@@ -34,7 +34,7 @@ const Error = React.forwardRef(({ message, level }, ref) => (React.createElement
34
34
  React.createElement(Text, { color: "greenBright" }, "^")))))))));
35
35
  export function renderBuildErrors({ errors, warnings }) {
36
36
  return [
37
- ...errors.map((error, index) => (React.createElement(Error, { key: `BuildError-${index}`, message: error, level: "error" }))),
38
- ...warnings.map((error, index) => (React.createElement(Error, { key: `BuildError-${index}`, message: error, level: "warning" }))),
37
+ ...(errors ?? []).map((error, index) => (React.createElement(Error, { key: `BuildError-${index}`, message: error, level: "error" }))),
38
+ ...(warnings ?? []).map((error, index) => (React.createElement(Error, { key: `BuildError-${index}`, message: error, level: "warning" }))),
39
39
  ];
40
40
  }
@@ -0,0 +1,22 @@
1
+ import { codeFrameColumns } from "@babel/code-frame";
2
+ import { Box, Text } from "ink";
3
+ import React from "react";
4
+ export const CodeGenError = React.forwardRef(({ error }, ref) => {
5
+ return (React.createElement(Box, { flexDirection: "column", ref: ref },
6
+ React.createElement(Box, null,
7
+ React.createElement(Text, { color: "red" },
8
+ React.createElement(Text, { backgroundColor: "red", color: "white" },
9
+ " ",
10
+ "GRAPHQL ERROR",
11
+ " "),
12
+ " ",
13
+ React.createElement(Text, null, error.message))),
14
+ React.createElement(Box, { paddingTop: 1, flexDirection: "column" },
15
+ React.createElement(Box, null,
16
+ React.createElement(Text, null, codeFrameColumns(error.source, {
17
+ start: {
18
+ line: error.locations[0].line,
19
+ column: error.locations[0].column,
20
+ },
21
+ }))))));
22
+ });
@@ -1,7 +1,8 @@
1
1
  import fs from "fs";
2
- import { parse, visit, validate, print, OperationTypeNode, isObjectType, isListType, isNonNullType, isInputObjectType, isEnumType, getNamedType, validateSchema, } from "graphql";
2
+ import { parse, visit, validate, print, OperationTypeNode, GraphQLError as OfficialGraphQLError, isObjectType, isListType, isNonNullType, isInputObjectType, isEnumType, getNamedType, validateSchema, } from "graphql";
3
3
  import path from "path";
4
4
  import { format } from "prettier";
5
+ import { getLineAndColumn, GraphQLError } from "./parse-schema.js";
5
6
  function findGraphQLFiles(dir) {
6
7
  const files = [];
7
8
  const entries = fs.readdirSync(dir, { withFileTypes: true });
@@ -113,34 +114,68 @@ declare module "./${graphqlFileName}" {
113
114
  `;
114
115
  }
115
116
  export async function generateOperationFromQuery(graphqlFileName, query, schema) {
116
- const ast = parse(query);
117
- let operations = "";
117
+ let ast;
118
+ try {
119
+ ast = parse(query);
120
+ }
121
+ catch (e) {
122
+ if (e instanceof OfficialGraphQLError) {
123
+ throw new GraphQLError(e.message, e.locations ?? [{ line: 1, column: 1 }], query, graphqlFileName);
124
+ }
125
+ throw e;
126
+ }
127
+ let operation = "";
118
128
  const schemaErrors = validateSchema(schema);
119
129
  if (schemaErrors.length > 0) {
120
130
  throw new Error(`Schema validation errors: ${schemaErrors.map((e) => e.message).join("\n")}`);
121
131
  }
122
132
  const validationErrors = validate(schema, ast);
123
133
  const filteredErrors = validationErrors.filter((error) => !error.message.includes("is not executable"));
124
- if (filteredErrors.length > 0) {
125
- throw new Error(`Validation errors: ${filteredErrors.map((e) => e.message).join("\n")}`);
134
+ const firstError = filteredErrors[0];
135
+ if (firstError) {
136
+ throw new GraphQLError(firstError.message, firstError.locations ?? [{ line: 1, column: 1 }], query, graphqlFileName);
126
137
  }
127
138
  visit(ast, {
128
139
  OperationDefinition(node) {
129
- if (node.name && node.operation === OperationTypeNode.QUERY) {
130
- const operationName = node.name.value;
131
- const variableDefinitions = node.variableDefinitions || [];
132
- operations += generateOperationFunction(graphqlFileName, operationName, variableDefinitions, schema, node.selectionSet);
140
+ if (node.name) {
141
+ switch (node.operation) {
142
+ case OperationTypeNode.MUTATION:
143
+ case OperationTypeNode.SUBSCRIPTION:
144
+ throw new GraphQLError(`Only queries are supported, found ${node.operation}`, node.loc ? [getLineAndColumn(query, node.loc)] : [{ line: 1, column: 1 }], query, graphqlFileName);
145
+ case OperationTypeNode.QUERY:
146
+ if (operation) {
147
+ throw new GraphQLError(`Only one query is allowed per .graphql file`, node.loc
148
+ ? [getLineAndColumn(query, node.loc)]
149
+ : [{ line: 1, column: 1 }], query, graphqlFileName);
150
+ }
151
+ const operationName = node.name.value;
152
+ const variableDefinitions = node.variableDefinitions || [];
153
+ operation = generateOperationFunction(graphqlFileName, operationName, variableDefinitions, schema, node.selectionSet);
154
+ break;
155
+ default:
156
+ return node.operation;
157
+ }
133
158
  }
134
159
  },
135
160
  });
136
- return format(operations, { parser: "typescript" });
161
+ return format(operation, { parser: "typescript" });
137
162
  }
138
163
  export async function generateOperations(rootDir, schema) {
139
164
  const graphqlFiles = findGraphQLFiles(rootDir);
140
- let operations = "";
141
- for (const file of graphqlFiles) {
142
- const content = fs.readFileSync(file, "utf-8");
143
- operations += await generateOperationFromQuery(path.basename(file), content, schema);
144
- }
145
- return format(operations, { parser: "typescript" });
165
+ await Promise.all(graphqlFiles.map(async (file) => {
166
+ const content = await fs.promises.readFile(file, "utf-8");
167
+ const operation = await generateOperationFromQuery(path.basename(file), content, schema);
168
+ const formattedOperation = await format(`/**
169
+ * ****************************************************
170
+ * THIS FILE IS AUTO-GENERATED AT DEVELOPMENT TIME.
171
+ *
172
+ * DO NOT EDIT DIRECTLY OR COMMIT IT TO SOURCE CONTROL.
173
+ * ****************************************************
174
+ */
175
+ import {Query } from "attio/client"
176
+
177
+ ${operation}
178
+ `, { parser: "typescript" });
179
+ await fs.promises.writeFile(`${file}.d.ts`, formattedOperation);
180
+ }));
146
181
  }
@@ -2,27 +2,41 @@ import { codeFrameColumns } from "@babel/code-frame";
2
2
  import fs from "fs";
3
3
  import { parse, validateSchema, buildASTSchema } from "graphql";
4
4
  import { validateSDL } from "graphql/validation/validate.js";
5
- function formatGraphQlError(e, source, filename) {
6
- if (e.locations?.length) {
7
- return `${e.message}\n\n${filename}:${e.locations[0].line}\n\n${e.locations
8
- .map(({ line, column }) => codeFrameColumns(source, {
5
+ export class GraphQLError extends Error {
6
+ constructor(message, locations, source, filename) {
7
+ super(message);
8
+ this.locations = locations;
9
+ this.source = source;
10
+ this.filename = filename;
11
+ }
12
+ toString() {
13
+ const { line, column } = this.locations[0];
14
+ return `${this.message}\n\n${this.filename}:${this.locations[0].line}\n\n${codeFrameColumns(this.source, {
9
15
  start: { line, column },
10
- }))
11
- .join(`\n\n`)}\n`;
16
+ })}\n`;
12
17
  }
13
- else {
14
- throw e;
18
+ }
19
+ export function getLineAndColumn(source, location) {
20
+ let line = 1;
21
+ let column = 1;
22
+ for (let i = 0; i < location.start; i++) {
23
+ if (source[i] === "\n") {
24
+ line++;
25
+ column = 1;
26
+ }
27
+ else {
28
+ column++;
29
+ }
15
30
  }
31
+ return { line, column };
16
32
  }
17
33
  export function parseSchemaString(schemaString, schemaPath) {
18
34
  let parsedSchema;
19
35
  try {
20
36
  parsedSchema = parse(schemaString);
21
37
  }
22
- catch (ex) {
23
- throw Object.assign(new Error(formatGraphQlError(ex, schemaString, schemaPath)), {
24
- code: `GRAPHQL_SYNTAX_ERROR`,
25
- });
38
+ catch (e) {
39
+ throw new GraphQLError(e.message, e.locations, schemaString, schemaPath);
26
40
  }
27
41
  parsedSchema = {
28
42
  ...parsedSchema,
@@ -42,16 +56,12 @@ export function parseSchemaString(schemaString, schemaPath) {
42
56
  };
43
57
  const sdlValidationErrors = validateSDL(parsedSchema);
44
58
  if (sdlValidationErrors.length) {
45
- throw Object.assign(new Error(formatGraphQlError(sdlValidationErrors[0], schemaString, schemaPath)), {
46
- code: `GRAPHQL_SCHEMA_ERROR`,
47
- });
59
+ throw new GraphQLError(sdlValidationErrors[0].message, sdlValidationErrors[0].locations ?? [{ line: 1, column: 1 }], schemaString, schemaPath);
48
60
  }
49
61
  const schema = buildASTSchema(parsedSchema, { assumeValid: false, assumeValidSDL: false });
50
62
  const schemaValidationErrors = validateSchema(schema);
51
63
  if (schemaValidationErrors.length > 0) {
52
- throw Object.assign(new Error(formatGraphQlError(schemaValidationErrors[0], schemaString, schemaPath)), {
53
- code: `GRAPHQL_SCHEMA_ERROR`,
54
- });
64
+ throw new GraphQLError(schemaValidationErrors[0].message, schemaValidationErrors[0].locations ?? [{ line: 1, column: 1 }], schemaString, schemaPath);
55
65
  }
56
66
  return {
57
67
  source: schemaString,
@@ -1,7 +1,7 @@
1
1
  import { sendTo, assign, setup, fromCallback } from "xstate";
2
+ import { generateOperations } from "../graphql/generate-operations.js";
2
3
  import { parseSchema } from "../graphql/parse-schema.js";
3
4
  import { findNodeModulesPath } from "../util/find-node-modules-path.js";
4
- import { updateOperationTypes } from "../util/update-operation-types.js";
5
5
  export const codeGenMachine = setup({
6
6
  types: {
7
7
  context: {},
@@ -27,12 +27,15 @@ export const codeGenMachine = setup({
27
27
  loadSchema();
28
28
  }),
29
29
  generateOperations: fromCallback(({ sendBack, input }) => {
30
- updateOperationTypes(input.schema)
30
+ generateOperations(".", input.schema)
31
31
  .then(() => sendBack({ type: "Done" }))
32
32
  .catch((error) => sendBack({ type: "Error", error }));
33
33
  }),
34
34
  },
35
35
  actions: {
36
+ clearError: assign({
37
+ error: () => undefined,
38
+ }),
36
39
  setError: assign({
37
40
  error: (_, params) => params.error,
38
41
  }),
@@ -72,6 +75,7 @@ export const codeGenMachine = setup({
72
75
  on: {
73
76
  Done: {
74
77
  target: "Done",
78
+ actions: "clearError",
75
79
  },
76
80
  Error: {
77
81
  target: "Errored",
@@ -117,13 +117,9 @@ 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",
122
- "src/assets",
123
- "src/webhooks",
124
- "src/events",
125
- ".env",
126
- ]);
120
+ const watcher = chokidar.watch(["src/app", "src/assets", "src/webhooks", "src/events", ".env"], {
121
+ ignored: "**/*.graphql.d.ts",
122
+ });
127
123
  watcher.on("ready", () => watcher.on("all", () => {
128
124
  sendBack({ type: "Change" });
129
125
  }));
@@ -35,12 +35,12 @@
35
35
  "strictNullChecks": true,
36
36
  "strictPropertyInitialization": true,
37
37
  "target": "ES2019",
38
- "types": ["attio/client", "attio/global", "attio/graphql"],
38
+ "types": ["attio/client", "attio/global"],
39
39
  },
40
40
  "exclude": [
41
41
  "node_modules"
42
42
  ],
43
43
  "include": [
44
- "src", "graphql.d.ts"
44
+ "src"
45
45
  ]
46
46
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "attio",
3
- "version": "0.0.1-experimental.20241002.1",
3
+ "version": "0.0.1-experimental.20241003",
4
4
  "bin": "lib/attio.js",
5
5
  "type": "module",
6
6
  "files": [
@@ -19,9 +19,6 @@
19
19
  "./global": {
20
20
  "types": "./global.d.ts"
21
21
  },
22
- "./graphql": {
23
- "types": "./lib/graphql.d.ts"
24
- },
25
22
  "./server": {
26
23
  "types": "./server.d.ts"
27
24
  },
package/lib/graphql.d.ts DELETED
@@ -1,4 +0,0 @@
1
- declare module "*.graphql" {
2
- const value: string
3
- export default value
4
- }
@@ -1,6 +0,0 @@
1
- /// <reference types="attio/graphql" />
2
-
3
- declare module "*.graphql" {
4
- const content: string
5
- export default content;
6
- }
@@ -1,13 +0,0 @@
1
- import { promises as fs } from "fs";
2
- import { format } from "prettier";
3
- import { generateOperations } from "../graphql/generate-operations.js";
4
- import { findNodeModulesPath } from "./find-node-modules-path.js";
5
- export async function updateOperationTypes(schema) {
6
- const filePath = await findNodeModulesPath(["lib", "graphql.d.ts"]);
7
- if (!filePath) {
8
- return;
9
- }
10
- const unformattedTypescript = await generateOperations(".", schema);
11
- const typescript = await format(unformattedTypescript, { parser: "typescript" });
12
- await fs.writeFile(filePath, `import type { Query } from "./client/index.js";\n\n${typescript}`);
13
- }