conductor-node 8.5.3 → 8.6.1

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,61 @@ 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 origError = unwrapResolverError(error);
273
+ if (origError instanceof ConductorError) {
274
+ Sentry.captureException(origError, {
275
+ level:
276
+ origError instanceof ConductorIntegrationError ? "warning" : "error",
277
+ });
278
+ // Return a different error message for your end-user to see in your app's UI.
279
+ return {
280
+ ...formattedError,
281
+ message: origError.endUserMessage,
282
+ };
283
+ } else {
284
+ // ...
285
+ }
286
+
287
+ // Otherwise return the formatted error. This error can also
288
+ // be manipulated in other ways, as long as it's returned.
289
+ return formattedError;
290
+ },
291
+ });
292
+ ```
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "conductor-node",
3
- "version": "8.5.3",
3
+ "version": "8.6.1",
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": "^5.0.0"
27
+ "graphql-request": "~5.1.0"
28
28
  },
29
29
  "devDependencies": {
30
- "@graphql-codegen/add": "^3.2.1",
31
- "@graphql-codegen/cli": "^2.13.11",
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": "^2.5.6",
33
+ "@graphql-codegen/typescript-operations": "^3.0.1",
34
34
  "tsc-alias": "^1.7.0"
35
35
  },
36
36
  "keywords": [
@@ -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 rawType: string;
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 returned from Conductor's API.
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 ConductorErrorOptionsWithoutRawType = Omit<ConductorErrorOptions, "rawType">;
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: ConductorErrorOptionsWithoutRawType);
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: ConductorErrorOptionsWithoutRawType);
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: ConductorErrorOptionsWithoutRawType);
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: ConductorErrorOptionsWithoutRawType);
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: ConductorErrorOptionsWithoutRawType);
98
+ constructor(options: ConductorErrorOptionsWithoutType);
92
99
  }
93
100
  /**
94
101
  * Raised as a fallback for any other error from Conductor that no other error
@@ -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 returned from Conductor's API.
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.rawType;
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, rawType: ConductorIntegrationError.rawType });
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, rawType: ConductorInvalidRequestError.rawType });
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, rawType: ConductorAuthenticationError.rawType });
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, rawType: ConductorConnectionError.rawType });
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, rawType: ConductorInternalError.rawType });
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.rawType) {
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 graphqlLog = {
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(graphqlLog, {
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
- ...graphqlLog,
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
- ...graphqlLog,
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.5.3",
3
+ "version": "8.6.1",
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": "^5.0.0"
27
+ "graphql-request": "~5.1.0"
28
28
  },
29
29
  "devDependencies": {
30
- "@graphql-codegen/add": "^3.2.1",
31
- "@graphql-codegen/cli": "^2.13.11",
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": "^2.5.6",
33
+ "@graphql-codegen/typescript-operations": "^3.0.1",
34
34
  "tsc-alias": "^1.7.0"
35
35
  },
36
36
  "keywords": [