@datocms/rest-client-utils 5.2.8 → 5.7.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/dist/cjs/__tests__/buildNormalizedParams.test.js +11 -11
- package/dist/cjs/__tests__/buildNormalizedParams.test.js.map +1 -1
- package/dist/cjs/__tests__/errors.test.js +121 -0
- package/dist/cjs/__tests__/errors.test.js.map +1 -0
- package/dist/cjs/errors.js +55 -1
- package/dist/cjs/errors.js.map +1 -1
- package/dist/cjs/index.js +10 -10
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/makeCancelablePromise.js +28 -0
- package/dist/cjs/makeCancelablePromise.js.map +1 -1
- package/dist/cjs/pollJobResult.js +4 -4
- package/dist/cjs/pollJobResult.js.map +1 -1
- package/dist/cjs/rawPageIterator.js +2 -2
- package/dist/cjs/rawPageIterator.js.map +1 -1
- package/dist/cjs/request.js +14 -14
- package/dist/cjs/request.js.map +1 -1
- package/dist/esm/__tests__/buildNormalizedParams.test.js +1 -1
- package/dist/esm/__tests__/buildNormalizedParams.test.js.map +1 -1
- package/dist/esm/__tests__/errors.test.d.ts +1 -0
- package/dist/esm/__tests__/errors.test.js +119 -0
- package/dist/esm/__tests__/errors.test.js.map +1 -0
- package/dist/esm/errors.d.ts +16 -0
- package/dist/esm/errors.js +55 -1
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/index.d.ts +10 -10
- package/dist/esm/index.js +10 -10
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/makeCancelablePromise.d.ts +9 -0
- package/dist/esm/makeCancelablePromise.js +28 -0
- package/dist/esm/makeCancelablePromise.js.map +1 -1
- package/dist/esm/package.json +1 -0
- package/dist/esm/pollJobResult.d.ts +1 -1
- package/dist/esm/pollJobResult.js +2 -2
- package/dist/esm/pollJobResult.js.map +1 -1
- package/dist/esm/rawPageIterator.js +1 -1
- package/dist/esm/rawPageIterator.js.map +1 -1
- package/dist/esm/request.d.ts +1 -1
- package/dist/esm/request.js +4 -4
- package/dist/esm/request.js.map +1 -1
- package/dist/types/__tests__/errors.test.d.ts +1 -0
- package/dist/types/errors.d.ts +16 -0
- package/dist/types/index.d.ts +10 -10
- package/dist/types/makeCancelablePromise.d.ts +9 -0
- package/dist/types/pollJobResult.d.ts +1 -1
- package/dist/types/request.d.ts +1 -1
- package/package.json +13 -3
- package/src/__tests__/buildNormalizedParams.test.ts +1 -1
- package/src/__tests__/errors.test.ts +141 -0
- package/src/errors.ts +74 -1
- package/src/index.ts +10 -10
- package/src/makeCancelablePromise.ts +41 -0
- package/src/pollJobResult.ts +3 -3
- package/src/rawPageIterator.ts +1 -1
- package/src/request.ts +5 -5
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { ApiError, TimeoutError } from '../errors';
|
|
2
|
+
import { CanceledPromiseError } from '../makeCancelablePromise';
|
|
3
|
+
|
|
4
|
+
function buildApiError() {
|
|
5
|
+
return new ApiError({
|
|
6
|
+
request: {
|
|
7
|
+
url: '/items/bad-id',
|
|
8
|
+
method: 'GET',
|
|
9
|
+
headers: {},
|
|
10
|
+
body: undefined,
|
|
11
|
+
},
|
|
12
|
+
response: {
|
|
13
|
+
status: 422,
|
|
14
|
+
statusText: 'Unprocessable Entity',
|
|
15
|
+
headers: {},
|
|
16
|
+
body: {
|
|
17
|
+
data: [
|
|
18
|
+
{
|
|
19
|
+
id: '1',
|
|
20
|
+
type: 'api_error' as const,
|
|
21
|
+
attributes: {
|
|
22
|
+
code: 'INVALID_FIELD',
|
|
23
|
+
doc_url: 'https://www.datocms.com/docs',
|
|
24
|
+
details: { field: 'item_id' },
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function buildTimeoutError() {
|
|
34
|
+
return new TimeoutError({
|
|
35
|
+
request: { url: '/items', method: 'GET', headers: {}, body: undefined },
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
describe('ApiError', () => {
|
|
40
|
+
it('keeps a correct prototype chain and name', () => {
|
|
41
|
+
const error = buildApiError();
|
|
42
|
+
|
|
43
|
+
expect(error).toBeInstanceOf(ApiError);
|
|
44
|
+
expect(error).toBeInstanceOf(Error);
|
|
45
|
+
expect(error.name).toBe('ApiError');
|
|
46
|
+
expect(error.constructor.name).toBe('ApiError');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('exposes request, response and parsed errors', () => {
|
|
50
|
+
const error = buildApiError();
|
|
51
|
+
|
|
52
|
+
expect(error.response.status).toBe(422);
|
|
53
|
+
expect(error.request.url).toBe('/items/bad-id');
|
|
54
|
+
expect(error.errors).toHaveLength(1);
|
|
55
|
+
expect(error.findError('INVALID_FIELD')).toBeTruthy();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// These packages ship parallel CJS and ESM builds, so a bundler can load two
|
|
59
|
+
// distinct copies of this module. `instanceof` must still work for an error
|
|
60
|
+
// produced by the other copy.
|
|
61
|
+
it('recognizes an instance coming from a duplicate copy of the module', () => {
|
|
62
|
+
const fromOtherCopy = Object.assign(new Error('API Error!'), {
|
|
63
|
+
name: 'ApiError',
|
|
64
|
+
[Symbol.for('@datocms/rest-client-utils:ApiError')]: true,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
expect(fromOtherCopy).toBeInstanceOf(ApiError);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('does not recognize unrelated values', () => {
|
|
71
|
+
expect(new Error('nope')).not.toBeInstanceOf(ApiError);
|
|
72
|
+
expect({}).not.toBeInstanceOf(ApiError);
|
|
73
|
+
expect(null).not.toBeInstanceOf(ApiError);
|
|
74
|
+
expect(buildTimeoutError()).not.toBeInstanceOf(ApiError);
|
|
75
|
+
// merely borrowing the name is not enough
|
|
76
|
+
expect(
|
|
77
|
+
Object.assign(new Error('impostor'), { name: 'ApiError' }),
|
|
78
|
+
).not.toBeInstanceOf(ApiError);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('keeps subclass checks exact', () => {
|
|
82
|
+
class SubclassedApiError extends ApiError {}
|
|
83
|
+
|
|
84
|
+
const parent = buildApiError();
|
|
85
|
+
const child = new SubclassedApiError({
|
|
86
|
+
request: { url: '/items', method: 'GET', headers: {}, body: undefined },
|
|
87
|
+
response: { status: 500, statusText: 'Error', headers: {}, body: {} },
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
expect(child).toBeInstanceOf(SubclassedApiError);
|
|
91
|
+
expect(child).toBeInstanceOf(ApiError);
|
|
92
|
+
expect(parent).not.toBeInstanceOf(SubclassedApiError);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe('TimeoutError', () => {
|
|
97
|
+
it('keeps a correct prototype chain and name', () => {
|
|
98
|
+
const error = buildTimeoutError();
|
|
99
|
+
|
|
100
|
+
expect(error).toBeInstanceOf(TimeoutError);
|
|
101
|
+
expect(error).toBeInstanceOf(Error);
|
|
102
|
+
expect(error.name).toBe('TimeoutError');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('recognizes an instance coming from a duplicate copy of the module', () => {
|
|
106
|
+
const fromOtherCopy = Object.assign(new Error('API Error!'), {
|
|
107
|
+
name: 'TimeoutError',
|
|
108
|
+
[Symbol.for('@datocms/rest-client-utils:TimeoutError')]: true,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
expect(fromOtherCopy).toBeInstanceOf(TimeoutError);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('is not confused with an ApiError', () => {
|
|
115
|
+
expect(buildApiError()).not.toBeInstanceOf(TimeoutError);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe('CanceledPromiseError', () => {
|
|
120
|
+
it('keeps a correct prototype chain and name', () => {
|
|
121
|
+
const error = new CanceledPromiseError();
|
|
122
|
+
|
|
123
|
+
expect(error).toBeInstanceOf(CanceledPromiseError);
|
|
124
|
+
expect(error).toBeInstanceOf(Error);
|
|
125
|
+
expect(error.name).toBe('CanceledPromiseError');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('recognizes an instance coming from a duplicate copy of the module', () => {
|
|
129
|
+
const fromOtherCopy = Object.assign(new Error('Promise canceled!'), {
|
|
130
|
+
name: 'CanceledPromiseError',
|
|
131
|
+
[Symbol.for('@datocms/rest-client-utils:CanceledPromiseError')]: true,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
expect(fromOtherCopy).toBeInstanceOf(CanceledPromiseError);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('is not confused with other error types', () => {
|
|
138
|
+
expect(buildApiError()).not.toBeInstanceOf(CanceledPromiseError);
|
|
139
|
+
expect(new Error('nope')).not.toBeInstanceOf(CanceledPromiseError);
|
|
140
|
+
});
|
|
141
|
+
});
|
package/src/errors.ts
CHANGED
|
@@ -59,6 +59,19 @@ export type ApiErrorResponse = {
|
|
|
59
59
|
body?: unknown;
|
|
60
60
|
};
|
|
61
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Brands stamped on error instances.
|
|
64
|
+
*
|
|
65
|
+
* `Symbol.for()` looks up the cross-realm global symbol registry, so every copy
|
|
66
|
+
* of this module — CJS or ESM, inlined by a bundler or not — resolves these to
|
|
67
|
+
* the very same symbols. Since these packages ship parallel CJS and ESM builds
|
|
68
|
+
* with no guarantee that a consumer ends up with a single copy, identity is
|
|
69
|
+
* carried by these brands rather than by class identity alone, so `instanceof`
|
|
70
|
+
* keeps working across duplicated copies.
|
|
71
|
+
*/
|
|
72
|
+
const TIMEOUT_ERROR = Symbol.for('@datocms/rest-client-utils:TimeoutError');
|
|
73
|
+
const API_ERROR = Symbol.for('@datocms/rest-client-utils:ApiError');
|
|
74
|
+
|
|
62
75
|
export type TimeoutErrorInitObject = {
|
|
63
76
|
request: ApiErrorRequest;
|
|
64
77
|
preCallStack?: string;
|
|
@@ -68,12 +81,44 @@ export class TimeoutError extends Error {
|
|
|
68
81
|
request: ApiErrorRequest;
|
|
69
82
|
preCallStack?: string;
|
|
70
83
|
|
|
84
|
+
declare readonly [TIMEOUT_ERROR]: true;
|
|
85
|
+
|
|
86
|
+
static [Symbol.hasInstance](value: unknown): value is TimeoutError {
|
|
87
|
+
if (
|
|
88
|
+
typeof value !== 'object' ||
|
|
89
|
+
value === null ||
|
|
90
|
+
!(TIMEOUT_ERROR in value)
|
|
91
|
+
) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// `this` is the constructor `instanceof` was invoked on: when it is a
|
|
96
|
+
// subclass, defer to a real prototype-chain check so subclasses stay exact.
|
|
97
|
+
// biome-ignore lint/complexity/noThisInStatic: dynamic `this` is required here; hardcoding the class would break subclass checks
|
|
98
|
+
const invokedOn = this as unknown as { prototype: object };
|
|
99
|
+
|
|
100
|
+
if (invokedOn !== TimeoutError) {
|
|
101
|
+
return Object.prototype.isPrototypeOf.call(invokedOn.prototype, value);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
|
|
71
107
|
constructor(initObject: TimeoutErrorInitObject) {
|
|
72
108
|
super('API Error!');
|
|
73
109
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
74
110
|
|
|
111
|
+
Object.defineProperty(this, TIMEOUT_ERROR, {
|
|
112
|
+
value: true,
|
|
113
|
+
enumerable: false,
|
|
114
|
+
writable: false,
|
|
115
|
+
configurable: false,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
this.name = 'TimeoutError';
|
|
119
|
+
|
|
75
120
|
if ('captureStackTrace' in Error) {
|
|
76
|
-
Error.captureStackTrace(this,
|
|
121
|
+
Error.captureStackTrace(this, TimeoutError);
|
|
77
122
|
} else {
|
|
78
123
|
this.stack = new (Error as any)().stack;
|
|
79
124
|
}
|
|
@@ -100,10 +145,38 @@ export class ApiError extends Error {
|
|
|
100
145
|
response: ApiErrorResponse;
|
|
101
146
|
preCallStack?: string;
|
|
102
147
|
|
|
148
|
+
declare readonly [API_ERROR]: true;
|
|
149
|
+
|
|
150
|
+
static [Symbol.hasInstance](value: unknown): value is ApiError {
|
|
151
|
+
if (typeof value !== 'object' || value === null || !(API_ERROR in value)) {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// `this` is the constructor `instanceof` was invoked on: when it is a
|
|
156
|
+
// subclass, defer to a real prototype-chain check so subclasses stay exact.
|
|
157
|
+
// biome-ignore lint/complexity/noThisInStatic: dynamic `this` is required here; hardcoding the class would break subclass checks
|
|
158
|
+
const invokedOn = this as unknown as { prototype: object };
|
|
159
|
+
|
|
160
|
+
if (invokedOn !== ApiError) {
|
|
161
|
+
return Object.prototype.isPrototypeOf.call(invokedOn.prototype, value);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
|
|
103
167
|
constructor(initObject: ApiErrorInitObject) {
|
|
104
168
|
super('API Error!');
|
|
105
169
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
106
170
|
|
|
171
|
+
Object.defineProperty(this, API_ERROR, {
|
|
172
|
+
value: true,
|
|
173
|
+
enumerable: false,
|
|
174
|
+
writable: false,
|
|
175
|
+
configurable: false,
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
this.name = 'ApiError';
|
|
179
|
+
|
|
107
180
|
if ('captureStackTrace' in Error) {
|
|
108
181
|
Error.captureStackTrace(this, ApiError);
|
|
109
182
|
} else {
|
package/src/index.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
export * from './deserialize';
|
|
2
|
-
export * from './errors';
|
|
3
|
-
export * from './makeCancelablePromise';
|
|
4
|
-
export * from './pollJobResult';
|
|
5
|
-
export * from './rawPageIterator';
|
|
6
|
-
export * from './request';
|
|
7
|
-
export * from './serialize';
|
|
8
|
-
export * from './toId';
|
|
9
|
-
export * from './types';
|
|
10
|
-
export * from './wait';
|
|
1
|
+
export * from './deserialize.js';
|
|
2
|
+
export * from './errors.js';
|
|
3
|
+
export * from './makeCancelablePromise.js';
|
|
4
|
+
export * from './pollJobResult.js';
|
|
5
|
+
export * from './rawPageIterator.js';
|
|
6
|
+
export * from './request.js';
|
|
7
|
+
export * from './serialize.js';
|
|
8
|
+
export * from './toId.js';
|
|
9
|
+
export * from './types.js';
|
|
10
|
+
export * from './wait.js';
|
|
@@ -1,7 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* See the note on the brands in `errors.ts`: identity is carried by a symbol
|
|
3
|
+
* from the cross-realm global registry so that `instanceof` keeps working even
|
|
4
|
+
* when a bundler ends up loading more than one copy of this module.
|
|
5
|
+
*/
|
|
6
|
+
const CANCELED_PROMISE_ERROR = Symbol.for(
|
|
7
|
+
'@datocms/rest-client-utils:CanceledPromiseError',
|
|
8
|
+
);
|
|
9
|
+
|
|
1
10
|
export class CanceledPromiseError extends Error {
|
|
11
|
+
declare readonly [CANCELED_PROMISE_ERROR]: true;
|
|
12
|
+
|
|
13
|
+
static [Symbol.hasInstance](value: unknown): value is CanceledPromiseError {
|
|
14
|
+
if (
|
|
15
|
+
typeof value !== 'object' ||
|
|
16
|
+
value === null ||
|
|
17
|
+
!(CANCELED_PROMISE_ERROR in value)
|
|
18
|
+
) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// `this` is the constructor `instanceof` was invoked on: when it is a
|
|
23
|
+
// subclass, defer to a real prototype-chain check so subclasses stay exact.
|
|
24
|
+
// biome-ignore lint/complexity/noThisInStatic: dynamic `this` is required here; hardcoding the class would break subclass checks
|
|
25
|
+
const invokedOn = this as unknown as { prototype: object };
|
|
26
|
+
|
|
27
|
+
if (invokedOn !== CanceledPromiseError) {
|
|
28
|
+
return Object.prototype.isPrototypeOf.call(invokedOn.prototype, value);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
|
|
2
34
|
constructor() {
|
|
3
35
|
super('Promise canceled!');
|
|
4
36
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
37
|
+
|
|
38
|
+
Object.defineProperty(this, CANCELED_PROMISE_ERROR, {
|
|
39
|
+
value: true,
|
|
40
|
+
enumerable: false,
|
|
41
|
+
writable: false,
|
|
42
|
+
configurable: false,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
this.name = 'CanceledPromiseError';
|
|
5
46
|
}
|
|
6
47
|
}
|
|
7
48
|
|
package/src/pollJobResult.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { ApiError } from './errors';
|
|
2
|
-
import type { JobResult } from './internalTypes';
|
|
3
|
-
import { wait } from './wait';
|
|
1
|
+
import { ApiError } from './errors.js';
|
|
2
|
+
import type { JobResult } from './internalTypes.js';
|
|
3
|
+
import { wait } from './wait.js';
|
|
4
4
|
|
|
5
5
|
export async function pollJobResult(
|
|
6
6
|
fetcher: () => Promise<JobResult>,
|
package/src/rawPageIterator.ts
CHANGED
package/src/request.ts
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
/// <reference lib="dom" />
|
|
2
2
|
|
|
3
|
-
import { buildNormalizedParams } from './buildNormalizedParams';
|
|
3
|
+
import { buildNormalizedParams } from './buildNormalizedParams.js';
|
|
4
4
|
import {
|
|
5
5
|
ApiError,
|
|
6
6
|
type ApiErrorInitObject,
|
|
7
7
|
TimeoutError,
|
|
8
8
|
type TimeoutErrorInitObject,
|
|
9
|
-
} from './errors';
|
|
10
|
-
import type { JobResult } from './internalTypes';
|
|
9
|
+
} from './errors.js';
|
|
10
|
+
import type { JobResult } from './internalTypes.js';
|
|
11
11
|
import {
|
|
12
12
|
CanceledPromiseError,
|
|
13
13
|
makeCancelablePromise,
|
|
14
|
-
} from './makeCancelablePromise';
|
|
15
|
-
import { wait } from './wait';
|
|
14
|
+
} from './makeCancelablePromise.js';
|
|
15
|
+
import { wait } from './wait.js';
|
|
16
16
|
|
|
17
17
|
const isBrowser =
|
|
18
18
|
typeof window !== 'undefined' && typeof window.document !== 'undefined';
|