@fleet-sdk/blockchain-providers 0.5.0 → 0.6.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/CHANGELOG.md +12 -0
- package/dist/index.d.mts +124 -33
- package/dist/index.d.ts +124 -33
- package/dist/index.js +241 -61
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +241 -61
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/ergo-graphql/ergoGraphQLProvider.ts +256 -86
- package/src/ergo-graphql/queries.ts +14 -5
- package/src/types/blockchainProvider.ts +101 -18
- package/src/utils/_tests.ts +6 -10
- package/src/utils/graphql.ts +54 -62
- package/src/utils/networking.ts +84 -0
package/src/utils/_tests.ts
CHANGED
@@ -1,12 +1,8 @@
|
|
1
|
-
export const
|
2
|
-
|
1
|
+
// export const mockSuccessResponse = (data: unknown) => resolveString(JSON.stringify(data));
|
2
|
+
|
3
|
+
export const resolveString = (data: string) =>
|
4
|
+
({
|
3
5
|
text: () => new Promise((resolve) => resolve(data))
|
4
|
-
} as unknown as Response;
|
5
|
-
};
|
6
|
+
}) as unknown as Response;
|
6
7
|
|
7
|
-
export const
|
8
|
-
let i = 0;
|
9
|
-
return {
|
10
|
-
text: () => new Promise((resolve) => resolve(chunks[i++]))
|
11
|
-
} as unknown as Response;
|
12
|
-
};
|
8
|
+
export const resolveData = (data: unknown) => resolveString(JSON.stringify(data));
|
package/src/utils/graphql.ts
CHANGED
@@ -5,17 +5,15 @@ import {
|
|
5
5
|
isEmpty,
|
6
6
|
some
|
7
7
|
} from "@fleet-sdk/common";
|
8
|
+
import type { FallbackRetryOptions, ParserLike } from "./networking";
|
9
|
+
import { request } from "./networking";
|
8
10
|
|
9
11
|
const OP_NAME_REGEX = /(query|mutation)\s?([\w\-_]+)?/;
|
10
|
-
export const DEFAULT_HEADERS
|
12
|
+
export const DEFAULT_HEADERS = {
|
11
13
|
"content-type": "application/json; charset=utf-8",
|
12
14
|
accept: "application/graphql-response+json, application/json"
|
13
15
|
};
|
14
16
|
|
15
|
-
type Credentials = RequestCredentials;
|
16
|
-
type Headers = HeadersInit;
|
17
|
-
type Fetcher = typeof fetch;
|
18
|
-
|
19
17
|
export type GraphQLVariables = Record<string, unknown> | null;
|
20
18
|
|
21
19
|
export interface GraphQLError {
|
@@ -36,84 +34,80 @@ export type GraphQLResponse<T = unknown> =
|
|
36
34
|
| GraphQLSuccessResponse<T>
|
37
35
|
| GraphQLErrorResponse;
|
38
36
|
|
39
|
-
export type GraphQLOperation<
|
37
|
+
export type GraphQLOperation<R extends GraphQLResponse, V extends GraphQLVariables> = (
|
38
|
+
variables?: V,
|
39
|
+
url?: string
|
40
|
+
) => Promise<R>;
|
41
|
+
|
42
|
+
export type GraphQLRequiredUrlOperation<
|
40
43
|
R extends GraphQLResponse,
|
41
44
|
V extends GraphQLVariables
|
42
|
-
> = (variables
|
45
|
+
> = (variables: V | undefined, url: string) => Promise<R>;
|
43
46
|
|
44
|
-
|
45
|
-
parse<T>(text: string): T;
|
46
|
-
stringify<T>(value: T): string;
|
47
|
-
}
|
48
|
-
|
49
|
-
export interface RequestParams {
|
47
|
+
interface RequestParams {
|
50
48
|
operationName?: string | null;
|
51
49
|
query: string;
|
52
50
|
variables?: Record<string, unknown> | null;
|
53
51
|
}
|
54
52
|
|
55
53
|
export interface GraphQLRequestOptions {
|
56
|
-
url
|
57
|
-
|
58
|
-
|
59
|
-
fetcher?: Fetcher;
|
60
|
-
credentials?: Credentials;
|
54
|
+
url?: string;
|
55
|
+
parser?: ParserLike;
|
56
|
+
retry?: FallbackRetryOptions;
|
61
57
|
throwOnNonNetworkErrors?: boolean;
|
58
|
+
httpOptions?: Omit<RequestInit, "body" | "method">;
|
62
59
|
}
|
63
60
|
|
64
|
-
export
|
65
|
-
throwOnNonNetworkErrors: true;
|
66
|
-
}
|
67
|
-
|
68
|
-
export function createGqlOperation<
|
69
|
-
R,
|
70
|
-
V extends GraphQLVariables = GraphQLVariables
|
71
|
-
>(
|
61
|
+
export function createGqlOperation<R, V extends GraphQLVariables = GraphQLVariables>(
|
72
62
|
query: string,
|
73
|
-
options:
|
63
|
+
options: GraphQLRequestOptions & { throwOnNonNetworkErrors: true }
|
74
64
|
): GraphQLOperation<GraphQLSuccessResponse<R>, V>;
|
75
|
-
export function createGqlOperation<
|
76
|
-
|
77
|
-
|
78
|
-
|
65
|
+
export function createGqlOperation<R, V extends GraphQLVariables = GraphQLVariables>(
|
66
|
+
query: string,
|
67
|
+
options?: GraphQLRequestOptions & { url: undefined }
|
68
|
+
): GraphQLRequiredUrlOperation<GraphQLResponse<R>, V>;
|
69
|
+
export function createGqlOperation<R, V extends GraphQLVariables = GraphQLVariables>(
|
70
|
+
query: string,
|
71
|
+
options: GraphQLRequestOptions & { url: undefined; throwOnNonNetworkErrors: true }
|
72
|
+
): GraphQLRequiredUrlOperation<GraphQLSuccessResponse<R>, V>;
|
73
|
+
export function createGqlOperation<R, V extends GraphQLVariables = GraphQLVariables>(
|
79
74
|
query: string,
|
80
75
|
options: GraphQLRequestOptions
|
81
76
|
): GraphQLOperation<GraphQLResponse<R>, V>;
|
82
|
-
export function createGqlOperation<
|
83
|
-
R,
|
84
|
-
V extends GraphQLVariables = GraphQLVariables
|
85
|
-
>(
|
77
|
+
export function createGqlOperation<R, V extends GraphQLVariables = GraphQLVariables>(
|
86
78
|
query: string,
|
87
|
-
options
|
88
|
-
):
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
79
|
+
options?: GraphQLRequestOptions
|
80
|
+
):
|
81
|
+
| GraphQLOperation<GraphQLResponse<R>, V>
|
82
|
+
| GraphQLRequiredUrlOperation<GraphQLResponse<R>, V> {
|
83
|
+
return async (variables?: V, url?: string): Promise<GraphQLResponse<R>> => {
|
84
|
+
url = url ?? options?.url;
|
85
|
+
if (!url) throw new Error("URL is required");
|
86
|
+
|
87
|
+
const response = await request<GraphQLResponse<R>>(url, {
|
88
|
+
...options,
|
89
|
+
httpOptions: {
|
90
|
+
...options?.httpOptions,
|
91
|
+
method: "POST",
|
92
|
+
headers: ensureDefaults(options?.httpOptions?.headers, DEFAULT_HEADERS),
|
93
|
+
body: (options?.parser ?? JSON).stringify({
|
94
|
+
operationName: getOpName(query),
|
95
|
+
query,
|
96
|
+
variables: variables ? clearUndefined(variables) : undefined
|
97
|
+
} as RequestParams)
|
98
|
+
}
|
99
99
|
});
|
100
100
|
|
101
|
-
const rawData = await response.text();
|
102
|
-
const parsedData = (options.parser ?? JSON).parse(
|
103
|
-
rawData
|
104
|
-
) as GraphQLResponse<R>;
|
105
|
-
|
106
101
|
if (
|
107
|
-
options
|
108
|
-
some(
|
109
|
-
isEmpty(
|
102
|
+
options?.throwOnNonNetworkErrors &&
|
103
|
+
some(response.errors) &&
|
104
|
+
isEmpty(response.data)
|
110
105
|
) {
|
111
|
-
|
112
|
-
|
113
|
-
});
|
106
|
+
const msg = response.errors[0].message;
|
107
|
+
throw new BlockchainProviderError(msg, { cause: response.errors });
|
114
108
|
}
|
115
109
|
|
116
|
-
return
|
110
|
+
return response;
|
117
111
|
};
|
118
112
|
}
|
119
113
|
|
@@ -126,7 +120,5 @@ export function getOpName(query: string): string | undefined {
|
|
126
120
|
}
|
127
121
|
|
128
122
|
export function isRequestParam(obj: unknown): obj is GraphQLRequestOptions {
|
129
|
-
return (
|
130
|
-
typeof obj === "object" && (obj as GraphQLRequestOptions).url !== undefined
|
131
|
-
);
|
123
|
+
return typeof obj === "object" && (obj as GraphQLRequestOptions).url !== undefined;
|
132
124
|
}
|
@@ -0,0 +1,84 @@
|
|
1
|
+
import { some } from "@fleet-sdk/common";
|
2
|
+
import { isEmpty } from "packages/common/src";
|
3
|
+
|
4
|
+
export interface ParserLike {
|
5
|
+
parse<T>(text: string): T;
|
6
|
+
stringify<T>(value: T): string;
|
7
|
+
}
|
8
|
+
|
9
|
+
export type Route = { base: string; path: string; query?: Record<string, unknown> };
|
10
|
+
export type URLLike = string | Route;
|
11
|
+
export type FallbackRetryOptions = { fallbacks?: URLLike[] } & RetryOptions;
|
12
|
+
|
13
|
+
export type FetchOptions = {
|
14
|
+
parser?: ParserLike;
|
15
|
+
base?: string;
|
16
|
+
query?: Record<string, unknown>;
|
17
|
+
retry?: FallbackRetryOptions;
|
18
|
+
httpOptions?: RequestInit;
|
19
|
+
};
|
20
|
+
|
21
|
+
export async function request<T>(path: string, opt?: Partial<FetchOptions>): Promise<T> {
|
22
|
+
const url = buildURL(path, opt?.query, opt?.base);
|
23
|
+
|
24
|
+
let response: Response;
|
25
|
+
if (opt?.retry) {
|
26
|
+
const routes = some(opt.retry.fallbacks) ? [url, ...opt.retry.fallbacks] : [url];
|
27
|
+
const attempts = opt.retry.attempts;
|
28
|
+
response = await exponentialRetry(
|
29
|
+
(r) => fetch(resolveUrl(routes, attempts - r), opt.httpOptions),
|
30
|
+
opt.retry
|
31
|
+
);
|
32
|
+
} else {
|
33
|
+
response = await fetch(url, opt?.httpOptions);
|
34
|
+
}
|
35
|
+
|
36
|
+
return (opt?.parser || JSON).parse(await response.text());
|
37
|
+
}
|
38
|
+
|
39
|
+
function resolveUrl(routes: URLLike[], attempt: number) {
|
40
|
+
const route = routes[attempt % routes.length];
|
41
|
+
return typeof route === "string"
|
42
|
+
? route
|
43
|
+
: buildURL(route.path, route.query, route.base).toString();
|
44
|
+
}
|
45
|
+
|
46
|
+
function buildURL(path: string, query?: Record<string, unknown>, base?: string) {
|
47
|
+
if (!base && !query) return path;
|
48
|
+
|
49
|
+
const url = new URL(path, base);
|
50
|
+
if (some(query)) {
|
51
|
+
for (const key in query) url.searchParams.append(key, String(query[key]));
|
52
|
+
}
|
53
|
+
|
54
|
+
return url.toString();
|
55
|
+
}
|
56
|
+
|
57
|
+
export type RetryOptions = {
|
58
|
+
attempts: number;
|
59
|
+
delay: number;
|
60
|
+
};
|
61
|
+
|
62
|
+
/**
|
63
|
+
* Retries an asynchronous operation a specified number of times with a delay
|
64
|
+
* growing exponentially between each attempt.
|
65
|
+
* @param operation - The asynchronous operation to retry.
|
66
|
+
* @param options - The retry options.
|
67
|
+
* @returns A promise that resolves to the result of the operation, or undefined
|
68
|
+
* if all attempts fail.
|
69
|
+
*/
|
70
|
+
export async function exponentialRetry<T>(
|
71
|
+
operation: (remainingAttempts: number) => Promise<T>,
|
72
|
+
{ attempts, delay }: RetryOptions
|
73
|
+
): Promise<T> {
|
74
|
+
try {
|
75
|
+
return await operation(attempts);
|
76
|
+
} catch (e) {
|
77
|
+
if (attempts > 0) {
|
78
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
79
|
+
return exponentialRetry(operation, { attempts: attempts - 1, delay: delay * 2 });
|
80
|
+
}
|
81
|
+
|
82
|
+
throw e;
|
83
|
+
}
|
84
|
+
}
|