@avantstay/graphql-ts-client 10.5.0
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/README.md +78 -0
- package/dist/endpoint.d.ts +13 -0
- package/dist/endpoint.js +836 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +985 -0
- package/dist/types-4ecbabdf.d.ts +69 -0
- package/package.json +85 -0
- package/src/__snapshots__/graphqlRequest.test.ts.snap +24 -0
- package/src/__testClient.d.ts +148 -0
- package/src/__testClient.js +118 -0
- package/src/endpoint.ts +117 -0
- package/src/generateTypescriptClient.test.ts +127 -0
- package/src/generateTypescriptClient.ts +547 -0
- package/src/graphqlRequest.test.ts +103 -0
- package/src/graphqlRequest.ts +95 -0
- package/src/index.ts +3 -0
- package/src/jsonToGraphQLQuery.test.ts +66 -0
- package/src/jsonToGraphQLQuery.ts +88 -0
- package/src/logging.ts +33 -0
- package/src/testServer.ts +71 -0
- package/src/types.ts +114 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
type Maybe<T> = null | undefined | T;
|
|
2
|
+
type Defined<T> = Exclude<T, undefined>;
|
|
3
|
+
type ResponseData = {
|
|
4
|
+
data: any;
|
|
5
|
+
warnings: any;
|
|
6
|
+
headers: any;
|
|
7
|
+
status?: number;
|
|
8
|
+
errors: {
|
|
9
|
+
message: string;
|
|
10
|
+
}[];
|
|
11
|
+
};
|
|
12
|
+
type ResponseListenerInfo = {
|
|
13
|
+
queryName: string;
|
|
14
|
+
query: string;
|
|
15
|
+
variables: any;
|
|
16
|
+
response: ResponseData;
|
|
17
|
+
};
|
|
18
|
+
type IResponseListener = (info: ResponseListenerInfo) => void | Promise<void>;
|
|
19
|
+
type ArrayElement<ArrayType extends readonly unknown[]> = ArrayType extends readonly (infer ElementType)[] ? ElementType : never;
|
|
20
|
+
type Projection<Selection, Base, E = never> = Base extends Array<any> ? ArrayElement<Base> extends Date | string | number | boolean | null | undefined | E ? ArrayElement<Base>[] : Projection<Defined<Selection>, ArrayElement<Base>, E>[] : Base extends Date | string | number | boolean | null | E ? Selection extends undefined ? Base | undefined : Base : {
|
|
21
|
+
[k in keyof Selection & keyof Base]: Selection[k] extends boolean ? Base[k] : Base[k] extends Array<infer A> ? Projection<Defined<Selection[k]>, A, E>[] : Projection<Defined<Selection[k]>, Base[k], E>;
|
|
22
|
+
};
|
|
23
|
+
type Unpacked<T> = T extends (infer U)[] ? U : T extends (...args: any[]) => infer U ? U : T extends Promise<infer U> ? U : T;
|
|
24
|
+
type Replacement<M extends [any, any], T> = M extends any ? ([T] extends [M[0]] ? M[1] : never) : never;
|
|
25
|
+
type DeepReplace<T, Ignore, M extends [any, any]> = {
|
|
26
|
+
[P in keyof T]: T[P] extends M[0] ? T[P] extends Ignore ? T[P] extends object ? DeepReplace<T[P], Ignore, M> : T[P] : Replacement<M, T[P]> : T[P] extends object ? DeepReplace<T[P], Ignore, M> : T[P];
|
|
27
|
+
};
|
|
28
|
+
type RawEndpoint<I, O, E> = <S extends I>(jsonQuery?: S) => Promise<{
|
|
29
|
+
data: Projection<S, O, E>;
|
|
30
|
+
errors: any[];
|
|
31
|
+
warnings: any[];
|
|
32
|
+
headers: any;
|
|
33
|
+
status: any;
|
|
34
|
+
}>;
|
|
35
|
+
type JsonOutput<O, ToBeIgnored> = DeepReplace<O, ToBeIgnored, [string | Date, string]>;
|
|
36
|
+
type Endpoint<I, O, E> = (<S extends I>(jsonQuery?: S) => Promise<Projection<S, JsonOutput<O, E>, E>>) & {
|
|
37
|
+
memo: <S extends I>(jsonQuery?: S) => Promise<Projection<S, JsonOutput<O, E>, E>>;
|
|
38
|
+
memoRaw: RawEndpoint<I, JsonOutput<O, E>, E>;
|
|
39
|
+
raw: RawEndpoint<I, JsonOutput<O, E>, E>;
|
|
40
|
+
};
|
|
41
|
+
type ClientConfig = {
|
|
42
|
+
url: string;
|
|
43
|
+
headers: {
|
|
44
|
+
[key: string]: string;
|
|
45
|
+
};
|
|
46
|
+
retryConfig: {
|
|
47
|
+
max: number;
|
|
48
|
+
waitBeforeRetry?: number;
|
|
49
|
+
before: IResponseListener;
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
type LogInfo = {
|
|
53
|
+
query: string;
|
|
54
|
+
variables: any;
|
|
55
|
+
formatGraphQL: any;
|
|
56
|
+
kind: string;
|
|
57
|
+
queryName: string;
|
|
58
|
+
response?: any;
|
|
59
|
+
error?: Error;
|
|
60
|
+
duration: number;
|
|
61
|
+
};
|
|
62
|
+
declare class GraphQLClientError extends Error {
|
|
63
|
+
responseData: ResponseData;
|
|
64
|
+
constructor(responseData: ResponseData);
|
|
65
|
+
get message(): string;
|
|
66
|
+
get response(): ResponseData;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export { ClientConfig as C, Defined as D, Endpoint as E, GraphQLClientError as G, IResponseListener as I, JsonOutput as J, LogInfo as L, Maybe as M, Projection as P, ResponseData as R, Unpacked as U, ResponseListenerInfo as a, Replacement as b, DeepReplace as c, RawEndpoint as d };
|
package/package.json
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@avantstay/graphql-ts-client",
|
|
3
|
+
"version": "10.5.0",
|
|
4
|
+
"description": "GraphQL Typescript Client Generator",
|
|
5
|
+
"author": "Wellington Guimaraes",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"typings": "dist/index.d.ts",
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsup src/index.ts src/endpoint.ts --dts",
|
|
11
|
+
"test": "cross-env GQL_CLIENT_DIST_PATH='.' jest --watch",
|
|
12
|
+
"prepublishOnly": "yarn build"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"src"
|
|
17
|
+
],
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=12"
|
|
20
|
+
},
|
|
21
|
+
"bugs": {
|
|
22
|
+
"url": "https://github.com/avantstay/graphql-ts-client/issues"
|
|
23
|
+
},
|
|
24
|
+
"homepage": "https://github.com/avantstay/graphql-ts-client#readme",
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "git+https://github.com/avantstay/graphql-ts-client.git"
|
|
28
|
+
},
|
|
29
|
+
"peerDependencies": {},
|
|
30
|
+
"prettier": {
|
|
31
|
+
"trailingComma": "es5",
|
|
32
|
+
"tabWidth": 2,
|
|
33
|
+
"proseWrap": "always",
|
|
34
|
+
"bracketSpacing": true,
|
|
35
|
+
"jsxBracketSameLine": false,
|
|
36
|
+
"semi": false,
|
|
37
|
+
"singleQuote": true,
|
|
38
|
+
"arrowParens": "avoid",
|
|
39
|
+
"endOfLine": "lf",
|
|
40
|
+
"printWidth": 130,
|
|
41
|
+
"htmlWhitespaceSensitivity": "ignore",
|
|
42
|
+
"jsxSingleQuote": false
|
|
43
|
+
},
|
|
44
|
+
"module": "dist/graphql-ts-client.esm.js",
|
|
45
|
+
"size-limit": [
|
|
46
|
+
{
|
|
47
|
+
"path": "dist/graphql-ts-client.cjs.production.min.js",
|
|
48
|
+
"limit": "10 KB"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"path": "dist/graphql-ts-client.esm.js",
|
|
52
|
+
"limit": "10 KB"
|
|
53
|
+
}
|
|
54
|
+
],
|
|
55
|
+
"dependencies": {
|
|
56
|
+
"@types/md5": "^2.3.2",
|
|
57
|
+
"axios": "^1.4.0",
|
|
58
|
+
"axios-retry": "^3.5.1",
|
|
59
|
+
"case": "^1.6.3",
|
|
60
|
+
"esbuild": "^0.18.16",
|
|
61
|
+
"graphql": "^16.7.1",
|
|
62
|
+
"lodash": "^4.17.21",
|
|
63
|
+
"md5": "^2.3.0",
|
|
64
|
+
"moize": "^6.1.6",
|
|
65
|
+
"prettier": "^2.8.8",
|
|
66
|
+
"ts-essentials": "^8.1.0"
|
|
67
|
+
},
|
|
68
|
+
"devDependencies": {
|
|
69
|
+
"@babel/preset-env": "^7.22.9",
|
|
70
|
+
"@babel/preset-typescript": "^7.22.5",
|
|
71
|
+
"@jest/globals": "^29.6.1",
|
|
72
|
+
"@types/graphql": "^14.5.0",
|
|
73
|
+
"@types/jest": "^29.5.3",
|
|
74
|
+
"@types/lodash": "^4.14.195",
|
|
75
|
+
"@types/node": "^20.4.5",
|
|
76
|
+
"@types/prettier": "^2.7.3",
|
|
77
|
+
"apollo-server": "^3.12.0",
|
|
78
|
+
"cross-env": "^7.0.3",
|
|
79
|
+
"jest": "^29.6.1",
|
|
80
|
+
"ts-node": "^10.9.1",
|
|
81
|
+
"tsup": "^7.1.0",
|
|
82
|
+
"typescript": "^5.1.6",
|
|
83
|
+
"@swc/core": "^1.3.71"
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`GraphQLRequest Should request have proper structure 1`] = `
|
|
4
|
+
Arguments [
|
|
5
|
+
"https://whatever.com",
|
|
6
|
+
{
|
|
7
|
+
"operationName": "sampleQueryName",
|
|
8
|
+
"query": "sampleQuery",
|
|
9
|
+
"variables": {
|
|
10
|
+
"bar": "foo",
|
|
11
|
+
"foo": "bar",
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"headers": {
|
|
16
|
+
"Content-Type": "application/json",
|
|
17
|
+
},
|
|
18
|
+
"params": {
|
|
19
|
+
"_q": "sampleQueryName",
|
|
20
|
+
},
|
|
21
|
+
"validateStatus": [Function],
|
|
22
|
+
},
|
|
23
|
+
]
|
|
24
|
+
`;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// noinspection TypeScriptUnresolvedVariable, ES6UnusedImports, JSUnusedLocalSymbols, TypeScriptCheckImport
|
|
2
|
+
import { DeepRequired } from "ts-essentials"
|
|
3
|
+
import { Maybe, IResponseListener, Endpoint } from "."
|
|
4
|
+
|
|
5
|
+
// Scalars
|
|
6
|
+
export type IDate = string | Date
|
|
7
|
+
export declare type ISODate = IDate
|
|
8
|
+
|
|
9
|
+
// Enums
|
|
10
|
+
|
|
11
|
+
export declare enum BookType {
|
|
12
|
+
dolor = "DOLOR",
|
|
13
|
+
ipsum = "IPSUM",
|
|
14
|
+
sit = "SIT",
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
type AllEnums = BookType
|
|
18
|
+
|
|
19
|
+
// Args
|
|
20
|
+
export interface BooksWithoutParamsArgs {}
|
|
21
|
+
export interface BooksWithOptionalParamsArgs {
|
|
22
|
+
params?: BookSearchParamsAllOptional
|
|
23
|
+
}
|
|
24
|
+
export interface BooksWithRequiredParamsArgs {
|
|
25
|
+
params: BookSearchParamsSomeRequired
|
|
26
|
+
}
|
|
27
|
+
export interface FailingQueryArgs {
|
|
28
|
+
id: string
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Input/Output Types
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @deprecated Avoid directly using this interface. Instead, create a type alias based on the query/mutation return type.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
export interface Book {
|
|
38
|
+
title?: string
|
|
39
|
+
author?: string
|
|
40
|
+
type?: BookType
|
|
41
|
+
dateCreated?: ISODate
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @deprecated Avoid directly using this interface. Instead, create a type alias based on the query/mutation return type.
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
export interface BookSearchParamsAllOptional {
|
|
49
|
+
title?: string
|
|
50
|
+
author?: string
|
|
51
|
+
createdAfter?: ISODate
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @deprecated Avoid directly using this interface. Instead, create a type alias based on the query/mutation return type.
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
export interface BookSearchParamsSomeRequired {
|
|
59
|
+
title: string
|
|
60
|
+
author?: string
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @deprecated Avoid directly using this interface. Instead, create a type alias based on the query/mutation return type.
|
|
65
|
+
*/
|
|
66
|
+
|
|
67
|
+
export interface Query {
|
|
68
|
+
booksWithoutParams?: Book[]
|
|
69
|
+
booksWithOptionalParams?: Book[]
|
|
70
|
+
booksWithRequiredParams?: Book[]
|
|
71
|
+
failingQuery?: string
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Selection Types
|
|
75
|
+
|
|
76
|
+
export interface BookSelection {
|
|
77
|
+
title?: boolean
|
|
78
|
+
author?: boolean
|
|
79
|
+
type?: boolean
|
|
80
|
+
dateCreated?: boolean
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface BookSearchParamsAllOptionalSelection {
|
|
84
|
+
title?: boolean
|
|
85
|
+
author?: boolean
|
|
86
|
+
createdAfter?: boolean
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface BookSearchParamsSomeRequiredSelection {
|
|
90
|
+
title?: boolean
|
|
91
|
+
author?: boolean
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export declare const myApiClient: {
|
|
95
|
+
addResponseListener: (listener: IResponseListener) => void
|
|
96
|
+
setHeader: (key: string, value: string) => void
|
|
97
|
+
setHeaders: (newHeaders: { [k: string]: string }) => void
|
|
98
|
+
setUrl: (url: string) => void
|
|
99
|
+
setRetryConfig: (options: {
|
|
100
|
+
max: number
|
|
101
|
+
waitBeforeRetry?: number
|
|
102
|
+
before?: IResponseListener
|
|
103
|
+
}) => void
|
|
104
|
+
queries: {
|
|
105
|
+
booksWithoutParams: Endpoint<
|
|
106
|
+
{
|
|
107
|
+
__headers?: { [key: string]: string }
|
|
108
|
+
__retry?: boolean
|
|
109
|
+
__alias?: string
|
|
110
|
+
} & BookSelection,
|
|
111
|
+
DeepRequired<Book[]>,
|
|
112
|
+
AllEnums
|
|
113
|
+
>
|
|
114
|
+
booksWithOptionalParams: Endpoint<
|
|
115
|
+
{
|
|
116
|
+
__headers?: { [key: string]: string }
|
|
117
|
+
__retry?: boolean
|
|
118
|
+
__alias?: string
|
|
119
|
+
__args?: BooksWithOptionalParamsArgs
|
|
120
|
+
} & BookSelection,
|
|
121
|
+
DeepRequired<Book[]>,
|
|
122
|
+
AllEnums
|
|
123
|
+
>
|
|
124
|
+
booksWithRequiredParams: Endpoint<
|
|
125
|
+
{
|
|
126
|
+
__headers?: { [key: string]: string }
|
|
127
|
+
__retry?: boolean
|
|
128
|
+
__alias?: string
|
|
129
|
+
__args: BooksWithRequiredParamsArgs
|
|
130
|
+
} & BookSelection,
|
|
131
|
+
DeepRequired<Book[]>,
|
|
132
|
+
AllEnums
|
|
133
|
+
>
|
|
134
|
+
failingQuery: Endpoint<
|
|
135
|
+
{
|
|
136
|
+
__headers?: { [key: string]: string }
|
|
137
|
+
__retry?: boolean
|
|
138
|
+
__alias?: string
|
|
139
|
+
__args: FailingQueryArgs
|
|
140
|
+
},
|
|
141
|
+
string,
|
|
142
|
+
AllEnums
|
|
143
|
+
>
|
|
144
|
+
}
|
|
145
|
+
mutations: {}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export default myApiClient
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
20
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
21
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
22
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
23
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
24
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
25
|
+
mod
|
|
26
|
+
));
|
|
27
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
|
+
var stdin_exports = {};
|
|
29
|
+
__export(stdin_exports, {
|
|
30
|
+
BookType: () => BookType,
|
|
31
|
+
default: () => stdin_default,
|
|
32
|
+
myApiClient: () => myApiClient
|
|
33
|
+
});
|
|
34
|
+
module.exports = __toCommonJS(stdin_exports);
|
|
35
|
+
var import_endpoint = require("./endpoint");
|
|
36
|
+
var import_standalone = require("prettier/standalone");
|
|
37
|
+
var import_parser_graphql = __toESM(require("prettier/parser-graphql"));
|
|
38
|
+
const formatGraphQL = (query) => (0, import_standalone.format)(query, { parser: "graphql", plugins: [import_parser_graphql.default] });
|
|
39
|
+
const BookType = {
|
|
40
|
+
dolor: "DOLOR",
|
|
41
|
+
ipsum: "IPSUM",
|
|
42
|
+
sit: "SIT"
|
|
43
|
+
};
|
|
44
|
+
const typesTree = {
|
|
45
|
+
Query: {
|
|
46
|
+
get booksWithOptionalParams() {
|
|
47
|
+
return {
|
|
48
|
+
__args: {
|
|
49
|
+
params: "BookSearchParamsAllOptional!"
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
},
|
|
53
|
+
get booksWithRequiredParams() {
|
|
54
|
+
return {
|
|
55
|
+
__args: {
|
|
56
|
+
params: "BookSearchParamsSomeRequired!"
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
},
|
|
60
|
+
get failingQuery() {
|
|
61
|
+
return {
|
|
62
|
+
__args: {
|
|
63
|
+
id: "String!"
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
},
|
|
67
|
+
booksWithoutParams: {}
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
let verbose = false;
|
|
71
|
+
let headers = {};
|
|
72
|
+
let url = "http://localhost:4123/graphql";
|
|
73
|
+
let retryConfig = {
|
|
74
|
+
max: 0,
|
|
75
|
+
before: void 0,
|
|
76
|
+
waitBeforeRetry: 0
|
|
77
|
+
};
|
|
78
|
+
let responseListeners = [];
|
|
79
|
+
let errorsParser = void 0;
|
|
80
|
+
let apiEndpoint = (0, import_endpoint.getApiEndpointCreator)({
|
|
81
|
+
getClient: () => ({ url, headers, retryConfig }),
|
|
82
|
+
responseListeners,
|
|
83
|
+
maxAge: 3e4,
|
|
84
|
+
verbose,
|
|
85
|
+
typesTree,
|
|
86
|
+
formatGraphQL,
|
|
87
|
+
errorsParser
|
|
88
|
+
});
|
|
89
|
+
const myApiClient = {
|
|
90
|
+
addResponseListener: (listener) => responseListeners.push(
|
|
91
|
+
listener
|
|
92
|
+
),
|
|
93
|
+
setHeader: (key, value) => {
|
|
94
|
+
headers[key] = value;
|
|
95
|
+
},
|
|
96
|
+
setHeaders: (newHeaders) => {
|
|
97
|
+
headers = newHeaders;
|
|
98
|
+
},
|
|
99
|
+
setRetryConfig: (options) => {
|
|
100
|
+
if (!Number.isInteger(options.max) || options.max < 0) {
|
|
101
|
+
throw new Error("retryOptions.max should be a non-negative integer");
|
|
102
|
+
}
|
|
103
|
+
retryConfig = {
|
|
104
|
+
max: options.max,
|
|
105
|
+
waitBeforeRetry: options.waitBeforeRetry,
|
|
106
|
+
before: options.before
|
|
107
|
+
};
|
|
108
|
+
},
|
|
109
|
+
setUrl: (_url) => url = _url,
|
|
110
|
+
queries: {
|
|
111
|
+
booksWithoutParams: apiEndpoint("query", "booksWithoutParams"),
|
|
112
|
+
booksWithOptionalParams: apiEndpoint("query", "booksWithOptionalParams"),
|
|
113
|
+
booksWithRequiredParams: apiEndpoint("query", "booksWithRequiredParams"),
|
|
114
|
+
failingQuery: apiEndpoint("query", "failingQuery")
|
|
115
|
+
},
|
|
116
|
+
mutations: {}
|
|
117
|
+
};
|
|
118
|
+
var stdin_default = myApiClient;
|
package/src/endpoint.ts
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import memoize from 'moize'
|
|
2
|
+
import { graphqlRequest } from './graphqlRequest'
|
|
3
|
+
import { jsonToGraphQLQuery } from './jsonToGraphQLQuery'
|
|
4
|
+
import { logRequest } from './logging'
|
|
5
|
+
import {ClientConfig, Endpoint, GraphQLClientError, IResponseListener, Projection, ResponseListenerInfo} from './types'
|
|
6
|
+
|
|
7
|
+
const executeListeners = (listeners: IResponseListener[], data: ResponseListenerInfo) =>
|
|
8
|
+
setTimeout(() =>
|
|
9
|
+
listeners.forEach(runResponseListener =>
|
|
10
|
+
runResponseListener(data)
|
|
11
|
+
)
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
export const getApiEndpointCreator =
|
|
15
|
+
(apiConfig: {
|
|
16
|
+
getClient: () => ClientConfig
|
|
17
|
+
responseListeners: IResponseListener[]
|
|
18
|
+
typesTree: any
|
|
19
|
+
maxAge: number
|
|
20
|
+
verbose: boolean
|
|
21
|
+
formatGraphQL: any
|
|
22
|
+
errorsParser?: (errors: any[]) => any
|
|
23
|
+
}) =>
|
|
24
|
+
<I = any, O = any, E = any>(kind: 'mutation' | 'query', queryName: string): Endpoint<I, O, E> => {
|
|
25
|
+
const rawEndpoint: any = async <S extends I>(
|
|
26
|
+
failureMode: 'loud' | 'silent',
|
|
27
|
+
jsonQuery?: S
|
|
28
|
+
): Promise<{
|
|
29
|
+
data: Projection<S, O>
|
|
30
|
+
errors: any[]
|
|
31
|
+
warnings: any[]
|
|
32
|
+
headers: any
|
|
33
|
+
status: any
|
|
34
|
+
}> => {
|
|
35
|
+
const alias = (jsonQuery as any)?.__alias ?? queryName
|
|
36
|
+
const shouldRetry = (jsonQuery as any)?.__retry ?? true
|
|
37
|
+
const requestHeaders = (jsonQuery as any)?.__headers ?? {}
|
|
38
|
+
const { query, variables } = jsonToGraphQLQuery({ kind, queryName, jsonQuery, typesTree: apiConfig.typesTree })
|
|
39
|
+
const start = +new Date()
|
|
40
|
+
|
|
41
|
+
const logOptions = {
|
|
42
|
+
kind,
|
|
43
|
+
queryName: alias,
|
|
44
|
+
formatGraphQL: apiConfig.formatGraphQL,
|
|
45
|
+
requestHeaders,
|
|
46
|
+
query,
|
|
47
|
+
variables,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const responseListenerData = {
|
|
51
|
+
queryName: alias,
|
|
52
|
+
query: apiConfig.formatGraphQL(query),
|
|
53
|
+
variables,
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
const { data, errors, warnings, headers, status } = await graphqlRequest({
|
|
58
|
+
shouldRetry,
|
|
59
|
+
failureMode,
|
|
60
|
+
queryName: alias,
|
|
61
|
+
client: apiConfig.getClient(),
|
|
62
|
+
requestHeaders,
|
|
63
|
+
query,
|
|
64
|
+
variables,
|
|
65
|
+
errorsParser: apiConfig.errorsParser
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
const response = { data, warnings, headers, status, errors }
|
|
69
|
+
|
|
70
|
+
if (apiConfig.verbose && globalThis.document) {
|
|
71
|
+
logRequest({
|
|
72
|
+
...logOptions,
|
|
73
|
+
response,
|
|
74
|
+
duration: +new Date() - start,
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
executeListeners(apiConfig.responseListeners,{
|
|
79
|
+
...responseListenerData,
|
|
80
|
+
response,
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
return { data: data?.[alias], errors, warnings, headers, status }
|
|
84
|
+
} catch (error) {
|
|
85
|
+
if (apiConfig.verbose && globalThis.document) {
|
|
86
|
+
logRequest({
|
|
87
|
+
...logOptions,
|
|
88
|
+
error: error as Error,
|
|
89
|
+
duration: +new Date() - start,
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
executeListeners(apiConfig.responseListeners, {
|
|
94
|
+
...responseListenerData,
|
|
95
|
+
response: (error as GraphQLClientError).response,
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
throw error
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const endpoint: any = async <S extends I>(jsonQuery?: S): Promise<Projection<S, O>> => {
|
|
103
|
+
const { data } = await rawEndpoint('loud', jsonQuery)
|
|
104
|
+
return data
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const memoizeeOptions = {
|
|
108
|
+
maxAge: apiConfig.maxAge,
|
|
109
|
+
isSerialized: true,
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
endpoint.raw = rawEndpoint.bind(null, 'silent')
|
|
113
|
+
endpoint.memo = memoize(endpoint, memoizeeOptions)
|
|
114
|
+
endpoint.memoRaw = memoize(endpoint.raw, memoizeeOptions)
|
|
115
|
+
|
|
116
|
+
return endpoint
|
|
117
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { ApolloServer } from 'apollo-server'
|
|
2
|
+
import * as path from 'path'
|
|
3
|
+
import { generateTypescriptClient } from './generateTypescriptClient'
|
|
4
|
+
import { startServer } from './testServer'
|
|
5
|
+
import { GraphQLClientError, ResponseListenerInfo } from './types'
|
|
6
|
+
|
|
7
|
+
let testServer: { server: ApolloServer; url: string }
|
|
8
|
+
let client: any
|
|
9
|
+
|
|
10
|
+
describe('Generated Client', () => {
|
|
11
|
+
beforeAll(async () => {
|
|
12
|
+
testServer = await startServer()
|
|
13
|
+
|
|
14
|
+
const clientName = 'myApiClient'
|
|
15
|
+
|
|
16
|
+
const { js } = await generateTypescriptClient({
|
|
17
|
+
clientName,
|
|
18
|
+
endpoint: `${testServer.url}graphql`,
|
|
19
|
+
// For the sake of checking the generated code, we'll
|
|
20
|
+
// specify an output path
|
|
21
|
+
output: path.resolve(__dirname, './__testClient.ts'),
|
|
22
|
+
formatGraphQL: true,
|
|
23
|
+
skipCache: true,
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
client = eval(`${js};${clientName}`)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
afterAll(async () => await testServer.server.stop())
|
|
30
|
+
|
|
31
|
+
it('should be able to pass custom headers to a query without args', async () => {
|
|
32
|
+
const books = await client.queries.booksWithoutParams({
|
|
33
|
+
__headers: { 'X-Custom-Header': 'Foo' },
|
|
34
|
+
title: true,
|
|
35
|
+
author: true,
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
expect(books).toHaveLength(2)
|
|
39
|
+
expect(books[0]).toHaveProperty('title')
|
|
40
|
+
expect(books[0]).toHaveProperty('author')
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('should be able to make queries with optional args, not passing args obj', async () => {
|
|
44
|
+
// noinspection TypeScriptValidateJSTypes
|
|
45
|
+
const books = await client.queries.booksWithOptionalParams({
|
|
46
|
+
title: true,
|
|
47
|
+
author: true,
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
expect(books).toHaveLength(2)
|
|
51
|
+
expect(books[0]).toHaveProperty('title')
|
|
52
|
+
expect(books[0]).toHaveProperty('author')
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('should be able to make queries with optional args, not passing args obj', async () => {
|
|
56
|
+
// noinspection TypeScriptValidateJSTypes
|
|
57
|
+
const books = await client.queries.booksWithOptionalParams({
|
|
58
|
+
__alias: 'helloWorld',
|
|
59
|
+
title: true,
|
|
60
|
+
author: true,
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
expect(books).toHaveLength(2)
|
|
64
|
+
expect(books[0]).toHaveProperty('title')
|
|
65
|
+
expect(books[0]).toHaveProperty('author')
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('fail with broken queries', async () => {
|
|
69
|
+
const result = await client.queries
|
|
70
|
+
.failingQuery({
|
|
71
|
+
__args: {
|
|
72
|
+
id: 'hello',
|
|
73
|
+
},
|
|
74
|
+
})
|
|
75
|
+
.then(
|
|
76
|
+
() => 'success',
|
|
77
|
+
(err: any) => err
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
expect(result).toBeInstanceOf(GraphQLClientError)
|
|
81
|
+
expect(result.message).toBe('Failed lorem ipsum dolor')
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('does not fail with broken queries when using raw requests', async () => {
|
|
85
|
+
const result = await client.queries.failingQuery.raw({
|
|
86
|
+
__args: {
|
|
87
|
+
id: 'hello',
|
|
88
|
+
},
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
expect(result.status).toBe(200)
|
|
92
|
+
expect(result.errors).toHaveLength(1)
|
|
93
|
+
expect(result.errors[0].message).toBe('Failed lorem ipsum dolor')
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('failing operations throw errors', async () => {
|
|
97
|
+
let failed = false
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
await client.queries.failingQuery({
|
|
101
|
+
__args: {
|
|
102
|
+
id: 'hello',
|
|
103
|
+
},
|
|
104
|
+
})
|
|
105
|
+
} catch (err) {
|
|
106
|
+
failed = true
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
expect(failed).toBe(true)
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it('failing operations throw errors', async () => {
|
|
113
|
+
let responseData: ResponseListenerInfo | undefined
|
|
114
|
+
client.addResponseListener((data: any) => (responseData = data))
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
await client.queries.failingQuery({
|
|
118
|
+
__args: {
|
|
119
|
+
id: 'hello',
|
|
120
|
+
},
|
|
121
|
+
})
|
|
122
|
+
} catch {}
|
|
123
|
+
|
|
124
|
+
expect(responseData?.queryName).toBe('failingQuery')
|
|
125
|
+
expect(responseData?.response.errors.length).toBeGreaterThan(0)
|
|
126
|
+
})
|
|
127
|
+
})
|