attlaz-client 1.74.0 → 1.75.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/Client.d.ts +8 -1
- package/dist/Client.js +9 -0
- package/dist/Http/ClientError.js +6 -43
- package/dist/Http/HttpClientResponse.d.ts +0 -2
- package/dist/Http/HttpClientResponse.js +0 -19
- package/dist/Http/Transport/DirectTransport.d.ts +4 -1
- package/dist/Http/Transport/DirectTransport.js +11 -0
- package/dist/Http/Transport/ITransport.d.ts +8 -0
- package/dist/Http/Transport/OAuthClient.d.ts +8 -5
- package/dist/Http/Transport/OAuthClient.js +82 -56
- package/dist/Service/Endpoint.js +15 -13
- package/dist/index.d.ts +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/Client.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { OAuthClientToken } from './Http/OAuthClientToken.js';
|
|
2
|
-
import { ITransport } from './Http/Transport/ITransport.js';
|
|
2
|
+
import { ITransport, ParseErrorHandler } from './Http/Transport/ITransport.js';
|
|
3
3
|
import { OAuthClient } from './Http/Transport/OAuthClient.js';
|
|
4
4
|
import { AccessTokenEndpoint } from './Service/AccessTokenEndpoint.js';
|
|
5
5
|
import { AdapterConnectionEndpoint } from './Service/AdapterConnectionEndpoint.js';
|
|
@@ -59,6 +59,13 @@ export declare class Client {
|
|
|
59
59
|
setPublicClient(clientId: string): void;
|
|
60
60
|
setVersion(version: string | null): void;
|
|
61
61
|
getHttpClient(): OAuthClient;
|
|
62
|
+
/**
|
|
63
|
+
* Set a handler for non-fatal response parse failures (schema drift). Defaults to
|
|
64
|
+
* console.error; wire this to your logger / error tracker to observe drift in production
|
|
65
|
+
* without breaking views. Pass null to restore the default. Set on the active transport,
|
|
66
|
+
* so it applies whether the client uses OAuth or a custom transport.
|
|
67
|
+
*/
|
|
68
|
+
setParseErrorHandler(handler: ParseErrorHandler | null): void;
|
|
62
69
|
authenticate(): Promise<boolean>;
|
|
63
70
|
authenticate(username: string, password: string): Promise<boolean>;
|
|
64
71
|
isAuthenticated(): boolean;
|
package/dist/Client.js
CHANGED
|
@@ -122,6 +122,15 @@ export class Client {
|
|
|
122
122
|
getHttpClient() {
|
|
123
123
|
return this.httpClient;
|
|
124
124
|
}
|
|
125
|
+
/**
|
|
126
|
+
* Set a handler for non-fatal response parse failures (schema drift). Defaults to
|
|
127
|
+
* console.error; wire this to your logger / error tracker to observe drift in production
|
|
128
|
+
* without breaking views. Pass null to restore the default. Set on the active transport,
|
|
129
|
+
* so it applies whether the client uses OAuth or a custom transport.
|
|
130
|
+
*/
|
|
131
|
+
setParseErrorHandler(handler) {
|
|
132
|
+
this.transport.setParseErrorHandler(handler);
|
|
133
|
+
}
|
|
125
134
|
async authenticate(username = null, password = null) {
|
|
126
135
|
if (username === null || password === null) {
|
|
127
136
|
return await this.httpClient.authenticate();
|
package/dist/Http/ClientError.js
CHANGED
|
@@ -10,53 +10,16 @@ export class ClientError extends Error {
|
|
|
10
10
|
}
|
|
11
11
|
static fromError(error) {
|
|
12
12
|
// Already a ClientError (or a subclass such as ApiError) — preserve it as-is.
|
|
13
|
-
// Re-deriving here would discard its httpStatus, because this method inspects the
|
|
14
|
-
// `.status`/`.code` shape of raw transport errors, not ClientError's own fields.
|
|
15
13
|
if (error instanceof ClientError) {
|
|
16
14
|
return error;
|
|
17
15
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
if (clientErrorByStatus !== null) {
|
|
25
|
-
clientError = clientErrorByStatus;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
else if (xError.code !== null && xError.code !== undefined) {
|
|
29
|
-
switch (xError.code) {
|
|
30
|
-
case 'EUNAVAILABLE':
|
|
31
|
-
case 'ECONNREFUSED':
|
|
32
|
-
case 'HTTP_UNAVAILABLE':
|
|
33
|
-
clientError.httpStatus = HttpStatus.HTTP_UNAVAILABLE;
|
|
34
|
-
break;
|
|
35
|
-
case 'EAUTH':
|
|
36
|
-
case 401:
|
|
37
|
-
clientError.httpStatus = HttpStatus.HTTP_UNAUTHORIZED;
|
|
38
|
-
break;
|
|
39
|
-
case 'ESTATUS':
|
|
40
|
-
if (error.message === 'HTTP status 503') {
|
|
41
|
-
clientError.httpStatus = HttpStatus.HTTP_UNAVAILABLE;
|
|
42
|
-
}
|
|
43
|
-
break;
|
|
44
|
-
case 500:
|
|
45
|
-
clientError.httpStatus = HttpStatus.HTTP_INTERNAL_SERVER_ERROR;
|
|
46
|
-
break;
|
|
47
|
-
default:
|
|
48
|
-
console.warn('[Client error] Error code `' + xError.code + '` not recognised');
|
|
49
|
-
clientError.httpStatus = HttpStatus.HTTP_INTERNAL_SERVER_ERROR;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
else if (error instanceof TypeError) {
|
|
53
|
-
// fetch throws TypeError on network failures (ECONNREFUSED, DNS, etc.)
|
|
54
|
-
clientError.httpStatus = HttpStatus.HTTP_UNAVAILABLE;
|
|
55
|
-
clientError.message = 'Service not available';
|
|
56
|
-
return clientError;
|
|
57
|
-
}
|
|
16
|
+
// The transport uses fetch, which throws TypeError on network failures (DNS, refused
|
|
17
|
+
// connection, TLS, etc.). Anything else reaching here is unexpected — surface it as a
|
|
18
|
+
// generic 500 while keeping the original name/stack for debugging.
|
|
19
|
+
const clientError = error instanceof TypeError
|
|
20
|
+
? new ClientError('Service not available', HttpStatus.HTTP_UNAVAILABLE)
|
|
21
|
+
: new ClientError('Unknown error', HttpStatus.HTTP_INTERNAL_SERVER_ERROR);
|
|
58
22
|
clientError.name = error.name;
|
|
59
|
-
// TODO: only in debug mode
|
|
60
23
|
clientError.stack = error.stack;
|
|
61
24
|
return clientError;
|
|
62
25
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { ContentTypeHelper } from './ContentTypeHelper.js';
|
|
2
1
|
export class HttpClientResponse {
|
|
3
2
|
status;
|
|
4
3
|
statusText;
|
|
@@ -8,22 +7,4 @@ export class HttpClientResponse {
|
|
|
8
7
|
this.status = status;
|
|
9
8
|
this.statusText = statusText;
|
|
10
9
|
}
|
|
11
|
-
getContentType() {
|
|
12
|
-
// TODO: content-type or Content-Type or both...?
|
|
13
|
-
let contentType = this.headers['content-type'];
|
|
14
|
-
if (contentType === null || contentType === undefined) {
|
|
15
|
-
return null;
|
|
16
|
-
}
|
|
17
|
-
if (Array.isArray(contentType)) {
|
|
18
|
-
[contentType] = contentType;
|
|
19
|
-
}
|
|
20
|
-
if (contentType === null || contentType === undefined) {
|
|
21
|
-
return null;
|
|
22
|
-
}
|
|
23
|
-
const parsedContentType = ContentTypeHelper.formatContentType(contentType);
|
|
24
|
-
return parsedContentType.type;
|
|
25
|
-
}
|
|
26
|
-
isJson() {
|
|
27
|
-
return this.getContentType() === 'application/json';
|
|
28
|
-
}
|
|
29
10
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Parameters } from '../Data/Parameters.js';
|
|
2
|
-
import { ITransport } from './ITransport.js';
|
|
2
|
+
import { ITransport, ParseErrorHandler } from './ITransport.js';
|
|
3
3
|
export interface DirectTransportRoute {
|
|
4
4
|
prefix: string;
|
|
5
5
|
baseUrl: string;
|
|
@@ -27,6 +27,7 @@ export interface DirectTransportRoute {
|
|
|
27
27
|
export declare class DirectTransport implements ITransport {
|
|
28
28
|
private readonly clients;
|
|
29
29
|
private readonly sortedRoutes;
|
|
30
|
+
private parseErrorHandler;
|
|
30
31
|
/**
|
|
31
32
|
* Creates the default session payload for internal service-to-service calls.
|
|
32
33
|
*/
|
|
@@ -35,4 +36,6 @@ export declare class DirectTransport implements ITransport {
|
|
|
35
36
|
request<T>(action: string, parameters?: Parameters, method?: string, _signWithOauthToken?: boolean): Promise<T>;
|
|
36
37
|
private resolveClient;
|
|
37
38
|
isDebugEnabled(): boolean;
|
|
39
|
+
setParseErrorHandler(handler: ParseErrorHandler | null): void;
|
|
40
|
+
reportParseError(message: string, context: Record<string, unknown>): void;
|
|
38
41
|
}
|
|
@@ -23,6 +23,7 @@ import { OAuthClient } from './OAuthClient.js';
|
|
|
23
23
|
export class DirectTransport {
|
|
24
24
|
clients = new Map();
|
|
25
25
|
sortedRoutes;
|
|
26
|
+
parseErrorHandler = null;
|
|
26
27
|
/**
|
|
27
28
|
* Creates the default session payload for internal service-to-service calls.
|
|
28
29
|
*/
|
|
@@ -69,4 +70,14 @@ export class DirectTransport {
|
|
|
69
70
|
isDebugEnabled() {
|
|
70
71
|
return false;
|
|
71
72
|
}
|
|
73
|
+
setParseErrorHandler(handler) {
|
|
74
|
+
this.parseErrorHandler = handler;
|
|
75
|
+
}
|
|
76
|
+
reportParseError(message, context) {
|
|
77
|
+
if (this.parseErrorHandler !== null) {
|
|
78
|
+
this.parseErrorHandler(message, context);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
console.error(message, context);
|
|
82
|
+
}
|
|
72
83
|
}
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { Parameters } from '../Data/Parameters.js';
|
|
2
|
+
/**
|
|
3
|
+
* Invoked when an endpoint fails to parse a response object (schema drift). Reporting is
|
|
4
|
+
* non-fatal — parsing still degrades gracefully. Defaults to console.error; set a custom handler
|
|
5
|
+
* (e.g. via Client.setParseErrorHandler) to route these to a logger / error tracker.
|
|
6
|
+
*/
|
|
7
|
+
export type ParseErrorHandler = (message: string, context: Record<string, unknown>) => void;
|
|
2
8
|
export interface ITransport {
|
|
3
9
|
request: <T>(action: string, parameters: Parameters, method: string, signWithOauthToken: boolean) => Promise<T>;
|
|
4
10
|
isDebugEnabled: () => boolean;
|
|
11
|
+
reportParseError: (message: string, context: Record<string, unknown>) => void;
|
|
12
|
+
setParseErrorHandler: (handler: ParseErrorHandler | null) => void;
|
|
5
13
|
}
|
|
@@ -2,25 +2,26 @@ import { Headers } from '../Data/Headers.js';
|
|
|
2
2
|
import { Parameters } from '../Data/Parameters.js';
|
|
3
3
|
import { OAuthClientOptions } from '../OAuthClientOptions.js';
|
|
4
4
|
import { OAuthClientToken } from '../OAuthClientToken.js';
|
|
5
|
-
import { ITransport } from './ITransport.js';
|
|
5
|
+
import { ITransport, ParseErrorHandler } from './ITransport.js';
|
|
6
6
|
export declare class OAuthClient implements ITransport {
|
|
7
7
|
private readonly options;
|
|
8
8
|
private debug;
|
|
9
9
|
private oauthClientToken;
|
|
10
10
|
private refreshTokenPromise;
|
|
11
11
|
private version;
|
|
12
|
+
private parseErrorHandler;
|
|
12
13
|
constructor(options: OAuthClientOptions);
|
|
13
14
|
authenticate(username: string, password: string): Promise<boolean>;
|
|
14
15
|
authenticate(): Promise<boolean>;
|
|
15
16
|
refreshToken(): Promise<void>;
|
|
16
17
|
private isPublicClient;
|
|
17
18
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
19
|
+
* Performs an OAuth token request (password, client_credentials or refresh_token grant) and
|
|
20
|
+
* returns the raw token response. On a non-ok response it throws a ClientError carrying the
|
|
21
|
+
* server's HTTP status and, when the body is JSON, its structured error message.
|
|
21
22
|
*/
|
|
22
23
|
private requestToken;
|
|
23
|
-
|
|
24
|
+
isTokenExpired(): boolean;
|
|
24
25
|
request<T>(action: string, parameters?: Parameters, method?: string, signWithOauthToken?: boolean): Promise<T>;
|
|
25
26
|
isAuthenticated(): boolean;
|
|
26
27
|
getToken(): OAuthClientToken | null;
|
|
@@ -29,6 +30,8 @@ export declare class OAuthClient implements ITransport {
|
|
|
29
30
|
enableDebug(): void;
|
|
30
31
|
disableDebug(): void;
|
|
31
32
|
isDebugEnabled(): boolean;
|
|
33
|
+
setParseErrorHandler(handler: ParseErrorHandler | null): void;
|
|
34
|
+
reportParseError(message: string, context: Record<string, unknown>): void;
|
|
32
35
|
private defaultHeaders;
|
|
33
36
|
getDefaultHeaders(): Headers;
|
|
34
37
|
private initDefaultHeaders;
|
|
@@ -11,6 +11,7 @@ export class OAuthClient {
|
|
|
11
11
|
oauthClientToken = null;
|
|
12
12
|
refreshTokenPromise = null;
|
|
13
13
|
version = null;
|
|
14
|
+
parseErrorHandler = null;
|
|
14
15
|
constructor(options) {
|
|
15
16
|
this.options = options;
|
|
16
17
|
this.initDefaultHeaders();
|
|
@@ -96,9 +97,9 @@ export class OAuthClient {
|
|
|
96
97
|
return this.options.clientSecret === null || this.options.clientSecret === '';
|
|
97
98
|
}
|
|
98
99
|
/**
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
100
|
+
* Performs an OAuth token request (password, client_credentials or refresh_token grant) and
|
|
101
|
+
* returns the raw token response. On a non-ok response it throws a ClientError carrying the
|
|
102
|
+
* server's HTTP status and, when the body is JSON, its structured error message.
|
|
102
103
|
*/
|
|
103
104
|
async requestToken(params) {
|
|
104
105
|
const response = await fetch(this.getApiEndpointUrl(this.options.accessTokenUri), {
|
|
@@ -107,64 +108,83 @@ export class OAuthClient {
|
|
|
107
108
|
body: new URLSearchParams(params).toString(),
|
|
108
109
|
});
|
|
109
110
|
if (!response.ok) {
|
|
110
|
-
|
|
111
|
+
const clientError = new ClientError(response.statusText || 'Token request failed', response.status);
|
|
112
|
+
// The token endpoint bypasses HttpClient (and authenticate() bypasses Endpoint.toApiError),
|
|
113
|
+
// so without this the server's body is discarded and callers only see the generic HTTP
|
|
114
|
+
// statusText. Attach the body and surface its structured OAuth error message directly so a
|
|
115
|
+
// failed login shows e.g. an invalid-credentials reason. Mirrors HttpClient.request() +
|
|
116
|
+
// Endpoint.toApiError(). The refresh path replaces this message with a 401, so it's unaffected.
|
|
117
|
+
try {
|
|
118
|
+
const data = await response.json();
|
|
119
|
+
clientError.response = {
|
|
120
|
+
status: response.status,
|
|
121
|
+
statusText: response.statusText,
|
|
122
|
+
data,
|
|
123
|
+
};
|
|
124
|
+
const apiMessage = data?.error?.message;
|
|
125
|
+
if (apiMessage !== undefined && apiMessage !== null && apiMessage !== '') {
|
|
126
|
+
clientError.message = apiMessage;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
// Response body is not JSON — leave response/message as-is
|
|
131
|
+
}
|
|
132
|
+
throw clientError;
|
|
111
133
|
}
|
|
112
134
|
return response.json();
|
|
113
135
|
}
|
|
114
|
-
|
|
136
|
+
isTokenExpired() {
|
|
115
137
|
if (this.oauthClientToken === null) {
|
|
116
138
|
throw new Error('No token defined');
|
|
117
139
|
}
|
|
118
140
|
return OAuthClientToken.isExpired(this.oauthClientToken);
|
|
119
141
|
}
|
|
120
142
|
async request(action, parameters = null, method = 'GET', signWithOauthToken = true) {
|
|
121
|
-
if (signWithOauthToken
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
if (
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
if (
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
try {
|
|
133
|
-
await this.refreshTokenPromise;
|
|
134
|
-
}
|
|
135
|
-
finally {
|
|
136
|
-
// Always clear, even on failure, so a single failed refresh
|
|
137
|
-
// doesn't poison every later request with the same rejected
|
|
138
|
-
// promise (e.g. after the user re-authenticates).
|
|
139
|
-
this.refreshTokenPromise = null;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
else {
|
|
143
|
+
if (signWithOauthToken) {
|
|
144
|
+
// A single null check both guards and narrows the token type. No access token at
|
|
145
|
+
// all is an auth failure (401), so consumers route it to the same sign-out path as
|
|
146
|
+
// a rejected refresh.
|
|
147
|
+
if (this.oauthClientToken === null) {
|
|
148
|
+
throw new ClientError('Unable to perform request, access token not provided', HttpStatus.HTTP_UNAUTHORIZED);
|
|
149
|
+
}
|
|
150
|
+
if (OAuthClientToken.isExpired(this.oauthClientToken)) {
|
|
151
|
+
if (this.refreshTokenPromise === null) {
|
|
152
|
+
this.refreshTokenPromise = this.refreshToken();
|
|
153
|
+
try {
|
|
143
154
|
await this.refreshTokenPromise;
|
|
144
155
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
try {
|
|
152
|
-
const response = await HttpClient.request(requestData);
|
|
153
|
-
return response.body;
|
|
154
|
-
}
|
|
155
|
-
catch (error) {
|
|
156
|
-
if (!(error instanceof ClientError)) {
|
|
157
|
-
const clientError = ClientError.fromError(error);
|
|
158
|
-
if (this.debug) {
|
|
159
|
-
console.error('[Client] Error:', { error, clientError });
|
|
156
|
+
finally {
|
|
157
|
+
// Always clear, even on failure, so a single failed refresh doesn't
|
|
158
|
+
// poison every later request with the same rejected promise (e.g.
|
|
159
|
+
// after the user re-authenticates).
|
|
160
|
+
this.refreshTokenPromise = null;
|
|
160
161
|
}
|
|
161
|
-
throw clientError;
|
|
162
162
|
}
|
|
163
|
+
else {
|
|
164
|
+
await this.refreshTokenPromise;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const requestData = this.createRequestData(action, parameters, method, signWithOauthToken);
|
|
169
|
+
if (this.debug) {
|
|
170
|
+
console.debug('[OAuthClient] Request: ' + requestData.method.toUpperCase() + ' ' + requestData.getFullUrl(), { headers: requestData.headers });
|
|
171
|
+
}
|
|
172
|
+
try {
|
|
173
|
+
const response = await HttpClient.request(requestData);
|
|
174
|
+
return response.body;
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
if (!(error instanceof ClientError)) {
|
|
178
|
+
const clientError = ClientError.fromError(error);
|
|
163
179
|
if (this.debug) {
|
|
164
|
-
console.error('[Client] Error:', { error });
|
|
180
|
+
console.error('[Client] Error:', { error, clientError });
|
|
165
181
|
}
|
|
166
|
-
throw
|
|
182
|
+
throw clientError;
|
|
167
183
|
}
|
|
184
|
+
if (this.debug) {
|
|
185
|
+
console.error('[Client] Error:', { error });
|
|
186
|
+
}
|
|
187
|
+
throw error;
|
|
168
188
|
}
|
|
169
189
|
}
|
|
170
190
|
isAuthenticated() {
|
|
@@ -189,6 +209,16 @@ export class OAuthClient {
|
|
|
189
209
|
isDebugEnabled() {
|
|
190
210
|
return this.debug;
|
|
191
211
|
}
|
|
212
|
+
setParseErrorHandler(handler) {
|
|
213
|
+
this.parseErrorHandler = handler;
|
|
214
|
+
}
|
|
215
|
+
reportParseError(message, context) {
|
|
216
|
+
if (this.parseErrorHandler !== null) {
|
|
217
|
+
this.parseErrorHandler(message, context);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
console.error(message, context);
|
|
221
|
+
}
|
|
192
222
|
defaultHeaders = {};
|
|
193
223
|
getDefaultHeaders() {
|
|
194
224
|
return this.defaultHeaders;
|
|
@@ -262,22 +292,18 @@ export class OAuthClient {
|
|
|
262
292
|
});
|
|
263
293
|
}
|
|
264
294
|
if (signWithOauthToken) {
|
|
295
|
+
// request() already ensured a present, non-expired token; this guard only narrows
|
|
296
|
+
// the type before signing.
|
|
265
297
|
if (this.oauthClientToken === null) {
|
|
266
|
-
throw new ClientError('Unable to perform request, access token not provided');
|
|
298
|
+
throw new ClientError('Unable to perform request, access token not provided', HttpStatus.HTTP_UNAUTHORIZED);
|
|
267
299
|
}
|
|
268
|
-
|
|
269
|
-
throw new Error('Unable to sign request, token is expired');
|
|
270
|
-
}
|
|
271
|
-
requestData = this.signRequest(requestData);
|
|
300
|
+
requestData = this.signRequest(requestData, this.oauthClientToken);
|
|
272
301
|
}
|
|
273
302
|
return requestData;
|
|
274
303
|
}
|
|
275
|
-
signRequest(requestObject) {
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
}
|
|
279
|
-
const tokenType = this.oauthClientToken.token_type;
|
|
280
|
-
const accessToken = this.oauthClientToken.access_token;
|
|
304
|
+
signRequest(requestObject, token) {
|
|
305
|
+
const tokenType = token.token_type;
|
|
306
|
+
const accessToken = token.access_token;
|
|
281
307
|
if (tokenType !== undefined && tokenType.toLowerCase() === 'bearer') {
|
|
282
308
|
requestObject.setHeader('Authorization', 'Bearer ' + accessToken);
|
|
283
309
|
}
|
package/dist/Service/Endpoint.js
CHANGED
|
@@ -17,10 +17,6 @@ export class Endpoint {
|
|
|
17
17
|
if (data === null || data === undefined) {
|
|
18
18
|
return null;
|
|
19
19
|
}
|
|
20
|
-
if (typeof data === 'object' && '_formatted' in data) {
|
|
21
|
-
// TODO: unset _formatted
|
|
22
|
-
return data;
|
|
23
|
-
}
|
|
24
20
|
if (Array.isArray(data)) {
|
|
25
21
|
const result = [];
|
|
26
22
|
for (const value of data) {
|
|
@@ -118,14 +114,9 @@ export class Endpoint {
|
|
|
118
114
|
/**
|
|
119
115
|
* Parse errors
|
|
120
116
|
*/
|
|
121
|
-
// TODO: temporary check until we know the API is fully upgraded
|
|
122
117
|
if (requestResponse === null || requestResponse === undefined) {
|
|
123
118
|
throw new ApiError('Unable to parse object: response is empty for action `[' + method + '] ' + action + '`', HttpStatus.HTTP_INTERNAL_SERVER_ERROR);
|
|
124
119
|
}
|
|
125
|
-
if ((Object.prototype.hasOwnProperty.call(requestResponse, 'data') && !Object.prototype.hasOwnProperty.call(requestResponse, 'id')) && requestResponse.data !== undefined) {
|
|
126
|
-
console.error('Response for object "' + action + '" seem to still use old "data" property (this property is only used for collection requests)', { requestResponse });
|
|
127
|
-
requestResponse = requestResponse.data;
|
|
128
|
-
}
|
|
129
120
|
result.setData(this.parseObject(requestResponse, parser));
|
|
130
121
|
return result;
|
|
131
122
|
}
|
|
@@ -170,13 +161,24 @@ export class Endpoint {
|
|
|
170
161
|
return data;
|
|
171
162
|
}
|
|
172
163
|
parseObject(rawObject, parser) {
|
|
173
|
-
//
|
|
174
|
-
|
|
164
|
+
// Parse the raw object directly by default. With debug enabled the object is wrapped so
|
|
165
|
+
// that reads of properties the response doesn't contain get reported — useful for spotting
|
|
166
|
+
// schema drift, but too noisy (and a per-object Proxy cost) to run normally.
|
|
167
|
+
// Enable via client.getHttpClient().enableDebug().
|
|
168
|
+
const data = this.httpClient.isDebugEnabled() ? ObjectWrapper.wrap(rawObject) : rawObject;
|
|
175
169
|
try {
|
|
176
|
-
return parser(
|
|
170
|
+
return parser(data);
|
|
177
171
|
}
|
|
178
172
|
catch (error) {
|
|
179
|
-
|
|
173
|
+
// Report through the transport's parse-error handler (defaults to console.error;
|
|
174
|
+
// consumers can route it to a logger / error tracker via Client.setParseErrorHandler).
|
|
175
|
+
this.httpClient.reportParseError('Unable to parse object', { object: rawObject, error });
|
|
176
|
+
// In debug, surface schema drift loudly (dev/tests) by rethrowing. In production,
|
|
177
|
+
// degrade gracefully — return null so the caller skips the row / shows a not-found
|
|
178
|
+
// state, and a single unparseable object never breaks an entire view.
|
|
179
|
+
if (this.httpClient.isDebugEnabled()) {
|
|
180
|
+
throw error;
|
|
181
|
+
}
|
|
180
182
|
}
|
|
181
183
|
return null;
|
|
182
184
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export { OAuthClientOptions } from './Http/OAuthClientOptions.js';
|
|
|
15
15
|
export { OAuthClientToken } from './Http/OAuthClientToken.js';
|
|
16
16
|
export { ClientError } from './Http/ClientError.js';
|
|
17
17
|
export { ApiError } from './Model/Error/ApiError.js';
|
|
18
|
+
export type { ParseErrorHandler } from './Http/Transport/ITransport.js';
|
|
18
19
|
export { HttpStatus } from './Http/HttpStatus.js';
|
|
19
20
|
export { ContentTypeHelper } from './Http/ContentTypeHelper.js';
|
|
20
21
|
/**
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "1.
|
|
1
|
+
export declare const VERSION = "1.74.0";
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = "1.
|
|
1
|
+
export const VERSION = "1.74.0";
|