liaise 0.0.0 → 5.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/CHANGELOG.md +961 -0
- package/LICENSE +21 -0
- package/MIGRATION.md +925 -0
- package/README.md +1392 -4
- package/dist/built-in-middleware.d.ts +232 -0
- package/dist/built-in-middleware.js +127 -0
- package/dist/create-api.d.ts +120 -0
- package/dist/create-api.js +370 -0
- package/dist/define-request.d.ts +251 -0
- package/dist/define-request.js +4 -0
- package/dist/graphql.d.ts +30 -0
- package/dist/graphql.js +272 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +6 -0
- package/dist/middleware.d.ts +77 -0
- package/dist/middleware.js +12 -0
- package/dist/paginate.d.ts +71 -0
- package/dist/paginate.js +15 -0
- package/dist/request.d.ts +136 -0
- package/dist/request.js +12 -0
- package/dist/result.d.ts +178 -0
- package/dist/result.js +20 -0
- package/dist/testing.d.ts +51 -0
- package/dist/testing.js +135 -0
- package/dist/types.d.ts +751 -0
- package/dist/types.js +1 -0
- package/dist/utils/abort-kind.d.ts +58 -0
- package/dist/utils/abort-kind.js +28 -0
- package/dist/utils/any-signal.d.ts +18 -0
- package/dist/utils/any-signal.js +29 -0
- package/dist/utils/backstop.d.ts +49 -0
- package/dist/utils/backstop.js +80 -0
- package/dist/utils/budget.d.ts +53 -0
- package/dist/utils/budget.js +20 -0
- package/dist/utils/cache.d.ts +54 -0
- package/dist/utils/cache.js +37 -0
- package/dist/utils/dedupe.d.ts +90 -0
- package/dist/utils/dedupe.js +20 -0
- package/dist/utils/headers.d.ts +1 -0
- package/dist/utils/headers.js +19 -0
- package/dist/utils/path-params.d.ts +89 -0
- package/dist/utils/path-params.js +80 -0
- package/dist/utils/serialize.d.ts +48 -0
- package/dist/utils/serialize.js +21 -0
- package/dist/utils/share.d.ts +49 -0
- package/dist/utils/share.js +48 -0
- package/dist/utils/special-body.d.ts +18 -0
- package/dist/utils/special-body.js +7 -0
- package/dist/utils/stable-key.d.ts +55 -0
- package/dist/utils/stable-key.js +111 -0
- package/dist/utils/timeout.d.ts +27 -0
- package/dist/utils/timeout.js +7 -0
- package/dist/utils/validate.d.ts +32 -0
- package/dist/utils/validate.js +6 -0
- package/package.json +67 -5
package/dist/result.d.ts
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import type { Result, SuccessResult, ErrorResult } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* What category of failure an {@link ApiError} represents.
|
|
4
|
+
*
|
|
5
|
+
* Branch on this rather than on `status`: `'network'`, `'abort'` and
|
|
6
|
+
* `'timeout'` all carry `status: 0`, and they call for completely different
|
|
7
|
+
* handling — retry, ignore, and report respectively.
|
|
8
|
+
*
|
|
9
|
+
* `'parse'` is produced for a response body that failed to parse according to
|
|
10
|
+
* the request's `responseType`. Since 4.2.0 it also covers an optional
|
|
11
|
+
* `schema` on the request: a validator that refuses the body (`body` is its
|
|
12
|
+
* issues array) or one that throws (`body` is the thrown value).
|
|
13
|
+
*
|
|
14
|
+
* `'middleware'` means a middleware threw rather than the request itself
|
|
15
|
+
* failing — a bug in consumer code you would fix, not a transient failure
|
|
16
|
+
* you would retry.
|
|
17
|
+
*/
|
|
18
|
+
export type ApiErrorKind = 'http' | 'network' | 'abort' | 'timeout' | 'parse' | 'middleware';
|
|
19
|
+
/**
|
|
20
|
+
* Properties required to construct an ApiError instance.
|
|
21
|
+
*
|
|
22
|
+
* This interface exists to keep the ApiError constructor clean — instead of
|
|
23
|
+
* positional arguments, we use a single options object for clarity at call sites.
|
|
24
|
+
*/
|
|
25
|
+
interface ApiErrorOptions {
|
|
26
|
+
/** HTTP status code (e.g., 404, 500). Use 0 for network errors and aborts. */
|
|
27
|
+
status: number;
|
|
28
|
+
/** What category of failure this is. */
|
|
29
|
+
kind: ApiErrorKind;
|
|
30
|
+
/** HTTP status text (e.g., 'Not Found'). Use '' for network errors and aborts. */
|
|
31
|
+
statusText: string;
|
|
32
|
+
/**
|
|
33
|
+
* The parsed response body from the server.
|
|
34
|
+
*
|
|
35
|
+
* For HTTP errors, this is whatever the server returned (often a JSON error object).
|
|
36
|
+
* For network errors, this is the native Error that fetch threw.
|
|
37
|
+
* For aborted requests, this is a DOMException with name 'AbortError'.
|
|
38
|
+
*/
|
|
39
|
+
body: unknown;
|
|
40
|
+
/**
|
|
41
|
+
* Response headers from the server.
|
|
42
|
+
* For network errors (no response), this should be an empty Headers object.
|
|
43
|
+
*/
|
|
44
|
+
headers: Headers;
|
|
45
|
+
/**
|
|
46
|
+
* Metadata about the request that caused this error.
|
|
47
|
+
* Useful for logging, debugging, and error reporting.
|
|
48
|
+
*/
|
|
49
|
+
request: {
|
|
50
|
+
method: string;
|
|
51
|
+
url: string;
|
|
52
|
+
params: unknown;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Data the server returned alongside the errors.
|
|
56
|
+
*
|
|
57
|
+
* GraphQL allows partial success — a nullable field errors while the rest of
|
|
58
|
+
* the query resolves. That data lives here rather than in `Result.data` so
|
|
59
|
+
* the Result stays a clean discriminated union: `data` is non-null if and
|
|
60
|
+
* only if `error` is null.
|
|
61
|
+
*
|
|
62
|
+
* `undefined` for every REST error and for GraphQL responses carrying no data.
|
|
63
|
+
*/
|
|
64
|
+
partialData?: unknown;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Structured error returned in `Result.error` for both HTTP and network failures.
|
|
68
|
+
*
|
|
69
|
+
* This is NOT a subclass of `Error` — it's a plain class that stores error
|
|
70
|
+
* details in a structured way. This is intentional: ApiError represents an
|
|
71
|
+
* API-level error (not a programming error), and we don't need stack traces
|
|
72
|
+
* or error inheritance for that use case.
|
|
73
|
+
*
|
|
74
|
+
* Two categories of errors produce an ApiError:
|
|
75
|
+
*
|
|
76
|
+
* **HTTP errors** (server responded with 4xx or 5xx):
|
|
77
|
+
* - `status` is the HTTP status code (e.g., 404, 500)
|
|
78
|
+
* - `statusText` is the HTTP status text (e.g., 'Not Found')
|
|
79
|
+
* - `body` is the parsed response body (e.g., `{ message: 'Not found' }`)
|
|
80
|
+
* - `headers` contains the response headers
|
|
81
|
+
*
|
|
82
|
+
* **Network errors** (no HTTP response at all):
|
|
83
|
+
* - `status` is 0 (convention for "no HTTP status")
|
|
84
|
+
* - `statusText` is '' (empty string)
|
|
85
|
+
* - `body` is the native Error or DOMException that caused the failure
|
|
86
|
+
* - `headers` is an empty Headers object
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```ts
|
|
90
|
+
* const { error } = await api.getUser({ id: '42' })
|
|
91
|
+
* if (error) {
|
|
92
|
+
* if (error.status === 0) {
|
|
93
|
+
* // Network error — user is probably offline
|
|
94
|
+
* } else if (error.status === 404) {
|
|
95
|
+
* // User not found
|
|
96
|
+
* }
|
|
97
|
+
* }
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
export declare class ApiError {
|
|
101
|
+
/** HTTP status code, or 0 for network errors and aborted requests. */
|
|
102
|
+
readonly status: number;
|
|
103
|
+
/** What category of failure this is. */
|
|
104
|
+
readonly kind: ApiErrorKind;
|
|
105
|
+
/** HTTP status text, or '' for network errors and aborted requests. */
|
|
106
|
+
readonly statusText: string;
|
|
107
|
+
/**
|
|
108
|
+
* Parsed response body, Error for network failures, DOMException for aborts.
|
|
109
|
+
* The type is `unknown` because the server can return anything.
|
|
110
|
+
*/
|
|
111
|
+
readonly body: unknown;
|
|
112
|
+
/**
|
|
113
|
+
* Response headers. Empty Headers object when no HTTP response exists
|
|
114
|
+
* (network errors, aborts).
|
|
115
|
+
*/
|
|
116
|
+
readonly headers: Headers;
|
|
117
|
+
/**
|
|
118
|
+
* Metadata about the request that caused this error.
|
|
119
|
+
* Includes the method, fully resolved URL, and the original params object.
|
|
120
|
+
*/
|
|
121
|
+
readonly request: {
|
|
122
|
+
method: string;
|
|
123
|
+
url: string;
|
|
124
|
+
params: unknown;
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* Data the server returned alongside the errors.
|
|
128
|
+
*
|
|
129
|
+
* GraphQL allows partial success — a nullable field errors while the rest of
|
|
130
|
+
* the query resolves. That data lives here rather than in `Result.data` so
|
|
131
|
+
* the Result stays a clean discriminated union: `data` is non-null if and
|
|
132
|
+
* only if `error` is null.
|
|
133
|
+
*
|
|
134
|
+
* `undefined` for every REST error and for GraphQL responses carrying no data.
|
|
135
|
+
*/
|
|
136
|
+
readonly partialData?: unknown;
|
|
137
|
+
constructor(options: ApiErrorOptions);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Create a successful Result with parsed response data.
|
|
141
|
+
*
|
|
142
|
+
* Used when the HTTP response has a 2xx status code and the body was
|
|
143
|
+
* successfully parsed according to the request's `responseType`.
|
|
144
|
+
*
|
|
145
|
+
* @typeParam TResponse - The type of the parsed response data.
|
|
146
|
+
* @param data - The parsed response data.
|
|
147
|
+
* @param response - The raw HTTP Response object (for status, headers, etc.).
|
|
148
|
+
* @param retry - Function to re-execute the request from the outermost middleware.
|
|
149
|
+
* @returns A Result with `data` populated and `error` as null.
|
|
150
|
+
*/
|
|
151
|
+
export declare function createSuccessResult<TResponse>(data: TResponse, response: Response, retry: () => Promise<Result<TResponse>>): SuccessResult<TResponse>;
|
|
152
|
+
/**
|
|
153
|
+
* Create an error Result for HTTP errors (server responded, but with an error status).
|
|
154
|
+
*
|
|
155
|
+
* Used when the HTTP response has a non-2xx status code (4xx, 5xx).
|
|
156
|
+
* The raw Response is still available because the server DID respond — the
|
|
157
|
+
* caller might need response headers or want to inspect the raw body.
|
|
158
|
+
*
|
|
159
|
+
* @typeParam TResponse - The expected response type (data will be null).
|
|
160
|
+
* @param error - The structured ApiError with status, body, and request metadata.
|
|
161
|
+
* @param response - The raw HTTP Response object.
|
|
162
|
+
* @param retry - Function to re-execute the request from the outermost middleware.
|
|
163
|
+
* @returns A Result with `error` populated and `data` as null.
|
|
164
|
+
*/
|
|
165
|
+
export declare function createErrorResult<TResponse>(error: ApiError, response: Response, retry: () => Promise<Result<TResponse>>): ErrorResult<TResponse>;
|
|
166
|
+
/**
|
|
167
|
+
* Create an error Result for network failures (no HTTP response exists).
|
|
168
|
+
*
|
|
169
|
+
* Used when fetch itself throws — DNS failure, offline, CORS error, abort, etc.
|
|
170
|
+
* The `response` field is null because no HTTP response was received.
|
|
171
|
+
*
|
|
172
|
+
* @typeParam TResponse - The expected response type (data will be null).
|
|
173
|
+
* @param error - The structured ApiError (status 0, body is the native Error).
|
|
174
|
+
* @param retry - Function to re-execute the request from the outermost middleware.
|
|
175
|
+
* @returns A Result with `error` populated and both `data` and `response` as null.
|
|
176
|
+
*/
|
|
177
|
+
export declare function createNetworkErrorResult<TResponse>(error: ApiError, retry: () => Promise<Result<TResponse>>): ErrorResult<TResponse>;
|
|
178
|
+
export {};
|
package/dist/result.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export class ApiError {
|
|
2
|
+
constructor(options) {
|
|
3
|
+
this.status = options.status;
|
|
4
|
+
this.kind = options.kind;
|
|
5
|
+
this.statusText = options.statusText;
|
|
6
|
+
this.body = options.body;
|
|
7
|
+
this.headers = options.headers;
|
|
8
|
+
this.request = options.request;
|
|
9
|
+
this.partialData = options.partialData;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export function createSuccessResult(data, response, retry) {
|
|
13
|
+
return { data, error: null, response, retry };
|
|
14
|
+
}
|
|
15
|
+
export function createErrorResult(error, response, retry) {
|
|
16
|
+
return { data: null, error, response, retry };
|
|
17
|
+
}
|
|
18
|
+
export function createNetworkErrorResult(error, retry) {
|
|
19
|
+
return { data: null, error, response: null, retry };
|
|
20
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { Result } from './types.js';
|
|
2
|
+
/** A JSON response with the right content-type, for use as a route value. */
|
|
3
|
+
export declare function jsonResponse(body: unknown, init?: ResponseInit): Response;
|
|
4
|
+
export interface RouteContext {
|
|
5
|
+
params: Record<string, string>;
|
|
6
|
+
request: Request;
|
|
7
|
+
}
|
|
8
|
+
export type RouteHandler = (ctx: RouteContext) => Response | Promise<Response>;
|
|
9
|
+
export type RouteValue = Response | RouteHandler | Array<Response | RouteHandler>;
|
|
10
|
+
export interface RecordedCall {
|
|
11
|
+
method: string;
|
|
12
|
+
url: string;
|
|
13
|
+
headers: Headers;
|
|
14
|
+
body: unknown;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* A `fetch` stub that routes by `"METHOD /path"`, with `:token` capture.
|
|
18
|
+
*
|
|
19
|
+
* Stubs at the fetch boundary rather than the api boundary on purpose: URL
|
|
20
|
+
* building, path substitution, header merging, body serialization, response
|
|
21
|
+
* parsing and the consumer's own middleware all stay live. A test that passes
|
|
22
|
+
* while a `path: '/users/:userId'` typo ships is not doing its job.
|
|
23
|
+
*
|
|
24
|
+
* Framework-agnostic — `fetch` is a plain function, so install it whichever
|
|
25
|
+
* way your runner prefers.
|
|
26
|
+
*
|
|
27
|
+
* Honours `init.signal` as real `fetch` does: an already-aborted signal
|
|
28
|
+
* rejects with its `reason`, and so does one that aborts while a handler is
|
|
29
|
+
* still pending. That is what lets a consumer test their own timeout and
|
|
30
|
+
* cancellation handling through the stub. An aborted call is still recorded
|
|
31
|
+
* in `calls` and counted by `callCount`, but does not advance a response
|
|
32
|
+
* sequence.
|
|
33
|
+
*/
|
|
34
|
+
export declare function mockFetch(routes: Record<string, RouteValue>): {
|
|
35
|
+
fetch: typeof globalThis.fetch;
|
|
36
|
+
calls: RecordedCall[];
|
|
37
|
+
callCount: (key: string) => number;
|
|
38
|
+
lastCall: (key: string) => RecordedCall | undefined;
|
|
39
|
+
install(): void;
|
|
40
|
+
restore(): void;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* A success `Result`, for consumers stubbing at the api level.
|
|
44
|
+
*
|
|
45
|
+
* Worth using rather than hand-rolling `{ data, error: null, ... }`: 3.0.0
|
|
46
|
+
* turns `Result` into a discriminated union where `response` is non-null on
|
|
47
|
+
* success, so a hand-rolled literal breaks on that upgrade and this does not.
|
|
48
|
+
*/
|
|
49
|
+
export declare function successResult<T>(data: T, init?: ResponseInit): Result<T>;
|
|
50
|
+
/** An error `Result` with the given HTTP status. */
|
|
51
|
+
export declare function errorResult<T>(status: number, body?: unknown): Result<T>;
|
package/dist/testing.js
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { createSuccessResult, createErrorResult, ApiError } from './result.js';
|
|
2
|
+
export function jsonResponse(body, init = {}) {
|
|
3
|
+
const headers = new Headers(init.headers);
|
|
4
|
+
if (!headers.has('content-type'))
|
|
5
|
+
headers.set('content-type', 'application/json');
|
|
6
|
+
return new Response(body === null ? '' : JSON.stringify(body), { ...init, headers });
|
|
7
|
+
}
|
|
8
|
+
function matchPath(routeSegments, pathname) {
|
|
9
|
+
const actual = pathname.split('/').filter(Boolean);
|
|
10
|
+
if (actual.length !== routeSegments.length)
|
|
11
|
+
return null;
|
|
12
|
+
const params = {};
|
|
13
|
+
for (let i = 0; i < routeSegments.length; i++) {
|
|
14
|
+
const expected = routeSegments[i];
|
|
15
|
+
if (expected.startsWith(':')) {
|
|
16
|
+
params[expected.slice(1)] = decodeURIComponent(actual[i]);
|
|
17
|
+
}
|
|
18
|
+
else if (expected !== actual[i]) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return params;
|
|
23
|
+
}
|
|
24
|
+
function abortable(pending, signal) {
|
|
25
|
+
if (!signal)
|
|
26
|
+
return pending;
|
|
27
|
+
return new Promise((resolve, reject) => {
|
|
28
|
+
const onAbort = () => reject(signal.reason);
|
|
29
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
30
|
+
pending.then(value => { signal.removeEventListener('abort', onAbort); resolve(value); }, (err) => { signal.removeEventListener('abort', onAbort); reject(err); });
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
export function mockFetch(routes) {
|
|
34
|
+
const parsed = Object.entries(routes).map(([key, value]) => {
|
|
35
|
+
const spaceAt = key.indexOf(' ');
|
|
36
|
+
if (spaceAt === -1) {
|
|
37
|
+
throw new Error(`mockFetch: route key "${key}" must be "METHOD /path" — ` +
|
|
38
|
+
`an HTTP method, a space, then the path (e.g. "GET /users/:id").`);
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
key,
|
|
42
|
+
method: key.slice(0, spaceAt).toUpperCase(),
|
|
43
|
+
segments: key.slice(spaceAt + 1).split('/').filter(Boolean),
|
|
44
|
+
value,
|
|
45
|
+
cursor: 0,
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
const calls = [];
|
|
49
|
+
const matchedKeys = [];
|
|
50
|
+
let previousFetch;
|
|
51
|
+
let installed = false;
|
|
52
|
+
const resolveValue = (route) => {
|
|
53
|
+
if (!Array.isArray(route.value))
|
|
54
|
+
return route.value;
|
|
55
|
+
if (route.value.length === 0) {
|
|
56
|
+
throw new Error(`mockFetch: route "${route.key}" has an empty response array.`);
|
|
57
|
+
}
|
|
58
|
+
const index = Math.min(route.cursor, route.value.length - 1);
|
|
59
|
+
route.cursor++;
|
|
60
|
+
return route.value[index];
|
|
61
|
+
};
|
|
62
|
+
const mockedFetch = async (input, init = {}) => {
|
|
63
|
+
const url = typeof input === 'string' ? input : input instanceof URL ? input.href : input.url;
|
|
64
|
+
const method = (init.method ?? 'GET').toUpperCase();
|
|
65
|
+
const pathname = url.startsWith('http') ? new URL(url).pathname : url.split('?')[0];
|
|
66
|
+
calls.push({
|
|
67
|
+
method,
|
|
68
|
+
url,
|
|
69
|
+
headers: new Headers(init.headers),
|
|
70
|
+
body: init.body ?? null,
|
|
71
|
+
});
|
|
72
|
+
const signal = init.signal;
|
|
73
|
+
for (const route of parsed) {
|
|
74
|
+
if (route.method !== method)
|
|
75
|
+
continue;
|
|
76
|
+
const params = matchPath(route.segments, pathname);
|
|
77
|
+
if (!params)
|
|
78
|
+
continue;
|
|
79
|
+
matchedKeys.push(route.key);
|
|
80
|
+
if (signal?.aborted)
|
|
81
|
+
throw signal.reason;
|
|
82
|
+
const value = resolveValue(route);
|
|
83
|
+
if (typeof value !== 'function')
|
|
84
|
+
return value.clone();
|
|
85
|
+
const absoluteUrl = url.startsWith('http') ? url : new URL(url, 'http://localhost').href;
|
|
86
|
+
const request = new Request(absoluteUrl, init);
|
|
87
|
+
Object.defineProperty(request, 'url', { get: () => url });
|
|
88
|
+
return abortable(Promise.resolve(value({ params, request })), signal);
|
|
89
|
+
}
|
|
90
|
+
matchedKeys.push('');
|
|
91
|
+
throw new Error(`mockFetch: no route matched ${method} ${url}. ` +
|
|
92
|
+
`Defined routes: ${parsed.map(r => r.key).join(', ') || '(none)'}`);
|
|
93
|
+
};
|
|
94
|
+
return {
|
|
95
|
+
fetch: mockedFetch,
|
|
96
|
+
calls,
|
|
97
|
+
callCount: (key) => matchedKeys.filter(k => k === key).length,
|
|
98
|
+
lastCall: (key) => {
|
|
99
|
+
for (let i = matchedKeys.length - 1; i >= 0; i--) {
|
|
100
|
+
if (matchedKeys[i] === key)
|
|
101
|
+
return calls[i];
|
|
102
|
+
}
|
|
103
|
+
return undefined;
|
|
104
|
+
},
|
|
105
|
+
install() {
|
|
106
|
+
if (installed)
|
|
107
|
+
return;
|
|
108
|
+
previousFetch = globalThis.fetch;
|
|
109
|
+
installed = true;
|
|
110
|
+
globalThis.fetch = mockedFetch;
|
|
111
|
+
},
|
|
112
|
+
restore() {
|
|
113
|
+
if (!installed)
|
|
114
|
+
return;
|
|
115
|
+
globalThis.fetch = previousFetch;
|
|
116
|
+
installed = false;
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
export function successResult(data, init = {}) {
|
|
121
|
+
const result = createSuccessResult(data, jsonResponse(data, init), async () => result);
|
|
122
|
+
return result;
|
|
123
|
+
}
|
|
124
|
+
export function errorResult(status, body = null) {
|
|
125
|
+
const error = new ApiError({
|
|
126
|
+
kind: 'http',
|
|
127
|
+
status,
|
|
128
|
+
statusText: '',
|
|
129
|
+
body,
|
|
130
|
+
headers: new Headers(),
|
|
131
|
+
request: { method: 'GET', url: '', params: {} },
|
|
132
|
+
});
|
|
133
|
+
const result = createErrorResult(error, jsonResponse(body, { status }), async () => result);
|
|
134
|
+
return result;
|
|
135
|
+
}
|