conductor-node 8.5.3 → 8.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/README.md
CHANGED
|
@@ -192,14 +192,8 @@ The error object you receive will have one of the following error types:
|
|
|
192
192
|
| `ConductorConnectionError` | There was a network problem between the client (on your server) and Conductor's servers. |
|
|
193
193
|
| `ConductorInternalError` | Something went wrong on Conductor's end. (These are rare.) |
|
|
194
194
|
|
|
195
|
-
### Alerting
|
|
196
|
-
|
|
197
|
-
We recommend sending a **warning** to your error-tracking service (e.g., Sentry) for instances of `ConductorIntegrationError`, which are your end-user's fault (e.g., cannot connect to QBD on your end-user's computer) and sending an **error** for all other errors (e.g., invalid API key).
|
|
198
|
-
|
|
199
195
|
### Example
|
|
200
196
|
|
|
201
|
-
**NOTE:** We do not expect you to individually implement error handling as shown below for every API call. Ideally, we recommend implementing error handling in a single place in your app, such as a global error handler.
|
|
202
|
-
|
|
203
197
|
In the form of a rejected promise:
|
|
204
198
|
|
|
205
199
|
```ts
|
|
@@ -238,3 +232,64 @@ try {
|
|
|
238
232
|
}
|
|
239
233
|
}
|
|
240
234
|
```
|
|
235
|
+
|
|
236
|
+
### Integration
|
|
237
|
+
|
|
238
|
+
**NOTE:** We do not expect you to individually wrap every API call like the examples above. Instead, you should only need to wrap a specific Conductor API request when adding special handling for specific errors, such as checking the property `error.code`.
|
|
239
|
+
|
|
240
|
+
Ideally, if your server does not already, we expect you to have a single global error handler, such as [`app.use((error, req, res, next) => { ... })` in Express](https://expressjs.com/en/guide/error-handling.html#writing-error-handlers), or [`formatError` in Apollo Server](https://apollographql.com/docs/apollo-server/data/errors/#for-client-responses). In such a handler, you can do the following:
|
|
241
|
+
|
|
242
|
+
1. Ensure your app's UI shows your end-user the property `error.endUserMessage` of any `ConductorError` instance for any Conductor request while you log the rest.
|
|
243
|
+
2. Send the full error object to your error-tracking service (e.g., Sentry) for all `ConductorError` instances, for which we recommend:
|
|
244
|
+
1. Send a **warning** to your error-tracking service for instances of `ConductorIntegrationError`, which are your end-user's fault; e.g., cannot connect to QBD on your end-user's computer.
|
|
245
|
+
2. Send an **error** for all other `ConductorError` instances; e.g., invalid API key.
|
|
246
|
+
|
|
247
|
+
For example, here is how you might do this in Express:
|
|
248
|
+
|
|
249
|
+
```ts
|
|
250
|
+
app.use((error, req, res, next) => {
|
|
251
|
+
if (error instanceof ConductorError) {
|
|
252
|
+
Sentry.captureException(error, {
|
|
253
|
+
level: error instanceof ConductorIntegrationError ? "warning" : "error",
|
|
254
|
+
});
|
|
255
|
+
// Return a different error message for your end-user to see in your app's UI.
|
|
256
|
+
res.status(500).send({ error: { message: error.endUserMessage } });
|
|
257
|
+
} else {
|
|
258
|
+
// ...
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
And here is how you might do this in Apollo Server:
|
|
264
|
+
|
|
265
|
+
```ts
|
|
266
|
+
import { unwrapResolverError } from "@apollo/server/errors";
|
|
267
|
+
|
|
268
|
+
const server = new ApolloServer({
|
|
269
|
+
typeDefs,
|
|
270
|
+
resolvers,
|
|
271
|
+
formatError: (formattedError, error) => {
|
|
272
|
+
const originalError = unwrapResolverError(originalErrorWrapped);
|
|
273
|
+
|
|
274
|
+
if (originalError instanceof ConductorError) {
|
|
275
|
+
Sentry.captureException(originalError, {
|
|
276
|
+
level:
|
|
277
|
+
originalError instanceof ConductorIntegrationError
|
|
278
|
+
? "warning"
|
|
279
|
+
: "error",
|
|
280
|
+
});
|
|
281
|
+
// Return a different error message for your end-user to see in your app's UI.
|
|
282
|
+
return {
|
|
283
|
+
...formattedError,
|
|
284
|
+
message: originalError.endUserMessage,
|
|
285
|
+
};
|
|
286
|
+
} else {
|
|
287
|
+
// ...
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Otherwise return the formatted error. This error can also
|
|
291
|
+
// be manipulated in other ways, as long as it's returned.
|
|
292
|
+
return formattedError;
|
|
293
|
+
},
|
|
294
|
+
});
|
|
295
|
+
```
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "conductor-node",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.6.0",
|
|
4
4
|
"description": "Easily integrate with the entire QuickBooks Desktop API with fully-typed async TypeScript",
|
|
5
5
|
"author": "Danny Nemer <hi@DannyNemer.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -24,13 +24,13 @@
|
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"graphql": "^16.6.0",
|
|
27
|
-
"graphql-request": "
|
|
27
|
+
"graphql-request": "~5.1.0"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
|
-
"@graphql-codegen/add": "^
|
|
31
|
-
"@graphql-codegen/cli": "^2.
|
|
30
|
+
"@graphql-codegen/add": "^4.0.1",
|
|
31
|
+
"@graphql-codegen/cli": "^3.2.1",
|
|
32
32
|
"@graphql-codegen/typescript-graphql-request": "^4.5.8",
|
|
33
|
-
"@graphql-codegen/typescript-operations": "^
|
|
33
|
+
"@graphql-codegen/typescript-operations": "^3.0.1",
|
|
34
34
|
"tsc-alias": "^1.7.0"
|
|
35
35
|
},
|
|
36
36
|
"keywords": [
|
package/dist/src/errors.d.ts
CHANGED
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
export declare const DEFAULT_END_USER_MESSAGE = "An internal server error occurred. Please try again later.";
|
|
2
2
|
export interface ConductorErrorOptions {
|
|
3
|
-
readonly
|
|
3
|
+
readonly type: string;
|
|
4
4
|
readonly code: string;
|
|
5
5
|
readonly message: string;
|
|
6
6
|
readonly endUserMessage?: string;
|
|
7
7
|
readonly httpStatusCode?: number;
|
|
8
8
|
readonly integrationCode?: string;
|
|
9
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* The raw GraphQL error that Conductor's API returns.
|
|
12
|
+
*/
|
|
13
|
+
export interface ConductorRawGraphqlError {
|
|
14
|
+
readonly message: string;
|
|
15
|
+
readonly extensions: Omit<ConductorErrorOptions, "httpStatusCode" | "message">;
|
|
16
|
+
}
|
|
10
17
|
/**
|
|
11
18
|
* The base error from which all other more specific Conductor errors derive.
|
|
12
|
-
* Specifically for errors
|
|
19
|
+
* Specifically for errors that Conductor's API returned.
|
|
13
20
|
*/
|
|
14
21
|
export declare class ConductorError extends Error {
|
|
15
22
|
/**
|
|
@@ -49,7 +56,7 @@ export declare class ConductorError extends Error {
|
|
|
49
56
|
readonly integrationCode: string | undefined;
|
|
50
57
|
protected constructor(options: ConductorErrorOptions);
|
|
51
58
|
}
|
|
52
|
-
type
|
|
59
|
+
type ConductorErrorOptionsWithoutType = Omit<ConductorErrorOptions, "type">;
|
|
53
60
|
/**
|
|
54
61
|
* Raised when an error occurs on the third-party integration's end while
|
|
55
62
|
* processing your end-user's request. E.g., cannot connect to QuickBooks
|
|
@@ -57,7 +64,7 @@ type ConductorErrorOptionsWithoutRawType = Omit<ConductorErrorOptions, "rawType"
|
|
|
57
64
|
*/
|
|
58
65
|
export declare class ConductorIntegrationError extends ConductorError {
|
|
59
66
|
static readonly rawType: string;
|
|
60
|
-
constructor(options:
|
|
67
|
+
constructor(options: ConductorErrorOptionsWithoutType);
|
|
61
68
|
}
|
|
62
69
|
/**
|
|
63
70
|
* Raised when you make an API call with the wrong parameters, in the wrong
|
|
@@ -65,7 +72,7 @@ export declare class ConductorIntegrationError extends ConductorError {
|
|
|
65
72
|
*/
|
|
66
73
|
export declare class ConductorInvalidRequestError extends ConductorError {
|
|
67
74
|
static readonly rawType = "INVALID_REQUEST_ERROR";
|
|
68
|
-
constructor(options:
|
|
75
|
+
constructor(options: ConductorErrorOptionsWithoutType);
|
|
69
76
|
}
|
|
70
77
|
/**
|
|
71
78
|
* Raised when Conductor cannot authenticate you with the credentials you
|
|
@@ -73,7 +80,7 @@ export declare class ConductorInvalidRequestError extends ConductorError {
|
|
|
73
80
|
*/
|
|
74
81
|
export declare class ConductorAuthenticationError extends ConductorError {
|
|
75
82
|
static readonly rawType = "AUTHENTICATION_ERROR";
|
|
76
|
-
constructor(options:
|
|
83
|
+
constructor(options: ConductorErrorOptionsWithoutType);
|
|
77
84
|
}
|
|
78
85
|
/**
|
|
79
86
|
* Raised when there is a network problem between the client (on your server)
|
|
@@ -81,14 +88,14 @@ export declare class ConductorAuthenticationError extends ConductorError {
|
|
|
81
88
|
*/
|
|
82
89
|
export declare class ConductorConnectionError extends ConductorError {
|
|
83
90
|
static readonly rawType = "CONNECTION_ERROR";
|
|
84
|
-
constructor(options:
|
|
91
|
+
constructor(options: ConductorErrorOptionsWithoutType);
|
|
85
92
|
}
|
|
86
93
|
/**
|
|
87
94
|
* Raised when something goes wrong on Conductor's end. (These are rare.)
|
|
88
95
|
*/
|
|
89
96
|
export declare class ConductorInternalError extends ConductorError {
|
|
90
97
|
static readonly rawType = "INTERNAL_ERROR";
|
|
91
|
-
constructor(options:
|
|
98
|
+
constructor(options: ConductorErrorOptionsWithoutType);
|
|
92
99
|
}
|
|
93
100
|
/**
|
|
94
101
|
* Raised as a fallback for any other error from Conductor that no other error
|
package/dist/src/errors.js
CHANGED
|
@@ -5,7 +5,7 @@ exports.generateConductorErrorFromType = exports.ConductorUnknownError = exports
|
|
|
5
5
|
exports.DEFAULT_END_USER_MESSAGE = "An internal server error occurred. Please try again later.";
|
|
6
6
|
/**
|
|
7
7
|
* The base error from which all other more specific Conductor errors derive.
|
|
8
|
-
* Specifically for errors
|
|
8
|
+
* Specifically for errors that Conductor's API returned.
|
|
9
9
|
*/
|
|
10
10
|
class ConductorError extends Error {
|
|
11
11
|
/**
|
|
@@ -56,7 +56,7 @@ class ConductorError extends Error {
|
|
|
56
56
|
// `ConductorError` always have the correct `type` even if they are
|
|
57
57
|
// instantiated with the wrong options.
|
|
58
58
|
this.type = this.constructor.name;
|
|
59
|
-
this.rawType = options.
|
|
59
|
+
this.rawType = options.type;
|
|
60
60
|
this.code = options.code;
|
|
61
61
|
this.endUserMessage = options.endUserMessage ?? exports.DEFAULT_END_USER_MESSAGE;
|
|
62
62
|
this.httpStatusCode = options.httpStatusCode;
|
|
@@ -72,7 +72,7 @@ exports.ConductorError = ConductorError;
|
|
|
72
72
|
class ConductorIntegrationError extends ConductorError {
|
|
73
73
|
static rawType = "INTEGRATION_ERROR";
|
|
74
74
|
constructor(options) {
|
|
75
|
-
super({ ...options,
|
|
75
|
+
super({ ...options, type: ConductorIntegrationError.rawType });
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
78
|
exports.ConductorIntegrationError = ConductorIntegrationError;
|
|
@@ -83,7 +83,7 @@ exports.ConductorIntegrationError = ConductorIntegrationError;
|
|
|
83
83
|
class ConductorInvalidRequestError extends ConductorError {
|
|
84
84
|
static rawType = "INVALID_REQUEST_ERROR";
|
|
85
85
|
constructor(options) {
|
|
86
|
-
super({ ...options,
|
|
86
|
+
super({ ...options, type: ConductorInvalidRequestError.rawType });
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
exports.ConductorInvalidRequestError = ConductorInvalidRequestError;
|
|
@@ -94,7 +94,7 @@ exports.ConductorInvalidRequestError = ConductorInvalidRequestError;
|
|
|
94
94
|
class ConductorAuthenticationError extends ConductorError {
|
|
95
95
|
static rawType = "AUTHENTICATION_ERROR";
|
|
96
96
|
constructor(options) {
|
|
97
|
-
super({ ...options,
|
|
97
|
+
super({ ...options, type: ConductorAuthenticationError.rawType });
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
100
|
exports.ConductorAuthenticationError = ConductorAuthenticationError;
|
|
@@ -105,7 +105,7 @@ exports.ConductorAuthenticationError = ConductorAuthenticationError;
|
|
|
105
105
|
class ConductorConnectionError extends ConductorError {
|
|
106
106
|
static rawType = "CONNECTION_ERROR";
|
|
107
107
|
constructor(options) {
|
|
108
|
-
super({ ...options,
|
|
108
|
+
super({ ...options, type: ConductorConnectionError.rawType });
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
111
|
exports.ConductorConnectionError = ConductorConnectionError;
|
|
@@ -115,7 +115,7 @@ exports.ConductorConnectionError = ConductorConnectionError;
|
|
|
115
115
|
class ConductorInternalError extends ConductorError {
|
|
116
116
|
static rawType = "INTERNAL_ERROR";
|
|
117
117
|
constructor(options) {
|
|
118
|
-
super({ ...options,
|
|
118
|
+
super({ ...options, type: ConductorInternalError.rawType });
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
121
|
exports.ConductorInternalError = ConductorInternalError;
|
|
@@ -132,7 +132,7 @@ class ConductorUnknownError extends ConductorError {
|
|
|
132
132
|
}
|
|
133
133
|
exports.ConductorUnknownError = ConductorUnknownError;
|
|
134
134
|
function generateConductorErrorFromType(options) {
|
|
135
|
-
switch (options.
|
|
135
|
+
switch (options.type) {
|
|
136
136
|
case ConductorIntegrationError.rawType: {
|
|
137
137
|
return new ConductorIntegrationError(options);
|
|
138
138
|
}
|
|
@@ -4,4 +4,5 @@ export declare function wrapGraphqlOperations<T extends ReturnType<typeof getSdk
|
|
|
4
4
|
export declare function graphqlOperationWrapper<V extends {
|
|
5
5
|
[key: string]: unknown;
|
|
6
6
|
}, R>(operationName: string, variables: V | undefined, operation: (variables: V | undefined) => Promise<R>, verbose: boolean): Promise<R>;
|
|
7
|
+
export declare function getDurationString(startTime: number): string;
|
|
7
8
|
export declare function wrapError(error: Error): ConductorError;
|
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.wrapError = exports.graphqlOperationWrapper = exports.wrapGraphqlOperations = void 0;
|
|
6
|
+
exports.wrapError = exports.getDurationString = exports.graphqlOperationWrapper = exports.wrapGraphqlOperations = void 0;
|
|
7
7
|
const package_json_1 = __importDefault(require("../../package.json"));
|
|
8
8
|
const errors_1 = require("../errors");
|
|
9
9
|
const graphql_request_1 = require("graphql-request");
|
|
@@ -18,7 +18,7 @@ function wrapGraphqlOperations(graphqlOperations, verbose) {
|
|
|
18
18
|
}
|
|
19
19
|
exports.wrapGraphqlOperations = wrapGraphqlOperations;
|
|
20
20
|
async function graphqlOperationWrapper(operationName, variables, operation, verbose) {
|
|
21
|
-
const
|
|
21
|
+
const graphqlInfo = {
|
|
22
22
|
operationName,
|
|
23
23
|
input: variables
|
|
24
24
|
? Object.keys(variables).length === 1 && "input" in variables
|
|
@@ -27,7 +27,7 @@ async function graphqlOperationWrapper(operationName, variables, operation, verb
|
|
|
27
27
|
: {},
|
|
28
28
|
};
|
|
29
29
|
if (verbose) {
|
|
30
|
-
console.log(`Conductor request start: ${node_util_1.default.inspect(
|
|
30
|
+
console.log(`Conductor request start: ${node_util_1.default.inspect(graphqlInfo, {
|
|
31
31
|
depth: undefined,
|
|
32
32
|
})}`);
|
|
33
33
|
}
|
|
@@ -37,7 +37,7 @@ async function graphqlOperationWrapper(operationName, variables, operation, verb
|
|
|
37
37
|
if (verbose) {
|
|
38
38
|
const responseLog = {
|
|
39
39
|
duration: getDurationString(startTime),
|
|
40
|
-
...
|
|
40
|
+
...graphqlInfo,
|
|
41
41
|
};
|
|
42
42
|
const responseString = node_util_1.default.inspect(responseLog, { depth: undefined });
|
|
43
43
|
console.log(`Conductor request complete: ${responseString}`);
|
|
@@ -50,7 +50,7 @@ async function graphqlOperationWrapper(operationName, variables, operation, verb
|
|
|
50
50
|
const errorLog = {
|
|
51
51
|
duration: getDurationString(startTime),
|
|
52
52
|
error: String(conductorError),
|
|
53
|
-
...
|
|
53
|
+
...graphqlInfo,
|
|
54
54
|
};
|
|
55
55
|
const errorString = node_util_1.default.inspect(errorLog, { depth: undefined });
|
|
56
56
|
console.log(`Conductor error: ${errorString}`);
|
|
@@ -63,6 +63,7 @@ function getDurationString(startTime) {
|
|
|
63
63
|
const duration = Date.now() - startTime;
|
|
64
64
|
return `${Math.round(duration / 10) / 100}s`;
|
|
65
65
|
}
|
|
66
|
+
exports.getDurationString = getDurationString;
|
|
66
67
|
function wrapError(error) {
|
|
67
68
|
if (error instanceof graphql_request_1.ClientError) {
|
|
68
69
|
const { response } = error;
|
|
@@ -83,12 +84,19 @@ function wrapError(error) {
|
|
|
83
84
|
httpStatusCode: response.status,
|
|
84
85
|
});
|
|
85
86
|
}
|
|
87
|
+
// Do *not* bother validating if this error is a Conductor error:
|
|
88
|
+
// 1. We know this is a `ClientError`, so `message` and `httpStatusCode`
|
|
89
|
+
// exist.
|
|
90
|
+
// 2. We know this is a GraphQL error, so `code` likely exists either from
|
|
91
|
+
// Conductor or Apollo.
|
|
92
|
+
// 3. If `type` is absent, then `generateConductorErrorFromType` will
|
|
93
|
+
// default to `ConductorUnknownError`.
|
|
94
|
+
// 4. If `endUserMessage` is absent, then `ConductorError` will use a
|
|
95
|
+
// default value.
|
|
86
96
|
return (0, errors_1.generateConductorErrorFromType)({
|
|
87
|
-
rawType: errorExtensions["type"],
|
|
88
|
-
code: errorExtensions["code"],
|
|
89
97
|
message: nestedError.message,
|
|
90
|
-
endUserMessage: errorExtensions["endUserMessage"],
|
|
91
98
|
httpStatusCode: response.status,
|
|
99
|
+
...errorExtensions,
|
|
92
100
|
});
|
|
93
101
|
}
|
|
94
102
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "conductor-node",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.6.0",
|
|
4
4
|
"description": "Easily integrate with the entire QuickBooks Desktop API with fully-typed async TypeScript",
|
|
5
5
|
"author": "Danny Nemer <hi@DannyNemer.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -24,13 +24,13 @@
|
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"graphql": "^16.6.0",
|
|
27
|
-
"graphql-request": "
|
|
27
|
+
"graphql-request": "~5.1.0"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
|
-
"@graphql-codegen/add": "^
|
|
31
|
-
"@graphql-codegen/cli": "^2.
|
|
30
|
+
"@graphql-codegen/add": "^4.0.1",
|
|
31
|
+
"@graphql-codegen/cli": "^3.2.1",
|
|
32
32
|
"@graphql-codegen/typescript-graphql-request": "^4.5.8",
|
|
33
|
-
"@graphql-codegen/typescript-operations": "^
|
|
33
|
+
"@graphql-codegen/typescript-operations": "^3.0.1",
|
|
34
34
|
"tsc-alias": "^1.7.0"
|
|
35
35
|
},
|
|
36
36
|
"keywords": [
|