attio 0.0.1-experimental.20241002.1 → 0.0.1-experimental.20241003.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/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 });
@@ -10,7 +11,22 @@ function findGraphQLFiles(dir) {
10
11
  if (entry.isDirectory()) {
11
12
  files.push(...findGraphQLFiles(fullPath));
12
13
  }
13
- else if (entry.isFile() && path.extname(entry.name) === ".graphql") {
14
+ else if (entry.isFile() &&
15
+ (path.extname(entry.name) === ".graphql" || path.extname(entry.name) === ".gql")) {
16
+ files.push(fullPath);
17
+ }
18
+ }
19
+ return files;
20
+ }
21
+ function findGeneratedFiles(dir) {
22
+ const files = [];
23
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
24
+ for (const entry of entries) {
25
+ const fullPath = path.join(dir, entry.name);
26
+ if (entry.isDirectory()) {
27
+ files.push(...findGraphQLFiles(fullPath));
28
+ }
29
+ else if (entry.isFile() && path.extname(entry.name) === ".graphql.d.ts") {
14
30
  files.push(fullPath);
15
31
  }
16
32
  }
@@ -113,34 +129,69 @@ declare module "./${graphqlFileName}" {
113
129
  `;
114
130
  }
115
131
  export async function generateOperationFromQuery(graphqlFileName, query, schema) {
116
- const ast = parse(query);
117
- let operations = "";
132
+ let ast;
133
+ try {
134
+ ast = parse(query);
135
+ }
136
+ catch (e) {
137
+ if (e instanceof OfficialGraphQLError) {
138
+ throw new GraphQLError(e.message, e.locations ?? [{ line: 1, column: 1 }], query, graphqlFileName);
139
+ }
140
+ throw e;
141
+ }
142
+ let operation = "";
118
143
  const schemaErrors = validateSchema(schema);
119
144
  if (schemaErrors.length > 0) {
120
145
  throw new Error(`Schema validation errors: ${schemaErrors.map((e) => e.message).join("\n")}`);
121
146
  }
122
147
  const validationErrors = validate(schema, ast);
123
148
  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")}`);
149
+ const firstError = filteredErrors[0];
150
+ if (firstError) {
151
+ throw new GraphQLError(firstError.message, firstError.locations ?? [{ line: 1, column: 1 }], query, graphqlFileName);
126
152
  }
127
153
  visit(ast, {
128
154
  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);
155
+ if (node.name) {
156
+ switch (node.operation) {
157
+ case OperationTypeNode.MUTATION:
158
+ case OperationTypeNode.SUBSCRIPTION:
159
+ throw new GraphQLError(`Only queries are supported, found ${node.operation}`, node.loc ? [getLineAndColumn(query, node.loc)] : [{ line: 1, column: 1 }], query, graphqlFileName);
160
+ case OperationTypeNode.QUERY:
161
+ if (operation) {
162
+ throw new GraphQLError(`Only one query is allowed per .graphql file`, node.loc
163
+ ? [getLineAndColumn(query, node.loc)]
164
+ : [{ line: 1, column: 1 }], query, graphqlFileName);
165
+ }
166
+ const operationName = node.name.value;
167
+ const variableDefinitions = node.variableDefinitions || [];
168
+ operation = generateOperationFunction(graphqlFileName, operationName, variableDefinitions, schema, node.selectionSet);
169
+ break;
170
+ default:
171
+ return node.operation;
172
+ }
133
173
  }
134
174
  },
135
175
  });
136
- return format(operations, { parser: "typescript" });
176
+ return format(operation, { parser: "typescript" });
137
177
  }
138
178
  export async function generateOperations(rootDir, schema) {
179
+ await Promise.all(findGeneratedFiles(rootDir).map(async (file) => fs.promises.unlink(file)));
139
180
  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" });
181
+ await Promise.all(graphqlFiles.map(async (file) => {
182
+ const content = await fs.promises.readFile(file, "utf-8");
183
+ const operation = await generateOperationFromQuery(path.basename(file), content, schema);
184
+ const formattedOperation = await format(`/**
185
+ * ****************************************************
186
+ * THIS FILE IS AUTO-GENERATED AT DEVELOPMENT TIME.
187
+ *
188
+ * DO NOT EDIT DIRECTLY OR COMMIT IT TO SOURCE CONTROL.
189
+ * ****************************************************
190
+ */
191
+ import {Query } from "attio/client"
192
+
193
+ ${operation}
194
+ `, { parser: "typescript" });
195
+ await fs.promises.writeFile(`${file}.d.ts`, formattedOperation);
196
+ }));
146
197
  }
@@ -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.1",
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
- }