@moneylion/engine-api 1.0.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 +8 -0
- package/dist/builders.d.ts +206 -0
- package/dist/builders.js +80 -0
- package/dist/client.d.ts +91 -0
- package/dist/client.js +127 -0
- package/dist/client.test.d.ts +1 -0
- package/dist/client.test.js +78 -0
- package/dist/decoders.d.ts +40 -0
- package/dist/decoders.js +220 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/leads.d.ts +54 -0
- package/dist/leads.js +86 -0
- package/dist/leads.test.d.ts +1 -0
- package/dist/leads.test.js +231 -0
- package/dist/rate_tables.d.ts +45 -0
- package/dist/rate_tables.js +91 -0
- package/dist/rate_tables.test.d.ts +1 -0
- package/dist/rate_tables.test.js +224 -0
- package/package.json +45 -0
|
@@ -0,0 +1,91 @@
|
|
|
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
|
+
};
|
|
10
|
+
import { Client } from "./client.js";
|
|
11
|
+
import { rateTableDecoder } from "./decoders.js";
|
|
12
|
+
const pollInterval = 1000; // 1 second
|
|
13
|
+
/**
|
|
14
|
+
* The class used to fetch rate tables from the Engine API.
|
|
15
|
+
* This class will handle polling the rate tables endpoint until the pending responses are fulfilled.
|
|
16
|
+
*/
|
|
17
|
+
export class AsyncRateTable {
|
|
18
|
+
/**
|
|
19
|
+
* Construct a new AsyncRateTable instance.
|
|
20
|
+
* @param rateTable - An existing Rate Table structure to start from.
|
|
21
|
+
* @param uuid - A UUID of an existing Rate Table to fetch.
|
|
22
|
+
* @param client - A Client instance to use for fetching the data.
|
|
23
|
+
* @param host - A host used to construct a Client instance if none is provided.
|
|
24
|
+
* @param auth_token - An authentication token used to construct a Client instance if none is provided.
|
|
25
|
+
*
|
|
26
|
+
* @remarks
|
|
27
|
+
* Either one of rateTable or uuid is required, but providing both is an error.
|
|
28
|
+
* Similarly, provide either a pre-constructed Client instance or the host and auth_token parameters to construct one but not both.
|
|
29
|
+
*/
|
|
30
|
+
constructor({ rateTable, uuid, client, host, auth_token, }) {
|
|
31
|
+
if (client && host && auth_token) {
|
|
32
|
+
throw new TypeError("Client cannot be provided at the same time as a host and auth_token.");
|
|
33
|
+
}
|
|
34
|
+
if (rateTable && uuid) {
|
|
35
|
+
throw new TypeError("Rate table and uuid cannot be provided at the same time.");
|
|
36
|
+
}
|
|
37
|
+
if (client) {
|
|
38
|
+
this.client = client;
|
|
39
|
+
}
|
|
40
|
+
else if (host && auth_token) {
|
|
41
|
+
this.client = new Client(host, auth_token);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
throw new TypeError("Either client must be provided or host and auth_token must be provided.");
|
|
45
|
+
}
|
|
46
|
+
this.rateTable = rateTable;
|
|
47
|
+
this.uuid = uuid;
|
|
48
|
+
if (!this.rateTable && !this.uuid) {
|
|
49
|
+
throw new TypeError("Either a rate table or a uuid must be provided.");
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Resolve the async Rate Table into a real Rate Table.
|
|
54
|
+
*
|
|
55
|
+
* @returns A Rate Table with no pending responses.
|
|
56
|
+
*
|
|
57
|
+
* @remarks
|
|
58
|
+
*
|
|
59
|
+
* This will only make a network request if one is required.
|
|
60
|
+
* A network request is required if only a UUID was provided to the constructor, or if the provided Rate Table has pending responses.
|
|
61
|
+
* This method will also poll the API every second until the returned Rate Table has no more pending responses.
|
|
62
|
+
*/
|
|
63
|
+
resolve() {
|
|
64
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
65
|
+
if (this.rateTable && this.rateTable.pendingResponses.length > 0) {
|
|
66
|
+
const resolved = yield this.getRateTable(this.rateTable.uuid);
|
|
67
|
+
return new Promise((resolve) => {
|
|
68
|
+
setTimeout(() => {
|
|
69
|
+
resolve(resolved.resolve());
|
|
70
|
+
}, pollInterval);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
else if (this.uuid) {
|
|
74
|
+
const resolved = yield this.getRateTable(this.uuid);
|
|
75
|
+
return resolved.resolve();
|
|
76
|
+
}
|
|
77
|
+
else if (this.rateTable) {
|
|
78
|
+
return this.rateTable;
|
|
79
|
+
}
|
|
80
|
+
throw new TypeError("No uuid or rate table was provided to resolve");
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
getRateTable(uuid) {
|
|
84
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
85
|
+
const endpoint = `originator/rateTables/${uuid}`;
|
|
86
|
+
const resp = yield this.client.get(endpoint);
|
|
87
|
+
const rateTable = rateTableDecoder.runWithException(resp);
|
|
88
|
+
return new AsyncRateTable({ rateTable, client: this.client });
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,224 @@
|
|
|
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
|
+
};
|
|
10
|
+
import { describe, expect, test, beforeAll, afterEach, afterAll, } from "@jest/globals";
|
|
11
|
+
import { Client } from "./client";
|
|
12
|
+
import { setupServer } from "msw/node";
|
|
13
|
+
import { http, HttpResponse } from "msw";
|
|
14
|
+
import { AsyncRateTable } from "./rate_tables";
|
|
15
|
+
const fullRateTable = {
|
|
16
|
+
uuid: "full-rate-table-uuid",
|
|
17
|
+
leadUuid: "full-lead-uuid",
|
|
18
|
+
loanAmount: 10000,
|
|
19
|
+
creditCardOffers: [],
|
|
20
|
+
loanOffers: [],
|
|
21
|
+
mortgageOffers: [],
|
|
22
|
+
savingsOffers: [],
|
|
23
|
+
specialOffers: [
|
|
24
|
+
{
|
|
25
|
+
uuid: "full-special-offer-uuid",
|
|
26
|
+
name: "Mock Credit Builder Offer",
|
|
27
|
+
desc: "Description of the credit builder offer",
|
|
28
|
+
url: "https://offers.engine.tech/ref/d6888b75-5454-498c-8710-e4c22daaf64e",
|
|
29
|
+
partnerName: "Engine Demo Loans Demand Sub Account 1",
|
|
30
|
+
partnerImageUrl: "https://s3.amazonaws.com/images.evenfinancial.com/logos/dev/engine_demo_loans_demand_sub_account-wjksncio.svg",
|
|
31
|
+
recommendationScore: 10,
|
|
32
|
+
payout: 0.1,
|
|
33
|
+
productSubType: "credit_builder",
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
uuid: "full-special-offer-uuid-2",
|
|
37
|
+
name: "Mock Debt Relief Offer",
|
|
38
|
+
desc: "Description of the debt relief offer",
|
|
39
|
+
url: "https://offers.engine.tech/ref/567beb02-9230-4bad-b60a-b2311bad400b",
|
|
40
|
+
partnerName: "Engine Demo Loans Demand Sub Account 2",
|
|
41
|
+
partnerImageUrl: "https://s3.amazonaws.com/images.evenfinancial.com/logos/dev/engine_demo_loans_demand_sub_account_2-wjkurpux.svg",
|
|
42
|
+
recommendationScore: 10,
|
|
43
|
+
payout: 0.1,
|
|
44
|
+
productSubType: "debt_relief",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
uuid: "full-special-offer-uuid-3",
|
|
48
|
+
name: "Mock Installment Loans Offer",
|
|
49
|
+
desc: "Description of the installment loans offer",
|
|
50
|
+
url: "https://offers.engine.tech/ref/ec88908d-bdee-4b5d-8c49-2268167ea3c5",
|
|
51
|
+
partnerName: "Engine Demo Loans Demand Sub Account 3",
|
|
52
|
+
partnerImageUrl: "https://s3.amazonaws.com/images.evenfinancial.com/logos/dev/engine_demo_loans_demand_sub_account_3-wjkuspug.svg",
|
|
53
|
+
recommendationScore: 10,
|
|
54
|
+
payout: 0.1,
|
|
55
|
+
productSubType: "installment_loans",
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
pendingOriginators: [],
|
|
59
|
+
pendingResponses: [],
|
|
60
|
+
};
|
|
61
|
+
const pendingRateTable = {
|
|
62
|
+
uuid: "pending-rate-table-uuid",
|
|
63
|
+
leadUuid: "pending-lead-uuid",
|
|
64
|
+
loanAmount: 20000,
|
|
65
|
+
creditCardOffers: [],
|
|
66
|
+
loanOffers: [],
|
|
67
|
+
mortgageOffers: [],
|
|
68
|
+
savingsOffers: [],
|
|
69
|
+
specialOffers: [
|
|
70
|
+
{
|
|
71
|
+
uuid: "pending-special-offer-uuid",
|
|
72
|
+
name: "Mock Credit Builder Offer",
|
|
73
|
+
desc: "Description of the credit builder offer",
|
|
74
|
+
url: "https://offers.engine.tech/ref/d6888b75-5454-498c-8710-e4c22daaf64e",
|
|
75
|
+
partnerName: "Engine Demo Loans Demand Sub Account 1",
|
|
76
|
+
partnerImageUrl: "https://s3.amazonaws.com/images.evenfinancial.com/logos/dev/engine_demo_loans_demand_sub_account-wjksncio.svg",
|
|
77
|
+
recommendationScore: 10,
|
|
78
|
+
payout: 0.1,
|
|
79
|
+
productSubType: "credit_builder",
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
uuid: "pending-special-offer-uuid-2",
|
|
83
|
+
name: "Mock Debt Relief Offer",
|
|
84
|
+
desc: "Description of the debt relief offer",
|
|
85
|
+
url: "https://offers.engine.tech/ref/567beb02-9230-4bad-b60a-b2311bad400b",
|
|
86
|
+
partnerName: "Engine Demo Loans Demand Sub Account 2",
|
|
87
|
+
partnerImageUrl: "https://s3.amazonaws.com/images.evenfinancial.com/logos/dev/engine_demo_loans_demand_sub_account_2-wjkurpux.svg",
|
|
88
|
+
recommendationScore: 10,
|
|
89
|
+
payout: 0.1,
|
|
90
|
+
productSubType: "debt_relief",
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
uuid: "pending-special-offer-uuid-3",
|
|
94
|
+
name: "Mock Installment Loans Offer",
|
|
95
|
+
desc: "Description of the installment loans offer",
|
|
96
|
+
url: "https://offers.engine.tech/ref/ec88908d-bdee-4b5d-8c49-2268167ea3c5",
|
|
97
|
+
partnerName: "Engine Demo Loans Demand Sub Account 3",
|
|
98
|
+
partnerImageUrl: "https://s3.amazonaws.com/images.evenfinancial.com/logos/dev/engine_demo_loans_demand_sub_account_3-wjkuspug.svg",
|
|
99
|
+
recommendationScore: 10,
|
|
100
|
+
payout: 0.1,
|
|
101
|
+
productSubType: "installment_loans",
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
pendingOriginators: [],
|
|
105
|
+
pendingResponses: [
|
|
106
|
+
{
|
|
107
|
+
partner: {
|
|
108
|
+
uuid: "partner-uuid",
|
|
109
|
+
name: "partner",
|
|
110
|
+
description: "partner-description",
|
|
111
|
+
disclaimer: "partner-disclaimer",
|
|
112
|
+
supportsPreSelect: false,
|
|
113
|
+
shouldDisplayPreSelect: false,
|
|
114
|
+
supportsPersonalizedOffers: false,
|
|
115
|
+
imageUrl: "partner-image",
|
|
116
|
+
},
|
|
117
|
+
productTypes: ["credit_card"],
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
};
|
|
121
|
+
const doublePollUuid = "double-poll-uuid";
|
|
122
|
+
const testHost = "https://engine.com";
|
|
123
|
+
const token = "good_auth_token";
|
|
124
|
+
const handlers = [
|
|
125
|
+
http.get(`${testHost}/originator/rateTables/${pendingRateTable["uuid"]}`, () => {
|
|
126
|
+
return new HttpResponse(JSON.stringify(fullRateTable), {
|
|
127
|
+
status: 200,
|
|
128
|
+
});
|
|
129
|
+
}),
|
|
130
|
+
http.get(`${testHost}/originator/rateTables/${doublePollUuid}`, () => {
|
|
131
|
+
return new HttpResponse(JSON.stringify(pendingRateTable), {
|
|
132
|
+
status: 200,
|
|
133
|
+
});
|
|
134
|
+
}),
|
|
135
|
+
];
|
|
136
|
+
const server = setupServer(...handlers);
|
|
137
|
+
describe("Leads", () => {
|
|
138
|
+
beforeAll(() => server.listen({
|
|
139
|
+
onUnhandledRequest: "error",
|
|
140
|
+
}));
|
|
141
|
+
afterEach(() => server.resetHandlers());
|
|
142
|
+
afterAll(() => server.close());
|
|
143
|
+
test("Either client or host and auth_token must be provided to constructor", () => {
|
|
144
|
+
expect(() => new AsyncRateTable({
|
|
145
|
+
host: testHost,
|
|
146
|
+
auth_token: token,
|
|
147
|
+
uuid: "test-uuid",
|
|
148
|
+
})).not.toThrowError();
|
|
149
|
+
expect(() => new AsyncRateTable({
|
|
150
|
+
client: new Client(testHost, token),
|
|
151
|
+
uuid: "test-uuid",
|
|
152
|
+
})).not.toThrowError();
|
|
153
|
+
expect(() => new AsyncRateTable({ uuid: "test-uuid" })).toThrowError(TypeError);
|
|
154
|
+
});
|
|
155
|
+
test("Either a rate table or a uuid must be provided to constructor", () => {
|
|
156
|
+
expect(() => new AsyncRateTable({ host: testHost, auth_token: token })).toThrowError(TypeError);
|
|
157
|
+
expect(() => new AsyncRateTable({
|
|
158
|
+
host: testHost,
|
|
159
|
+
auth_token: token,
|
|
160
|
+
uuid: "test-uuid",
|
|
161
|
+
})).not.toThrowError();
|
|
162
|
+
expect(() => new AsyncRateTable({
|
|
163
|
+
host: testHost,
|
|
164
|
+
auth_token: token,
|
|
165
|
+
rateTable: fullRateTable,
|
|
166
|
+
})).not.toThrowError();
|
|
167
|
+
});
|
|
168
|
+
test("Providing both a rate table and a uuid to constructor is an error", () => {
|
|
169
|
+
expect(() => new AsyncRateTable({
|
|
170
|
+
host: testHost,
|
|
171
|
+
auth_token: token,
|
|
172
|
+
rateTable: fullRateTable,
|
|
173
|
+
uuid: "test-uuid",
|
|
174
|
+
})).toThrowError(TypeError);
|
|
175
|
+
});
|
|
176
|
+
test("Providing both a client instance and a host/auth_token pair is an error", () => {
|
|
177
|
+
expect(() => new AsyncRateTable({
|
|
178
|
+
client: new Client(testHost, token),
|
|
179
|
+
host: testHost,
|
|
180
|
+
auth_token: token,
|
|
181
|
+
uuid: "test-uuid",
|
|
182
|
+
})).toThrowError(TypeError);
|
|
183
|
+
});
|
|
184
|
+
test("Providing a UUID resolves the rate table.", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
185
|
+
const rateTables = new AsyncRateTable({
|
|
186
|
+
host: testHost,
|
|
187
|
+
auth_token: token,
|
|
188
|
+
uuid: doublePollUuid,
|
|
189
|
+
});
|
|
190
|
+
const resp = yield rateTables.resolve();
|
|
191
|
+
expect(resp).toEqual(fullRateTable);
|
|
192
|
+
}));
|
|
193
|
+
test("Providing a rate table with pending responses resolves the rate table.", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
194
|
+
const rateTables = new AsyncRateTable({
|
|
195
|
+
host: testHost,
|
|
196
|
+
auth_token: token,
|
|
197
|
+
rateTable: pendingRateTable,
|
|
198
|
+
});
|
|
199
|
+
const resp = yield rateTables.resolve();
|
|
200
|
+
expect(resp).toEqual(fullRateTable);
|
|
201
|
+
}));
|
|
202
|
+
test("Providing a rate table with no pending responses resolves the rate table.", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
203
|
+
const rateTables = new AsyncRateTable({
|
|
204
|
+
host: testHost,
|
|
205
|
+
auth_token: token,
|
|
206
|
+
rateTable: fullRateTable,
|
|
207
|
+
});
|
|
208
|
+
const resp = yield rateTables.resolve();
|
|
209
|
+
expect(resp).toEqual(fullRateTable);
|
|
210
|
+
}));
|
|
211
|
+
test("If there are no pending responses no requests are made when resolving.", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
212
|
+
const rateTables = new AsyncRateTable({
|
|
213
|
+
host: testHost,
|
|
214
|
+
auth_token: token,
|
|
215
|
+
rateTable: fullRateTable,
|
|
216
|
+
});
|
|
217
|
+
let requestSent = false;
|
|
218
|
+
server.events.on("request:start", () => {
|
|
219
|
+
requestSent = true;
|
|
220
|
+
});
|
|
221
|
+
yield rateTables.resolve();
|
|
222
|
+
expect(requestSent).toBe(false);
|
|
223
|
+
}));
|
|
224
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@moneylion/engine-api",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Interface to engine.tech API",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"files": ["dist"],
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsc",
|
|
10
|
+
"lint": "prettier . --check && eslint src/",
|
|
11
|
+
"lint:fix": "prettier . --write && eslint --fix src/",
|
|
12
|
+
"test": "npm run build && NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" jest dist/",
|
|
13
|
+
"generate:schema": "openapi-typescript https://even-api-reference.s3.amazonaws.com/releases/branches/public@1.49.0/latest/open-api/public/spec.json -o src/generated/schema.d.ts"
|
|
14
|
+
},
|
|
15
|
+
"author": "",
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@jest/globals": "^29.7.0",
|
|
19
|
+
"@tsconfig/node18": "^18.2.2",
|
|
20
|
+
"@typescript-eslint/eslint-plugin": "^7.3.1",
|
|
21
|
+
"@typescript-eslint/parser": "^7.3.1",
|
|
22
|
+
"eslint": "^8.57.0",
|
|
23
|
+
"eslint-config-prettier": "^9.1.0",
|
|
24
|
+
"eslint-plugin-tsdoc": "^0.2.17",
|
|
25
|
+
"jest": "^29.7.0",
|
|
26
|
+
"msw": "^2.2.10",
|
|
27
|
+
"openapi-typescript": "^6.7.5",
|
|
28
|
+
"prettier": "3.2.5",
|
|
29
|
+
"ts-jest": "^29.1.2"
|
|
30
|
+
},
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=18"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@mojotech/json-type-validation": "^3.1.0",
|
|
36
|
+
"node-fetch": "^3.3.2"
|
|
37
|
+
},
|
|
38
|
+
"prettier": {
|
|
39
|
+
"quoteProps": "consistent",
|
|
40
|
+
"singleQuote": false,
|
|
41
|
+
"tabWidth": 4,
|
|
42
|
+
"trailingComma": "es5",
|
|
43
|
+
"useTabs": true
|
|
44
|
+
}
|
|
45
|
+
}
|