attio 0.0.1-experimental.20240927 → 0.0.1-experimental.20241002

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.
@@ -11,7 +11,7 @@ export function startGraphqlServer(sendBack) {
11
11
  const startServer = async () => {
12
12
  const currentFilePath = fileURLToPath(import.meta.url);
13
13
  const currentDirPath = dirname(currentFilePath);
14
- const schemaPath = path.resolve(currentDirPath, "..", "schema.graphql");
14
+ const schemaPath = path.resolve(currentDirPath, "..", "..", "schema.graphql");
15
15
  const schemaString = fs.readFileSync(schemaPath, "utf8");
16
16
  const port = await findAvailablePort(8700);
17
17
  const schema = buildSchema(schemaString);
@@ -1,10 +1,11 @@
1
1
  import React from "react";
2
+ import { Icon } from "../icon";
2
3
  /**
3
4
  * Action Button
4
5
  */
5
6
  export declare function Action(props: {
6
7
  onTrigger: () => void;
7
- icon?: string;
8
+ icon?: Icon;
8
9
  disabled?: boolean;
9
10
  children: React.ReactNode;
10
11
  }): React.JSX.Element;
@@ -1,5 +1,4 @@
1
1
  export { useDialog } from "./use-dialog.js";
2
- export { useGraphQL } from "./use-graphql.js";
3
2
  export { useAsyncCache, AsyncCacheConfig, AsyncFunction } from "./use-async-cache.js";
4
3
  export { useQuery } from "./use-query.js";
5
4
  export { useRecord } from "./use-record.js";
@@ -1,5 +1,6 @@
1
- export declare function useQuery<T>(
1
+ import { Query } from "../run-query";
2
+ export declare function useQuery<Variables, Result>(
2
3
  /** GraphQL query */
3
- query: string,
4
+ query: Query<Variables, Result>,
4
5
  /** GraphQL query variables */
5
- variableValues?: Record<string, any>): T;
6
+ variableValues?: Variables): Result;
@@ -0,0 +1,2 @@
1
+ export type Icon = AttioIcon | `${string}.png`;
2
+ export type AttioIcon = "Activity" | "AlignArrowUp" | "AlignBlockCenter" | "AlignBlockLeft" | "AlignBlockRight" | "AlignCenter" | "AlignLeft" | "AlignRight" | "Apps" | "Archive" | "ArrowDown" | "ArrowDownLeft" | "ArrowDownRight" | "ArrowLeft" | "ArrowLeftRight" | "ArrowRight" | "ArrowsRefresh" | "ArrowUp" | "ArrowUpDown" | "ArrowUpLeft" | "ArrowUpRight" | "ArrowUpRightCircle" | "At" | "Attachment" | "Attributes" | "AttributesPlus" | "Automation" | "Bank" | "BarChart" | "Bold" | "BracketsCross" | "Breakpoint" | "BrowserExtension" | "BulletList" | "CalendarArrowLeft" | "CalendarArrowRight" | "CalendarClock" | "Calendar" | "CalendarArrowLeft" | "CalendarArrowRight" | "CalendarClock" | "CalendarDate" | "CalendarMinus" | "CalendarPlus" | "Caption" | "Change" | "Changelog" | "Check" | "CheckCircle" | "CheckDouble" | "CheckSquare" | "CheckSquarePlus" | "ChevronDown" | "ChevronDownChevronUp" | "ChevronLeft" | "ChevronRight" | "ChevronRightDouble" | "ChevronUp" | "ChevronUpChevronDown" | "CircleEmpty" | "CircleFilled" | "ClassifyRecord" | "ClassifyText" | "Clock" | "ClockDelay" | "Code" | "Collapse" | "Collapsed" | "CollapsedHover" | "CollapseHover" | "CollapseScreen" | "Collection" | "CollectionPlus" | "ColumnPlus" | "Columns" | "ColumnsPlus" | "Command" | "Comment" | "CommentPlus" | "Comments" | "Companies" | "Company" | "CompanyPlus" | "ConditionDown" | "Confetti" | "Copy" | "CreditCard" | "Cross" | "CrossCircle" | "Currency" | "CustomForms" | "CustomFormsCog" | "CustomFormsEdit" | "CustomFormsPlus" | "Dashboard" | "DashboardPlus" | "Data" | "DataText" | "Deal" | "Description" | "Desktop" | "Dollar" | "DollarArrowUp" | "DotsHorizontal" | "DotsVertical" | "Download" | "DragHandle" | "Duplicate" | "Edit" | "EditDisabled" | "Education" | "Email" | "EmailArrowLeft" | "EmailArrowRight" | "EmailDraft" | "EmailMass" | "EmailMassPlus" | "EmailMinus" | "EmailPlus" | "EmailTemplate" | "EmailTemplatePlus" | "EmojiAdd" | "EmptyCircle" | "Enter" | "Equals" | "EstimatedArr" | "Euro" | "EverythingBagel" | "ExpandScreen" | "Eye" | "EyeHide" | "Feedback" | "FileCsv" | "Filter" | "FilterAdd" | "FilterFunnel" | "Flash" | "Folder" | "FolderPlus" | "Forbid" | "FunnelChart" | "FunnelStepsChart" | "GalleryView" | "Gbp" | "GitBranch" | "Globe" | "GlobeHemisphereEast" | "Grid" | "Growth" | "Hand" | "Handshake" | "Hashtag" | "HelpCircle" | "Image" | "ImportExport" | "Inbox" | "InboxCross" | "InboxNotification" | "InfoCircle" | "Input" | "Italic" | "Key" | "KeyboardKey" | "LineChart" | "Link" | "List" | "ListArrowLeft" | "ListEdit" | "ListFlash" | "ListMinus" | "ListPlus" | "ListSearch" | "ListView" | "Location" | "Locked" | "LogOut" | "MapChart" | "Maximise" | "Merge" | "Message" | "Minimise" | "Minus" | "MinusSquare" | "MobilePhone" | "Moon" | "More" | "Move" | "MultiSelect" | "Name" | "None" | "Note" | "NotePlus" | "NoteTemplate" | "NoteTemplateArrowRight" | "NoteTemplatePlus" | "NumberList" | "ObjectIcon" | "ObjectPlaceholder" | "ObjectPlus" | "PanelLeft" | "PanelRight" | "Paste" | "Pause" | "Percentage" | "Phone" | "PhoneArrowLeft" | "PhoneArrowRight" | "PhoneBusy" | "PhoneCancel" | "PhoneCheck" | "PhoneCross" | "PhoneMissed" | "PhoneNoAnswer" | "PieChart" | "Pin" | "Pinned" | "Placeholder" | "Play" | "Plus" | "PlusSquare" | "Pointer" | "PromptCompletion" | "Record" | "RecordDetailEdit" | "RecordEdit" | "RecordFlash" | "RecordPlus" | "RecordSearch" | "Reference" | "RelationshipConnect" | "RemoveCircle" | "Rename" | "Repeat" | "Replace" | "Reply" | "Report" | "ReportPlus" | "Reset" | "RingBell" | "Robot" | "Rocket" | "Rows" | "RowsPlus" | "Sales" | "Search" | "SearchMinus" | "SearchPlus" | "SectionArrowDown" | "SectionArrowUp" | "Select" | "SelfServed" | "Send" | "SendAdd" | "SendEdit" | "ServiceBell" | "Sessions" | "Settings" | "SettingsArrowRight" | "SettingsCheck" | "SettingsMinus" | "Share" | "ShieldLocker" | "Shortcut" | "ShortcutMinus" | "ShortcutPlus" | "ShowRightSidebar" | "Signature" | "SingleMetricChart" | "SnapGrid" | "SortAscending" | "SortDescending" | "SortPlus" | "Spreadsheet" | "SquareEmpty" | "SquareFilled" | "SquareStrength" | "StageChanged" | "Star" | "StarCross" | "Status" | "Strength" | "Subscription" | "SummariseRecord" | "Sun" | "Switch" | "Table" | "Tag" | "Templates" | "TestTube" | "Text" | "TextIndentLeft" | "TextIndentRight" | "ThumbsDown" | "ThumbsUp" | "TidyUp" | "TimeChart" | "TimeInStatus" | "Timestamp" | "Trash" | "Trigger" | "TriggerPlus" | "Undo" | "Unlink" | "Upload" | "User" | "UserArrowLeft" | "UserArrowRight" | "UserFlash" | "UserHeart" | "UserKey" | "UserMinus" | "UserPhone" | "UserPlaceholder" | "UserPlus" | "Users" | "VideoCamera" | "View" | "ViewPlus" | "Voicemail" | "WarningCircle" | "WarningTriangle" | "Webhooks" | "WorkflowCredit" | "WorkflowRun" | "WorkflowsEdit" | "WorkflowsPlaceholder" | "WorkflowsPlus" | "Workspace";
@@ -1,5 +1,6 @@
1
1
  export * from "./hooks/index.js";
2
2
  export * from "./components/index.js";
3
+ export { runQuery, Query } from "./run-query.js";
3
4
  export * as Forms from "./forms/index.js";
4
5
  export { FormArray } from "./forms/array.js";
5
6
  export { FormNumber } from "./forms/number.js";
@@ -7,3 +8,4 @@ export { FormString } from "./forms/string.js";
7
8
  export { FormConnection, Connection } from "./forms/connection.js";
8
9
  export * from "./forms/path.js";
9
10
  export { Form } from "./forms/build.js";
11
+ export { Icon, AttioIcon } from "./icon.js";
@@ -0,0 +1,8 @@
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, Result> = {
6
+ __data: Result;
7
+ __variables: Variables;
8
+ } & string;
@@ -84,12 +84,12 @@ export default function AddConnection({ options: { slug, label, description, typ
84
84
  snapshot.matches("Ask for slug") && (React.createElement(React.Fragment, null,
85
85
  React.createElement(Box, null,
86
86
  React.createElement(Text, null, "Unique Slug: "),
87
- React.createElement(TextInput, { value: snapshot.context.slug ?? "", placeholder: "my-access-token", onChange: (slug) => send({ type: "Update Slug", slug }), onSubmit: () => send({ type: "Submit" }) })))),
87
+ React.createElement(TextInput, { value: snapshot.context.slug ?? "", onChange: (slug) => send({ type: "Update Slug", slug }), onSubmit: () => send({ type: "Submit" }) })))),
88
88
  snapshot.matches("Ask for label") && (React.createElement(React.Fragment, null,
89
89
  React.createElement(Box, null,
90
90
  React.createElement(Text, null, "Provide a label for your connection. This will be displayed to users. (You can edit this later)")),
91
91
  React.createElement(Box, null,
92
- React.createElement(TextInput, { value: snapshot.context.label ?? "", placeholder: "My Awesome Connection", onChange: (label) => send({ type: "Update Label", label }), onSubmit: () => send({ type: "Submit" }) })))),
92
+ React.createElement(TextInput, { value: snapshot.context.label ?? "", onChange: (label) => send({ type: "Update Label", label }), onSubmit: () => send({ type: "Submit" }) })))),
93
93
  snapshot.matches("Ask for description") && (React.createElement(React.Fragment, null,
94
94
  React.createElement(Box, null,
95
95
  React.createElement(Text, null, "Provide an optional detailed description for your connection. This will be displayed to users. (You can edit this later)")),
@@ -119,11 +119,11 @@ export default function AddConnection({ options: { slug, label, description, typ
119
119
  snapshot.matches("Ask for Authorize URL") && (React.createElement(React.Fragment, null,
120
120
  React.createElement(Box, null,
121
121
  React.createElement(Text, null, "OAuth Authorize URL: "),
122
- React.createElement(TextInput, { value: snapshot.context.authorizeUrl ?? "", placeholder: "https://authorization-server.com/authorize", onChange: (authorizeUrl) => send({ type: "Update Authorize URL", authorizeUrl }), onSubmit: () => send({ type: "Submit" }) })))),
122
+ React.createElement(TextInput, { value: snapshot.context.authorizeUrl ?? "", onChange: (authorizeUrl) => send({ type: "Update Authorize URL", authorizeUrl }), onSubmit: () => send({ type: "Submit" }) })))),
123
123
  snapshot.matches("Ask for Access Token URL") && (React.createElement(React.Fragment, null,
124
124
  React.createElement(Box, null,
125
125
  React.createElement(Text, null, "OAuth Access Token URL: "),
126
- React.createElement(TextInput, { value: snapshot.context.accessTokenUrl ?? "", placeholder: "https://authorization-server.com/token", onChange: (accessTokenUrl) => send({ type: "Update Access Token URL", accessTokenUrl }), onSubmit: () => send({ type: "Submit" }) })))),
126
+ React.createElement(TextInput, { value: snapshot.context.accessTokenUrl ?? "", onChange: (accessTokenUrl) => send({ type: "Update Access Token URL", accessTokenUrl }), onSubmit: () => send({ type: "Submit" }) })))),
127
127
  snapshot.matches("Ask for Scopes") && (React.createElement(React.Fragment, null,
128
128
  React.createElement(Box, null,
129
129
  React.createElement(Text, null, "OAuth Scopes: (separated by commas) "),
@@ -53,6 +53,14 @@ export default function Dev({ options: { debug } }) {
53
53
  React.createElement(Text, null,
54
54
  "Env: ",
55
55
  JSON.stringify(snapshot.children.env?.getSnapshot().value))),
56
+ React.createElement(Box, null,
57
+ React.createElement(Text, null,
58
+ "Code Gen:",
59
+ " ",
60
+ JSON.stringify(snapshot.children["code-gen"]?.getSnapshot()?.value),
61
+ " ",
62
+ snapshot.children["code-gen"]?.getSnapshot().context.error
63
+ ?.message)),
56
64
  snapshot.context.devVersion?.app_id && (React.createElement(Box, null,
57
65
  React.createElement(Text, null,
58
66
  "App ID: ",
@@ -0,0 +1,151 @@
1
+ import fs from "fs";
2
+ import { parse, visit, validate, print, OperationTypeNode, isObjectType, isListType, isNonNullType, isInputObjectType, isEnumType, getNamedType, } from "graphql";
3
+ import path from "path";
4
+ import { format } from "prettier";
5
+ function findGraphQLFiles(dir) {
6
+ const files = [];
7
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
8
+ for (const entry of entries) {
9
+ const fullPath = path.join(dir, entry.name);
10
+ if (entry.isDirectory()) {
11
+ files.push(...findGraphQLFiles(fullPath));
12
+ }
13
+ else if (entry.isFile() && path.extname(entry.name) === ".graphql") {
14
+ files.push(fullPath);
15
+ }
16
+ }
17
+ return files;
18
+ }
19
+ function generateEnumTypeDefinition(type) {
20
+ const values = type
21
+ .getValues()
22
+ .map((v) => `'${v.name}'`)
23
+ .join(" | ");
24
+ return values;
25
+ }
26
+ function generateTypeDefinition(type) {
27
+ if (isNonNullType(type)) {
28
+ return generateTypeDefinition(type.ofType);
29
+ }
30
+ if (isListType(type)) {
31
+ return `${generateTypeDefinition(type.ofType)}`;
32
+ }
33
+ if (isObjectType(type) || isInputObjectType(type)) {
34
+ return type.name;
35
+ }
36
+ if (isEnumType(type)) {
37
+ return generateEnumTypeDefinition(type);
38
+ }
39
+ switch (type.name) {
40
+ case "Int":
41
+ case "Float":
42
+ return "number";
43
+ case "String":
44
+ case "ID":
45
+ return "string";
46
+ case "Boolean":
47
+ return "boolean";
48
+ default:
49
+ return "any";
50
+ }
51
+ }
52
+ function generateInputObjectTypeDefinition(type) {
53
+ const fields = Object.values(type.getFields())
54
+ .map((field) => `${field.name}: ${generateTypeDefinition(field.type)}`)
55
+ .join(", ");
56
+ return `{ ${fields} }`;
57
+ }
58
+ function generateTypeDefinitionFromSelectionSet(selectionSet, parentType) {
59
+ const fieldDefinitions = selectionSet.selections
60
+ .map((selection) => {
61
+ if (selection.kind === "Field") {
62
+ const field = parentType.getFields()[selection.name.value];
63
+ if (!field) {
64
+ throw new Error(`Field ${selection.name.value} not found in type ${parentType.name}`);
65
+ }
66
+ let fieldType = field.type;
67
+ let fieldTypeDefinition = "";
68
+ if (selection.selectionSet) {
69
+ if (isObjectType(getNamedType(fieldType))) {
70
+ fieldTypeDefinition = generateTypeDefinitionFromSelectionSet(selection.selectionSet, getNamedType(fieldType));
71
+ }
72
+ }
73
+ else {
74
+ fieldTypeDefinition = generateTypeDefinition(fieldType);
75
+ }
76
+ return `${selection.name.value}: ${fieldTypeDefinition}${isListType(fieldType) ? "[]" : ""}`;
77
+ }
78
+ return "";
79
+ })
80
+ .filter(Boolean);
81
+ return `{${fieldDefinitions.join(",")}}`;
82
+ }
83
+ function generateReturnType(selectionSet, schema) {
84
+ const queryType = schema.getQueryType();
85
+ if (!queryType) {
86
+ throw new Error("Query type not found in schema");
87
+ }
88
+ return generateTypeDefinitionFromSelectionSet(selectionSet, queryType);
89
+ }
90
+ function generateOperationFunction(graphqlFileName, operationName, variableDefinitions, schema, selectionSet) {
91
+ const typeName = `${operationName.charAt(0).toUpperCase() + operationName.slice(1)}`;
92
+ const returnType = generateReturnType(selectionSet, schema);
93
+ let input;
94
+ if (variableDefinitions.length === 1) {
95
+ const varDef = variableDefinitions[0];
96
+ const varType = schema.getType(print(varDef.type).replace(/[!]/g, ""));
97
+ const varTypeString = isInputObjectType(varType)
98
+ ? generateInputObjectTypeDefinition(varType)
99
+ : generateTypeDefinition(varType);
100
+ input = `export type ${typeName}Variables = ${varTypeString}`;
101
+ }
102
+ else {
103
+ const variables = variableDefinitions
104
+ .map((def) => {
105
+ const varType = schema.getType(print(def.type).replace(/[!]/g, ""));
106
+ const varTypeString = isInputObjectType(varType)
107
+ ? generateInputObjectTypeDefinition(varType)
108
+ : generateTypeDefinition(varType);
109
+ return `${def.variable.name.value}: ${varTypeString}`;
110
+ })
111
+ .join(", ");
112
+ input = `export interface ${typeName}Variables { ${variables} }`;
113
+ }
114
+ return `
115
+ declare module "./${graphqlFileName}" {
116
+ ${input}
117
+
118
+ export interface ${typeName}Result ${returnType}
119
+
120
+ const value: Query<${typeName}Variables, ${typeName}Result>
121
+ export default value
122
+ }
123
+ `;
124
+ }
125
+ export async function generateOperationFromQuery(graphqlFileName, query, schema) {
126
+ const ast = parse(query);
127
+ let operations = "";
128
+ const validationErrors = validate(schema, ast);
129
+ if (validationErrors.length > 0) {
130
+ throw new Error(`Validation errors: ${validationErrors.map((e) => e.message).join("\n")}`);
131
+ }
132
+ visit(ast, {
133
+ OperationDefinition(node) {
134
+ if (node.name && node.operation === OperationTypeNode.QUERY) {
135
+ const operationName = node.name.value;
136
+ const variableDefinitions = node.variableDefinitions || [];
137
+ operations += generateOperationFunction(graphqlFileName, operationName, variableDefinitions, schema, node.selectionSet);
138
+ }
139
+ },
140
+ });
141
+ return format(operations, { parser: "typescript" });
142
+ }
143
+ export async function generateOperations(rootDir, schema) {
144
+ const graphqlFiles = findGraphQLFiles(rootDir);
145
+ let operations = "";
146
+ for (const file of graphqlFiles) {
147
+ const content = fs.readFileSync(file, "utf-8");
148
+ operations += await generateOperationFromQuery(path.basename(file), content, schema);
149
+ }
150
+ return format(operations, { parser: "typescript" });
151
+ }
@@ -0,0 +1,109 @@
1
+ import { isObjectType, isInputObjectType, isScalarType, isEnumType, isListType, isNonNullType, } from "graphql";
2
+ import { format } from "prettier";
3
+ function countTypeUsages(schema) {
4
+ const typeUsages = new Map();
5
+ Object.values(schema.getTypeMap()).forEach((type) => {
6
+ if ((isObjectType(type) || isInputObjectType(type)) && !type.name.startsWith("__")) {
7
+ Object.values(type.getFields()).forEach((field) => {
8
+ let fieldType = field.type;
9
+ while (isListType(fieldType) || isNonNullType(fieldType)) {
10
+ fieldType = fieldType.ofType;
11
+ }
12
+ if (isObjectType(fieldType) || isInputObjectType(fieldType)) {
13
+ const usage = typeUsages.get(fieldType.name) || { count: 0, type: fieldType };
14
+ usage.count++;
15
+ typeUsages.set(fieldType.name, usage);
16
+ }
17
+ });
18
+ }
19
+ });
20
+ return typeUsages;
21
+ }
22
+ function generateFieldType(type, schema, typeUsages, seenTypes = new Set()) {
23
+ if (isListType(type)) {
24
+ return `Array<${generateFieldType(type.ofType, schema, typeUsages, seenTypes)}>`;
25
+ }
26
+ if (isNonNullType(type)) {
27
+ return generateFieldType(type.ofType, schema, typeUsages, seenTypes);
28
+ }
29
+ if (isScalarType(type)) {
30
+ switch (type.name) {
31
+ case "ID":
32
+ case "String":
33
+ return "string";
34
+ case "Int":
35
+ case "Float":
36
+ return "number";
37
+ case "Boolean":
38
+ return "boolean";
39
+ default:
40
+ return "any";
41
+ }
42
+ }
43
+ if (isEnumType(type)) {
44
+ return type
45
+ .getValues()
46
+ .map((v) => `'${v.name}'`)
47
+ .join(" | ");
48
+ }
49
+ if (isObjectType(type) || isInputObjectType(type)) {
50
+ const usage = typeUsages.get(type.name);
51
+ if (usage && usage.count > 1) {
52
+ return type.name;
53
+ }
54
+ if (seenTypes.has(type.name)) {
55
+ return type.name;
56
+ }
57
+ seenTypes.add(type.name);
58
+ const fields = type.getFields();
59
+ let inlineType = "{";
60
+ Object.entries(fields).forEach(([fieldName, field]) => {
61
+ const fieldType = generateFieldType(field.type, schema, typeUsages, new Set(seenTypes));
62
+ inlineType += `${fieldName}: ${fieldType};`;
63
+ });
64
+ inlineType += "}";
65
+ return inlineType;
66
+ }
67
+ return "any";
68
+ }
69
+ function generateDocComment(description) {
70
+ if (!description)
71
+ return "";
72
+ const lines = description.split("\n");
73
+ if (lines.length === 1) {
74
+ return `/** ${description} */`;
75
+ }
76
+ return `/**\n * ${lines.join("\n * ")}\n */`;
77
+ }
78
+ function generateType(type, schema, typeUsages) {
79
+ let typeString = "";
80
+ const typeComment = generateDocComment(type.description ?? "");
81
+ if (typeComment) {
82
+ typeString += `${typeComment}\n`;
83
+ }
84
+ typeString += `export type ${type.name.charAt(0).toUpperCase() + type.name.slice(1)} = {`;
85
+ const fields = type.getFields();
86
+ Object.entries(fields).forEach(([fieldName, field]) => {
87
+ typeString += "\n";
88
+ const fieldComment = generateDocComment(field.description);
89
+ if (fieldComment) {
90
+ typeString += `${fieldComment}\n`;
91
+ }
92
+ const fieldType = generateFieldType(field.type, schema, typeUsages);
93
+ typeString += `${fieldName}: ${fieldType};`;
94
+ });
95
+ typeString += "\n};";
96
+ return typeString;
97
+ }
98
+ export async function generateTypesFromSchema(schema) {
99
+ const typeUsages = countTypeUsages(schema);
100
+ let types = "";
101
+ const typeMap = schema.getTypeMap();
102
+ Object.values(typeMap).forEach((type) => {
103
+ if ((isObjectType(type) || isInputObjectType(type)) && !type.name.startsWith("__")) {
104
+ types += generateType(type, schema, typeUsages);
105
+ types += "\n\n";
106
+ }
107
+ });
108
+ return await format(types, { parser: "typescript" });
109
+ }
@@ -0,0 +1,65 @@
1
+ import { codeFrameColumns } from "@babel/code-frame";
2
+ import fs from "fs";
3
+ import { parse, validateSchema, buildASTSchema } from "graphql";
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, {
9
+ start: { line, column },
10
+ }))
11
+ .join(`\n\n`)}\n`;
12
+ }
13
+ else {
14
+ throw e;
15
+ }
16
+ }
17
+ export function parseSchemaString(schemaString, schemaPath) {
18
+ let parsedSchema;
19
+ try {
20
+ parsedSchema = parse(schemaString);
21
+ }
22
+ catch (ex) {
23
+ throw Object.assign(new Error(formatGraphQlError(ex, schemaString, schemaPath)), {
24
+ code: `GRAPHQL_SYNTAX_ERROR`,
25
+ });
26
+ }
27
+ parsedSchema = {
28
+ ...parsedSchema,
29
+ definitions: parsedSchema.definitions
30
+ .slice()
31
+ .sort((a, b) => {
32
+ if (!a.name)
33
+ return -1;
34
+ if (!b.name)
35
+ return 1;
36
+ if (a.name.value.toLowerCase() > b.name.value.toLowerCase())
37
+ return 1;
38
+ if (a.name.value.toLowerCase() < b.name.value.toLowerCase())
39
+ return -1;
40
+ return 0;
41
+ }),
42
+ };
43
+ const sdlValidationErrors = validateSDL(parsedSchema);
44
+ if (sdlValidationErrors.length) {
45
+ throw Object.assign(new Error(formatGraphQlError(sdlValidationErrors[0], schemaString, schemaPath)), {
46
+ code: `GRAPHQL_SCHEMA_ERROR`,
47
+ });
48
+ }
49
+ const schema = buildASTSchema(parsedSchema, { assumeValid: false, assumeValidSDL: false });
50
+ const schemaValidationErrors = validateSchema(schema);
51
+ if (schemaValidationErrors.length > 0) {
52
+ throw Object.assign(new Error(formatGraphQlError(schemaValidationErrors[0], schemaString, schemaPath)), {
53
+ code: `GRAPHQL_SCHEMA_ERROR`,
54
+ });
55
+ }
56
+ return {
57
+ source: schemaString,
58
+ documentNode: parsedSchema,
59
+ schema,
60
+ };
61
+ }
62
+ export function parseSchema(schemaPath) {
63
+ const schemaString = fs.readFileSync(schemaPath, "utf-8");
64
+ return parseSchemaString(schemaString, schemaPath);
65
+ }
@@ -0,0 +1,4 @@
1
+ declare module "*.graphql" {
2
+ const value: string
3
+ export default value
4
+ }
@@ -0,0 +1,102 @@
1
+ import { sendTo, assign, setup, fromCallback } from "xstate";
2
+ import { parseSchema } from "../graphql/parse-schema.js";
3
+ import { findNodeModulesPath } from "../util/find-node-modules-path.js";
4
+ import { updateOperationTypes } from "../util/update-operation-types.js";
5
+ export const codeGenMachine = setup({
6
+ types: {
7
+ context: {},
8
+ events: {},
9
+ input: {},
10
+ },
11
+ guards: {
12
+ "all finished": (_, params) => params.finished.length === 2,
13
+ },
14
+ actors: {
15
+ loadSchema: fromCallback(({ sendBack }) => {
16
+ const loadSchema = async () => {
17
+ try {
18
+ const path = await findNodeModulesPath(["schema.graphql"]);
19
+ if (!path) {
20
+ sendBack({ type: "Error", error: new Error("No schema found") });
21
+ return;
22
+ }
23
+ const { schema } = parseSchema(path);
24
+ sendBack({ type: "Schema Loaded", schema });
25
+ }
26
+ catch (error) {
27
+ sendBack({ type: "Error", error });
28
+ }
29
+ };
30
+ loadSchema();
31
+ }),
32
+ generateOperations: fromCallback(({ sendBack, input }) => {
33
+ updateOperationTypes(input.schema)
34
+ .then(() => sendBack({ type: "Done" }))
35
+ .catch((error) => sendBack({ type: "Error", error }));
36
+ }),
37
+ },
38
+ actions: {
39
+ clearFinished: assign({
40
+ finished: [],
41
+ }),
42
+ setOperationsFinished: assign({
43
+ finished: (_, params) => params.finished.concat("operations.ts"),
44
+ }),
45
+ setError: assign({
46
+ error: (_, params) => params.error,
47
+ }),
48
+ setSchema: assign({
49
+ schema: (_, params) => params.schema,
50
+ }),
51
+ raiseDone: sendTo(({ context }) => context.parentRef, { type: "Code Generation Done" }),
52
+ },
53
+ }).createMachine({
54
+ id: "GraphQL Code Generation Machine",
55
+ context: ({ input }) => ({
56
+ schema: null,
57
+ finished: [],
58
+ parentRef: input.parentRef,
59
+ }),
60
+ states: {
61
+ "Load Schema": {
62
+ on: {
63
+ "Error": {
64
+ target: "Errored",
65
+ actions: { type: "setError", params: ({ event }) => event },
66
+ },
67
+ "Schema Loaded": {
68
+ target: "Generating Operations",
69
+ actions: { type: "setSchema", params: ({ event }) => event },
70
+ reenter: true,
71
+ },
72
+ },
73
+ invoke: {
74
+ src: "loadSchema",
75
+ },
76
+ },
77
+ "Done": {
78
+ entry: "raiseDone",
79
+ },
80
+ "Errored": {},
81
+ "Generating Operations": {
82
+ on: {
83
+ Done: {
84
+ target: "Done",
85
+ },
86
+ Error: {
87
+ target: "Errored",
88
+ actions: { type: "setError", params: ({ event }) => event },
89
+ },
90
+ },
91
+ invoke: {
92
+ src: "generateOperations",
93
+ input: ({ context }) => ({ schema: context.schema }),
94
+ },
95
+ },
96
+ },
97
+ initial: "Load Schema",
98
+ on: {
99
+ Change: ".Load Schema",
100
+ },
101
+ description: `Generates operations typescript types`,
102
+ });