@hyper-fetch/graphql 1.0.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/.eslintrc.json +6 -0
- package/README.md +73 -0
- package/dist/browser/index.cjs.js +160 -0
- package/dist/browser/index.cjs.js.map +7 -0
- package/dist/browser/index.esm.js +139 -0
- package/dist/browser/index.esm.js.map +7 -0
- package/dist/index.cjs.js +250 -0
- package/dist/index.cjs.js.map +7 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.esm.js +215 -0
- package/dist/index.esm.js.map +7 -0
- package/package.json +87 -0
- package/src/adapter/adapter.browser.ts +90 -0
- package/src/adapter/adapter.constants.ts +9 -0
- package/src/adapter/adapter.server.ts +91 -0
- package/src/adapter/adapter.ts +27 -0
- package/src/adapter/adapter.types.ts +54 -0
- package/src/adapter/adapter.utils.ts +23 -0
- package/src/adapter/index.ts +4 -0
- package/src/index.ts +3 -0
- package/tsconfig.base.json +13 -0
- package/tsconfig.json +12 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { getAdapterBindings, getResponseHeaders, parseResponse, parseErrorResponse } from "@hyper-fetch/core";
|
|
2
|
+
|
|
3
|
+
import { gqlExtra, GraphQLAdapterType, defaultTimeout, getRequestValues } from "adapter";
|
|
4
|
+
|
|
5
|
+
export const adapter: GraphQLAdapterType = async (request, requestId) => {
|
|
6
|
+
const {
|
|
7
|
+
makeRequest,
|
|
8
|
+
config,
|
|
9
|
+
headers,
|
|
10
|
+
onError,
|
|
11
|
+
onResponseEnd,
|
|
12
|
+
onTimeoutError,
|
|
13
|
+
onRequestEnd,
|
|
14
|
+
createAbortListener,
|
|
15
|
+
onResponseProgress,
|
|
16
|
+
onRequestProgress,
|
|
17
|
+
onResponseStart,
|
|
18
|
+
onBeforeRequest,
|
|
19
|
+
onRequestStart,
|
|
20
|
+
onSuccess,
|
|
21
|
+
} = await getAdapterBindings<GraphQLAdapterType>(request, requestId, 0, gqlExtra);
|
|
22
|
+
|
|
23
|
+
const { fullUrl, payload, method } = getRequestValues(request);
|
|
24
|
+
|
|
25
|
+
return makeRequest((resolve) => {
|
|
26
|
+
const xhr = new XMLHttpRequest();
|
|
27
|
+
xhr.timeout = defaultTimeout;
|
|
28
|
+
|
|
29
|
+
const abort = () => xhr.abort();
|
|
30
|
+
|
|
31
|
+
// Inject xhr options
|
|
32
|
+
Object.entries(config).forEach(([name, value]) => {
|
|
33
|
+
xhr[name] = value;
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Open connection
|
|
37
|
+
xhr.open(method, fullUrl, true);
|
|
38
|
+
|
|
39
|
+
// Set Headers
|
|
40
|
+
Object.entries(headers).forEach(([name, value]) => xhr.setRequestHeader(name, value as string));
|
|
41
|
+
|
|
42
|
+
// Listen to abort signal
|
|
43
|
+
const unmountListener = createAbortListener(0, gqlExtra, abort, resolve);
|
|
44
|
+
|
|
45
|
+
// Request handlers
|
|
46
|
+
xhr.upload.onprogress = onRequestProgress;
|
|
47
|
+
|
|
48
|
+
// Response handlers
|
|
49
|
+
xhr.onloadstart = (): void => {
|
|
50
|
+
onRequestEnd();
|
|
51
|
+
onResponseStart();
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
xhr.onprogress = onResponseProgress;
|
|
55
|
+
|
|
56
|
+
xhr.onloadend = () => {
|
|
57
|
+
onResponseEnd();
|
|
58
|
+
unmountListener();
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
xhr.ontimeout = () => onTimeoutError(0, gqlExtra, resolve);
|
|
62
|
+
|
|
63
|
+
// Data handler
|
|
64
|
+
xhr.onreadystatechange = (e: Event) => {
|
|
65
|
+
const event = e as unknown as ProgressEvent<XMLHttpRequest>;
|
|
66
|
+
const finishedState = 4;
|
|
67
|
+
|
|
68
|
+
if (event.target && event.target.readyState === finishedState) {
|
|
69
|
+
const { status } = event.target;
|
|
70
|
+
const success = String(status).startsWith("2") || String(status).startsWith("3");
|
|
71
|
+
const responseHeaders = getResponseHeaders(xhr.getAllResponseHeaders());
|
|
72
|
+
|
|
73
|
+
if (success) {
|
|
74
|
+
const data = parseResponse(event.target.response);
|
|
75
|
+
onSuccess(data, status, { headers: responseHeaders }, resolve);
|
|
76
|
+
} else {
|
|
77
|
+
// delay to finish after onabort/ontimeout
|
|
78
|
+
const data = parseErrorResponse(event.target.response);
|
|
79
|
+
onError(data, status, { headers: responseHeaders }, resolve);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// Start request
|
|
85
|
+
onBeforeRequest();
|
|
86
|
+
onRequestStart();
|
|
87
|
+
|
|
88
|
+
xhr.send(payload);
|
|
89
|
+
});
|
|
90
|
+
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { getAdapterBindings, parseErrorResponse, parseResponse } from "@hyper-fetch/core";
|
|
2
|
+
import http, { OutgoingHttpHeaders } from "http";
|
|
3
|
+
import https from "https";
|
|
4
|
+
|
|
5
|
+
import { gqlExtra, defaultTimeout, GraphQLAdapterType, getRequestValues } from "adapter";
|
|
6
|
+
|
|
7
|
+
export const adapter: GraphQLAdapterType = async (request, requestId) => {
|
|
8
|
+
const {
|
|
9
|
+
makeRequest,
|
|
10
|
+
config,
|
|
11
|
+
headers,
|
|
12
|
+
onError,
|
|
13
|
+
onResponseEnd,
|
|
14
|
+
onTimeoutError,
|
|
15
|
+
onRequestEnd,
|
|
16
|
+
createAbortListener,
|
|
17
|
+
onResponseProgress,
|
|
18
|
+
onResponseStart,
|
|
19
|
+
onBeforeRequest,
|
|
20
|
+
onRequestStart,
|
|
21
|
+
onSuccess,
|
|
22
|
+
} = await getAdapterBindings<GraphQLAdapterType>(request, requestId, 0, gqlExtra);
|
|
23
|
+
|
|
24
|
+
const { fullUrl, payload, method } = getRequestValues(request);
|
|
25
|
+
|
|
26
|
+
const httpClient = request.client.url.includes("https://") ? https : http;
|
|
27
|
+
const options = {
|
|
28
|
+
path: fullUrl,
|
|
29
|
+
method,
|
|
30
|
+
headers: headers as OutgoingHttpHeaders,
|
|
31
|
+
timeout: defaultTimeout,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
Object.entries(config).forEach(([name, value]) => {
|
|
35
|
+
options[name] = value;
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
let unmountListener = () => null;
|
|
39
|
+
onBeforeRequest();
|
|
40
|
+
|
|
41
|
+
if (payload) {
|
|
42
|
+
options.headers["Content-Length"] = Buffer.byteLength(JSON.stringify(payload));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return makeRequest((resolve) => {
|
|
46
|
+
const httpRequest = httpClient.request(options, (response) => {
|
|
47
|
+
response.setEncoding("utf8");
|
|
48
|
+
unmountListener = createAbortListener(0, gqlExtra, response.destroy, resolve);
|
|
49
|
+
|
|
50
|
+
let chunks = "";
|
|
51
|
+
const totalDownloadBytes = Number(response.headers["content-length"]);
|
|
52
|
+
let downloadedBytes = 0;
|
|
53
|
+
|
|
54
|
+
onRequestStart();
|
|
55
|
+
|
|
56
|
+
response.on("data", (chunk) => {
|
|
57
|
+
if (!chunks) {
|
|
58
|
+
onRequestEnd();
|
|
59
|
+
onResponseStart();
|
|
60
|
+
}
|
|
61
|
+
downloadedBytes += chunk.length;
|
|
62
|
+
chunks += chunk;
|
|
63
|
+
onResponseProgress({ total: totalDownloadBytes, loaded: downloadedBytes });
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
response.on("end", () => {
|
|
67
|
+
const { statusCode } = response;
|
|
68
|
+
const success = String(statusCode).startsWith("2") || String(statusCode).startsWith("3");
|
|
69
|
+
|
|
70
|
+
if (success) {
|
|
71
|
+
const data = parseResponse(chunks);
|
|
72
|
+
onSuccess(data, statusCode, { headers: response.headers as Record<string, string> }, resolve);
|
|
73
|
+
} else {
|
|
74
|
+
// delay to finish after onabort/ontimeout
|
|
75
|
+
const data = parseErrorResponse(chunks);
|
|
76
|
+
onError(data, statusCode, { headers: response.headers as Record<string, string> }, resolve);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
unmountListener();
|
|
80
|
+
onResponseEnd();
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
httpRequest.on("timeout", () => onTimeoutError(0, gqlExtra, resolve));
|
|
85
|
+
httpRequest.on("error", (error) => onError(error, 0, gqlExtra, resolve));
|
|
86
|
+
if (payload) {
|
|
87
|
+
httpRequest.write(payload);
|
|
88
|
+
}
|
|
89
|
+
httpRequest.end();
|
|
90
|
+
});
|
|
91
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as browser from "@browser-adapter";
|
|
2
|
+
import * as server from "@server-adapter";
|
|
3
|
+
import { HttpMethodsEnum, ClientInstance } from "@hyper-fetch/core";
|
|
4
|
+
import { print } from "graphql/language/printer";
|
|
5
|
+
|
|
6
|
+
import { GraphQLAdapterType, GraphQlEndpointType } from "adapter";
|
|
7
|
+
|
|
8
|
+
export const graphqlAdapter = (client: ClientInstance) => {
|
|
9
|
+
const adapter: GraphQLAdapterType = async (request, requestId) => {
|
|
10
|
+
if (typeof XMLHttpRequest !== "undefined") {
|
|
11
|
+
return browser.adapter(request, requestId);
|
|
12
|
+
}
|
|
13
|
+
return server.adapter(request, requestId);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const mapper = <E extends GraphQlEndpointType>(endpoint: E): string => {
|
|
17
|
+
if (typeof endpoint === "string") {
|
|
18
|
+
return endpoint;
|
|
19
|
+
}
|
|
20
|
+
return print(endpoint);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
return client
|
|
24
|
+
.setDefaultMethod(HttpMethodsEnum.post)
|
|
25
|
+
.setEndpointMapper(mapper)
|
|
26
|
+
.setAdapter(() => adapter);
|
|
27
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { AdapterType, NegativeTypes, QueryParamsType } from "@hyper-fetch/core";
|
|
2
|
+
import { DocumentNode } from "graphql/language/ast";
|
|
3
|
+
|
|
4
|
+
export enum GraphQlMethod {
|
|
5
|
+
POST = "POST",
|
|
6
|
+
GET = "GET",
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type GraphQlExtraType = { headers: HeadersInit };
|
|
10
|
+
|
|
11
|
+
export type GraphQlEndpointType = string | DocumentNode;
|
|
12
|
+
|
|
13
|
+
export type GraphQLAdapterType = AdapterType<
|
|
14
|
+
Partial<XMLHttpRequest>,
|
|
15
|
+
GraphQlMethod,
|
|
16
|
+
number,
|
|
17
|
+
GraphQlExtraType,
|
|
18
|
+
QueryParamsType | string,
|
|
19
|
+
GraphQlEndpointType
|
|
20
|
+
>;
|
|
21
|
+
|
|
22
|
+
// Extract
|
|
23
|
+
|
|
24
|
+
type GqlParamType = boolean | string | number | Record<string, any> | NegativeTypes;
|
|
25
|
+
|
|
26
|
+
export type ExtractGqlRouteParams<T extends string> = string extends T
|
|
27
|
+
? NegativeTypes
|
|
28
|
+
: T extends `${string}query ${infer Parameters} {${infer QueryRest}`
|
|
29
|
+
? Parameters extends `${string}$${infer Param},${infer Rest}`
|
|
30
|
+
? GqlParameter<Param, Rest> &
|
|
31
|
+
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
32
|
+
(ExtractGqlRouteParams<QueryRest> extends NegativeTypes ? {} : ExtractGqlRouteParams<QueryRest>)
|
|
33
|
+
: Parameters extends `${string}$${infer Param})${infer Rest}`
|
|
34
|
+
? GqlParameter<Param, Rest> &
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
36
|
+
(ExtractGqlRouteParams<QueryRest> extends NegativeTypes ? {} : ExtractGqlRouteParams<QueryRest>)
|
|
37
|
+
: NegativeTypes
|
|
38
|
+
: T extends `${string}$${infer Parameters},${infer Rest}`
|
|
39
|
+
? GqlParameter<Parameters, Rest>
|
|
40
|
+
: T extends `${string}$${infer Parameters})${infer Rest}`
|
|
41
|
+
? GqlParameter<Parameters, Rest>
|
|
42
|
+
: NegativeTypes;
|
|
43
|
+
|
|
44
|
+
export type GqlParameter<T extends string, Rest extends string> = T extends `${infer Param}:${string}!`
|
|
45
|
+
? { [k in Param]: GqlParamType } & (ExtractGqlRouteParams<Rest> extends NegativeTypes
|
|
46
|
+
? // eslint-disable-next-line @typescript-eslint/ban-types
|
|
47
|
+
{}
|
|
48
|
+
: ExtractGqlRouteParams<Rest>)
|
|
49
|
+
: T extends `${infer Param}:${string}`
|
|
50
|
+
? { [k in Param]?: GqlParamType } & (ExtractGqlRouteParams<Rest> extends NegativeTypes
|
|
51
|
+
? // eslint-disable-next-line @typescript-eslint/ban-types
|
|
52
|
+
{}
|
|
53
|
+
: ExtractGqlRouteParams<Rest>)
|
|
54
|
+
: NegativeTypes;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { RequestInstance, HttpMethodsEnum } from "@hyper-fetch/core";
|
|
2
|
+
|
|
3
|
+
export const getRequestValues = (request: RequestInstance) => {
|
|
4
|
+
const { method } = request;
|
|
5
|
+
|
|
6
|
+
const isPostRequest = request.method === HttpMethodsEnum.post;
|
|
7
|
+
|
|
8
|
+
const query = request.endpoint;
|
|
9
|
+
const variables = request.data;
|
|
10
|
+
|
|
11
|
+
const fullUrl = isPostRequest
|
|
12
|
+
? request.client.url
|
|
13
|
+
: request.client.url + request.client.stringifyQueryParams({ query, variables });
|
|
14
|
+
|
|
15
|
+
const payload = isPostRequest
|
|
16
|
+
? JSON.stringify({
|
|
17
|
+
query,
|
|
18
|
+
variables,
|
|
19
|
+
})
|
|
20
|
+
: null;
|
|
21
|
+
|
|
22
|
+
return { fullUrl, payload, method };
|
|
23
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"rootDir": "./src",
|
|
5
|
+
"baseUrl": "./src",
|
|
6
|
+
"paths": {
|
|
7
|
+
"@browser-adapter": ["./adapter/adapter.browser.ts"],
|
|
8
|
+
"@server-adapter": ["./adapter/adapter.browser.ts"]
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
"include": ["./src/index.ts", "__tests__/*"],
|
|
12
|
+
"exclude": ["dist", "jest.config.ts"]
|
|
13
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"rootDir": ".",
|
|
5
|
+
"baseUrl": "./src",
|
|
6
|
+
"paths": {
|
|
7
|
+
"@browser-adapter": ["./adapter/adapter.browser.ts"],
|
|
8
|
+
"@server-adapter": ["./adapter/adapter.server.ts"]
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
"exclude": ["dist", "jest.config.ts"]
|
|
12
|
+
}
|