@moneylion/engine-api 1.3.5 → 1.3.7

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
@@ -112,15 +112,18 @@ const leadData = {
112
112
  purpose: "debt_consolidation",
113
113
  loanAmount: 10000
114
114
  },
115
- offerCatalogProductTypes: ["DebtSettlement"] // Note: different from productTypes
115
+ productTypes: ["DebtSettlement"] // Product types for offer catalog
116
116
  };
117
117
 
118
+ // First, create or get a lead UUID
119
+ const leadUuid = await leads.create(leadData);
120
+
118
121
  // Non-blocking: returns AsyncOfferCatalogRateTable that you can resolve
119
- const asyncRateTable = await leads.getOfferCatalogRateTable(leadData);
122
+ const asyncRateTable = await leads.getOfferCatalogRateTable(leadUuid, leadData);
120
123
  const rateTable = await asyncRateTable.resolve();
121
124
 
122
125
  // Blocking: waits for the final rate table
123
- const rateTable = await leads.getOfferCatalogRateTableBlocking(leadData);
126
+ const rateTable = await leads.getOfferCatalogRateTableBlocking(leadUuid, leadData);
124
127
  ```
125
128
 
126
129
  #### Fetching an Existing Offer Catalog Rate Table
package/dist/client.d.ts CHANGED
@@ -13,12 +13,16 @@ export declare class Client {
13
13
  private auth_token;
14
14
  private fetch_impl;
15
15
  private timeout_ms;
16
+ private additional_headers;
16
17
  /**
17
18
  * Construct a new client instance.
18
19
  * @param host - The API host URL. Example: https://www.example.com. Should not include a trailing slash.
19
20
  * @param auth_token - The auth token used for authenticating with the API.
21
+ * @param timeout_ms - Request timeout in milliseconds. Defaults to 15 seconds.
22
+ * @param fetch_impl - Fetch implementation to use. Defaults to global fetch.
23
+ * @param additional_headers - Additional headers to include in all requests.
20
24
  */
21
- constructor(host: string | undefined, auth_token: string, timeout_ms?: number, fetch_impl?: typeof fetch);
25
+ constructor(host: string | undefined, auth_token: string, timeout_ms?: number, fetch_impl?: typeof fetch, additional_headers?: Record<string, string>);
22
26
  /**
23
27
  * Make a GET request.
24
28
  *
@@ -59,6 +63,11 @@ export declare class Client {
59
63
  * @returns A deserialized Javascript object wrapped in a Promise.
60
64
  */
61
65
  request(endpoint: string, method: HttpMethod, body?: string): Promise<object>;
66
+ /**
67
+ * Safely attempts to read the response body as text.
68
+ * Returns an error message if reading fails.
69
+ */
70
+ private safelyReadResponseBody;
62
71
  }
63
72
  /** The generic error class used to represent error status codes such as 400 Bad Request.
64
73
  */
@@ -72,15 +81,19 @@ export declare class ApiError extends Error {
72
81
  /** The HTTP method that was used for the request.
73
82
  */
74
83
  method: HttpMethod;
75
- /** The serialized body.
84
+ /** The serialized request body.
76
85
  */
77
86
  body?: string;
78
- constructor({ failureStatus, failureReason, url, method, body }: {
87
+ /** The response body as text.
88
+ */
89
+ responseBody: string;
90
+ constructor({ failureStatus, failureReason, url, method, body, responseBody, }: {
79
91
  failureStatus?: number;
80
92
  failureReason: string;
81
93
  url: string;
82
94
  method: HttpMethod;
83
95
  body?: string;
96
+ responseBody?: string;
84
97
  });
85
98
  }
86
99
  /**
package/dist/client.js CHANGED
@@ -20,12 +20,16 @@ export class Client {
20
20
  * Construct a new client instance.
21
21
  * @param host - The API host URL. Example: https://www.example.com. Should not include a trailing slash.
22
22
  * @param auth_token - The auth token used for authenticating with the API.
23
+ * @param timeout_ms - Request timeout in milliseconds. Defaults to 15 seconds.
24
+ * @param fetch_impl - Fetch implementation to use. Defaults to global fetch.
25
+ * @param additional_headers - Additional headers to include in all requests.
23
26
  */
24
- constructor(host = "https://api.evenfinancial.com", auth_token, timeout_ms = 15 * 1000, fetch_impl = fetch) {
27
+ constructor(host = "https://api.evenfinancial.com", auth_token, timeout_ms = 15 * 1000, fetch_impl = fetch, additional_headers = {}) {
25
28
  this.host = host;
26
29
  this.auth_token = auth_token;
27
30
  this.fetch_impl = fetch_impl;
28
31
  this.timeout_ms = timeout_ms;
32
+ this.additional_headers = additional_headers;
29
33
  }
30
34
  /**
31
35
  * Make a GET request.
@@ -81,40 +85,40 @@ export class Client {
81
85
  const response = yield Promise.race([
82
86
  this.fetch_impl(url, {
83
87
  method: method,
84
- headers: {
85
- "Content-Type": "application/json",
86
- "Accept": "application/json",
87
- "Authorization": `Bearer ${this.auth_token}`,
88
- },
88
+ headers: Object.assign({ "Content-Type": "application/json", "Accept": "application/json", "Authorization": `Bearer ${this.auth_token}` }, this.additional_headers),
89
89
  body: body,
90
90
  }),
91
- timeout
91
+ timeout,
92
92
  ]);
93
93
  if (response === undefined) {
94
94
  throw new TimeoutError({
95
95
  failureReason: "Client Timeout",
96
96
  url,
97
97
  method,
98
- body
98
+ body,
99
99
  });
100
100
  }
101
101
  if (response.status >= 500) {
102
+ const responseBody = yield this.safelyReadResponseBody(response);
102
103
  throw new ServerError({
103
104
  failureStatus: response.status,
104
105
  failureReason: response.statusText,
105
106
  url,
106
107
  method,
107
- body
108
+ body,
109
+ responseBody,
108
110
  });
109
111
  }
110
112
  if (response.status >= 400) {
113
+ const responseBody = yield this.safelyReadResponseBody(response);
111
114
  if (response.status === 401 || response.status === 403) {
112
115
  throw new AuthenticationError({
113
116
  failureStatus: response.status,
114
117
  failureReason: response.statusText,
115
118
  url,
116
119
  method,
117
- body
120
+ body,
121
+ responseBody,
118
122
  });
119
123
  }
120
124
  throw new ClientError({
@@ -122,7 +126,8 @@ export class Client {
122
126
  failureReason: response.statusText,
123
127
  url,
124
128
  method,
125
- body
129
+ body,
130
+ responseBody,
126
131
  });
127
132
  }
128
133
  /* node-fetch types json() as Promise<unknown> to ensure the consumer makes a decision about the
@@ -133,16 +138,32 @@ export class Client {
133
138
  return json;
134
139
  });
135
140
  }
141
+ /**
142
+ * Safely attempts to read the response body as text.
143
+ * Returns an error message if reading fails.
144
+ */
145
+ safelyReadResponseBody(response) {
146
+ return __awaiter(this, void 0, void 0, function* () {
147
+ try {
148
+ const text = yield response.text();
149
+ return text || "[Empty response body]";
150
+ }
151
+ catch (error) {
152
+ return `[Unable to read response body: ${error instanceof Error ? error.message : String(error)}]`;
153
+ }
154
+ });
155
+ }
136
156
  }
137
157
  /** The generic error class used to represent error status codes such as 400 Bad Request.
138
158
  */
139
159
  export class ApiError extends Error {
140
- constructor({ failureStatus, failureReason, url, method, body }) {
160
+ constructor({ failureStatus, failureReason, url, method, body, responseBody = "", }) {
141
161
  super(failureReason);
142
162
  this.failureStatus = failureStatus;
143
163
  this.url = url;
144
164
  this.method = method;
145
165
  this.body = body;
166
+ this.responseBody = responseBody;
146
167
  }
147
168
  }
148
169
  /**
@@ -1,8 +1,18 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
1
10
  import { describe, expect, test, beforeAll, afterEach, afterAll, } from "@jest/globals";
2
11
  import { Client, AuthenticationError, ClientError, ServerError, TimeoutError, } from "./client";
3
12
  import { setupServer } from "msw/node";
4
13
  import { http, HttpResponse } from "msw";
5
14
  const successBody = { response: "Okay" };
15
+ const errorResponseBody = { error: "Something went wrong", code: "ERR_001" };
6
16
  const handlers = [
7
17
  http.get("https://example.com/auth_error", () => {
8
18
  return new HttpResponse(null, { status: 401 });
@@ -10,9 +20,15 @@ const handlers = [
10
20
  http.post("https://example.com/bad_request", () => {
11
21
  return new HttpResponse(null, { status: 400 });
12
22
  }),
23
+ http.post("https://example.com/bad_request_with_body", () => {
24
+ return HttpResponse.json(errorResponseBody, { status: 400 });
25
+ }),
13
26
  http.get("https://example.com/server_error", () => {
14
27
  return new HttpResponse(null, { status: 500 });
15
28
  }),
29
+ http.get("https://example.com/server_error_with_body", () => {
30
+ return HttpResponse.json(errorResponseBody, { status: 500 });
31
+ }),
16
32
  http.get("https://example.com/network_error", () => {
17
33
  return HttpResponse.error();
18
34
  }),
@@ -25,6 +41,13 @@ const handlers = [
25
41
  http.patch("https://example.com/success", () => {
26
42
  return HttpResponse.json(successBody);
27
43
  }),
44
+ http.get("https://example.com/check_headers", ({ request }) => {
45
+ const headers = Object.fromEntries(request.headers.entries());
46
+ return HttpResponse.json({ headers });
47
+ }),
48
+ http.get("https://example.com/empty_error_body", () => {
49
+ return new HttpResponse("", { status: 400 });
50
+ }),
28
51
  ];
29
52
  const server = setupServer(...handlers);
30
53
  describe("Client", () => {
@@ -83,4 +106,78 @@ describe("Client", () => {
83
106
  const response = client.request("timeout_error", "GET");
84
107
  return expect(response).rejects.toThrow(TimeoutError);
85
108
  });
109
+ test("Additional headers are included in requests", () => __awaiter(void 0, void 0, void 0, function* () {
110
+ const client = new Client(testEndpoint, "good_token", 15000, fetch, {
111
+ "x-custom-header": "test-value",
112
+ "x-another-header": "another-value",
113
+ });
114
+ const response = yield client.get("check_headers");
115
+ const headers = response.headers;
116
+ expect(headers).toMatchObject({
117
+ "x-another-header": "another-value",
118
+ "x-custom-header": "test-value",
119
+ });
120
+ }));
121
+ test("Response body is captured in ClientError", () => __awaiter(void 0, void 0, void 0, function* () {
122
+ const client = new Client(testEndpoint, "good_token");
123
+ try {
124
+ yield client.post("bad_request_with_body", { test: "body" });
125
+ }
126
+ catch (error) {
127
+ expect(error).toBeInstanceOf(ClientError);
128
+ if (error instanceof ClientError) {
129
+ expect(error.responseBody).toBe(JSON.stringify(errorResponseBody));
130
+ expect(error.failureStatus).toBe(400);
131
+ }
132
+ }
133
+ }));
134
+ test("Response body is captured in ServerError", () => __awaiter(void 0, void 0, void 0, function* () {
135
+ const client = new Client(testEndpoint, "good_token");
136
+ try {
137
+ yield client.get("server_error_with_body");
138
+ }
139
+ catch (error) {
140
+ expect(error).toBeInstanceOf(ServerError);
141
+ if (error instanceof ServerError) {
142
+ expect(error.responseBody).toBe(JSON.stringify(errorResponseBody));
143
+ expect(error.failureStatus).toBe(500);
144
+ }
145
+ }
146
+ }));
147
+ test("Empty response body returns placeholder message", () => __awaiter(void 0, void 0, void 0, function* () {
148
+ const client = new Client(testEndpoint, "good_token");
149
+ try {
150
+ yield client.get("empty_error_body");
151
+ }
152
+ catch (error) {
153
+ expect(error).toBeInstanceOf(ClientError);
154
+ if (error instanceof ClientError) {
155
+ expect(error.responseBody).toBe("[Empty response body]");
156
+ expect(error.failureStatus).toBe(400);
157
+ }
158
+ }
159
+ }));
160
+ test("Unreadable response body returns error message", () => __awaiter(void 0, void 0, void 0, function* () {
161
+ // Mock fetch that returns a response where .text() throws an error
162
+ const fetch_impl = () => __awaiter(void 0, void 0, void 0, function* () {
163
+ return {
164
+ status: 500,
165
+ statusText: "Internal Server Error",
166
+ text: () => __awaiter(void 0, void 0, void 0, function* () {
167
+ throw new Error("Sample error");
168
+ }),
169
+ };
170
+ });
171
+ const client = new Client(testEndpoint, "good_token", 15000, fetch_impl);
172
+ try {
173
+ yield client.get("unreadable_error_body");
174
+ }
175
+ catch (error) {
176
+ expect(error).toBeInstanceOf(ServerError);
177
+ if (error instanceof ServerError) {
178
+ expect(error.responseBody).toBe("[Unable to read response body: Sample error]");
179
+ expect(error.failureStatus).toBe(500);
180
+ }
181
+ }
182
+ }));
86
183
  });
package/dist/leads.d.ts CHANGED
@@ -93,20 +93,21 @@ export declare class Leads {
93
93
  /**
94
94
  * Create a new offer catalog rate table for a lead.
95
95
  *
96
- * @param lead - An object representing the lead information including offerCatalogProductTypes.
96
+ * @param leadUuid - The UUID of the lead for which to create the rate table.
97
+ * @param lead - An object representing the lead information.
97
98
  *
98
99
  * @returns AsyncOfferCatalogRateTable
99
100
  *
100
101
  * @remarks
101
102
  * This method returns an AsyncOfferCatalogRateTable.
102
103
  * Use the resolve method on the returned object to retrieve the final Offer Catalog Rate Table.
103
- * The lead data should include offerCatalogProductTypes (e.g., ["DebtSettlement"]) instead of productTypes.
104
104
  */
105
- getOfferCatalogRateTable(lead: LeadCreateData): Promise<AsyncOfferCatalogRateTable>;
105
+ getOfferCatalogRateTable(leadUuid: string, lead: LeadCreateData): Promise<AsyncOfferCatalogRateTable>;
106
106
  /**
107
107
  * Create a new offer catalog rate table, immediately resolving it.
108
108
  *
109
- * @param lead - An object representing the lead information including offerCatalogProductTypes.
109
+ * @param leadUuid - The UUID of the lead for which to create the rate table.
110
+ * @param lead - An object representing the lead information.
110
111
  *
111
112
  * @returns OfferCatalogRateTable
112
113
  *
@@ -114,7 +115,6 @@ export declare class Leads {
114
115
  * This method will immediately resolve the final Offer Catalog Rate Table.
115
116
  * This may mean waiting a significant amount of time before it is finalized.
116
117
  * Only use this if you can handle waiting for that amount of time.
117
- * The lead data should include offerCatalogProductTypes (e.g., ["DebtSettlement"]) instead of productTypes.
118
118
  */
119
- getOfferCatalogRateTableBlocking(lead: LeadCreateData): Promise<OfferCatalogRateTable>;
119
+ getOfferCatalogRateTableBlocking(leadUuid: string, lead: LeadCreateData): Promise<OfferCatalogRateTable>;
120
120
  }
package/dist/leads.js CHANGED
@@ -147,18 +147,18 @@ export class Leads {
147
147
  /**
148
148
  * Create a new offer catalog rate table for a lead.
149
149
  *
150
- * @param lead - An object representing the lead information including offerCatalogProductTypes.
150
+ * @param leadUuid - The UUID of the lead for which to create the rate table.
151
+ * @param lead - An object representing the lead information.
151
152
  *
152
153
  * @returns AsyncOfferCatalogRateTable
153
154
  *
154
155
  * @remarks
155
156
  * This method returns an AsyncOfferCatalogRateTable.
156
157
  * Use the resolve method on the returned object to retrieve the final Offer Catalog Rate Table.
157
- * The lead data should include offerCatalogProductTypes (e.g., ["DebtSettlement"]) instead of productTypes.
158
158
  */
159
- getOfferCatalogRateTable(lead) {
159
+ getOfferCatalogRateTable(leadUuid, lead) {
160
160
  return __awaiter(this, void 0, void 0, function* () {
161
- const resp = yield this.client.post("originator/offerCatalogRateTables", lead);
161
+ const resp = yield this.client.post(`leads/${leadUuid}/offerCatalogRateTables`, lead);
162
162
  const rateTable = offerCatalogRateTableDecoder.runWithException(resp);
163
163
  const asyncRateTable = new AsyncOfferCatalogRateTable({
164
164
  rateTable,
@@ -170,7 +170,8 @@ export class Leads {
170
170
  /**
171
171
  * Create a new offer catalog rate table, immediately resolving it.
172
172
  *
173
- * @param lead - An object representing the lead information including offerCatalogProductTypes.
173
+ * @param leadUuid - The UUID of the lead for which to create the rate table.
174
+ * @param lead - An object representing the lead information.
174
175
  *
175
176
  * @returns OfferCatalogRateTable
176
177
  *
@@ -178,11 +179,10 @@ export class Leads {
178
179
  * This method will immediately resolve the final Offer Catalog Rate Table.
179
180
  * This may mean waiting a significant amount of time before it is finalized.
180
181
  * Only use this if you can handle waiting for that amount of time.
181
- * The lead data should include offerCatalogProductTypes (e.g., ["DebtSettlement"]) instead of productTypes.
182
182
  */
183
- getOfferCatalogRateTableBlocking(lead) {
183
+ getOfferCatalogRateTableBlocking(leadUuid, lead) {
184
184
  return __awaiter(this, void 0, void 0, function* () {
185
- const resp = yield this.client.post("blocking/originator/offerCatalogRateTables", lead);
185
+ const resp = yield this.client.post(`blocking/leads/${leadUuid}/offerCatalogRateTables`, lead);
186
186
  const rateTable = offerCatalogRateTableDecoder.runWithException(resp);
187
187
  return rateTable;
188
188
  });
@@ -235,6 +235,8 @@ const patchLeadUuidResponse = "patch-lead-uuid-response";
235
235
  const createLeadUuidResponse = "create-lead-uuid-response";
236
236
  const getRateTableLeadUuid = "get-rate-table-lead-uuid";
237
237
  const getRateTableLeadUuidBlocking = "get-rate-table-lead-uuid-blocking";
238
+ const getOfferCatalogRateTableLeadUuid = "get-offer-catalog-rate-table-lead-uuid";
239
+ const getOfferCatalogRateTableLeadUuidBlocking = "get-offer-catalog-rate-table-lead-uuid-blocking";
238
240
  const handlers = [
239
241
  http.post(`${testHost}/blocking/leads/rateTables`, () => {
240
242
  return new HttpResponse(JSON.stringify(fullRateTable), { status: 200 });
@@ -275,12 +277,12 @@ const handlers = [
275
277
  });
276
278
  }),
277
279
  // Offer Catalog endpoints
278
- http.post(`${testHost}/originator/offerCatalogRateTables`, () => {
280
+ http.post(`${testHost}/leads/${getOfferCatalogRateTableLeadUuid}/offerCatalogRateTables`, () => {
279
281
  return new HttpResponse(JSON.stringify(pendingOfferCatalogRateTable), {
280
282
  status: 200,
281
283
  });
282
284
  }),
283
- http.post(`${testHost}/blocking/originator/offerCatalogRateTables`, () => {
285
+ http.post(`${testHost}/blocking/leads/${getOfferCatalogRateTableLeadUuidBlocking}/offerCatalogRateTables`, () => {
284
286
  return new HttpResponse(JSON.stringify(fullOfferCatalogRateTable), {
285
287
  status: 200,
286
288
  });
@@ -338,14 +340,14 @@ describe("Leads", () => {
338
340
  }));
339
341
  test("Get offer catalog rate table endpoint returns an async rate table", () => __awaiter(void 0, void 0, void 0, function* () {
340
342
  const leads = new Leads(testHost, token);
341
- const resp = yield leads.getOfferCatalogRateTable(testLeadData);
343
+ const resp = yield leads.getOfferCatalogRateTable(getOfferCatalogRateTableLeadUuid, testLeadData);
342
344
  expect(resp).toBeInstanceOf(AsyncOfferCatalogRateTable);
343
345
  const rateTable = yield resp.resolve();
344
346
  expect(rateTable).toEqual(fullOfferCatalogRateTable);
345
347
  }));
346
348
  test("Get offer catalog rate table blocking endpoint returns a resolved rate table", () => __awaiter(void 0, void 0, void 0, function* () {
347
349
  const leads = new Leads(testHost, token);
348
- const resp = yield leads.getOfferCatalogRateTableBlocking(testLeadData);
350
+ const resp = yield leads.getOfferCatalogRateTableBlocking(getOfferCatalogRateTableLeadUuidBlocking, testLeadData);
349
351
  expect(resp).toEqual(fullOfferCatalogRateTable);
350
352
  }));
351
353
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moneylion/engine-api",
3
- "version": "1.3.5",
3
+ "version": "1.3.7",
4
4
  "description": "Interface to engine.tech API",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",