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.
- package/lib/api/start-graphql-server.js +1 -1
- package/lib/client/components/action.d.ts +2 -1
- package/lib/client/hooks/index.d.ts +0 -1
- package/lib/client/hooks/use-query.d.ts +4 -3
- package/lib/client/icon.d.ts +2 -0
- package/lib/client/index.d.ts +2 -0
- package/lib/client/run-query.d.ts +8 -0
- package/lib/commands/connection/add.js +4 -4
- package/lib/commands/dev.js +8 -0
- package/lib/graphql/generate-operations.js +151 -0
- package/lib/graphql/generate-types.js +109 -0
- package/lib/graphql/parse-schema.js +65 -0
- package/lib/graphql.d.ts +4 -0
- package/lib/machines/code-gen-machine.js +102 -0
- package/lib/machines/dev-machine.js +32 -10
- package/lib/machines/js-machine.js +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/lib/util/find-node-modules-path.js +17 -0
- package/lib/util/update-env-types.js +3 -15
- package/lib/util/update-operation-types.js +13 -0
- package/package.json +6 -1
- package/lib/client/hooks/use-graphql.d.ts +0 -4
- package/lib/schema.graphql +0 -536
- package/lib/schema.json +0 -67
|
@@ -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?:
|
|
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
|
-
|
|
1
|
+
import { Query } from "../run-query";
|
|
2
|
+
export declare function useQuery<Variables, Result>(
|
|
2
3
|
/** GraphQL query */
|
|
3
|
-
query:
|
|
4
|
+
query: Query<Variables, Result>,
|
|
4
5
|
/** GraphQL query variables */
|
|
5
|
-
variableValues?:
|
|
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";
|
package/lib/client/index.d.ts
CHANGED
|
@@ -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";
|
|
@@ -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 ?? "",
|
|
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 ?? "",
|
|
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 ?? "",
|
|
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 ?? "",
|
|
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) "),
|
package/lib/commands/dev.js
CHANGED
|
@@ -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
|
+
}
|
package/lib/graphql.d.ts
ADDED
|
@@ -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
|
+
});
|