@borrowbetter/arpcsdk 0.1.1 → 0.3.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
@@ -19,14 +19,15 @@ pnpm add @borrowbetter/arpcsdk
19
19
  import { ArpcSDK } from "@borrowbetter/arpcsdk";
20
20
 
21
21
  const sdk = new ArpcSDK({
22
- oauthUrl: "https://oauth.stg.ffngcp.com",
23
- gatewayUrl: "https://apis-gateway-v2.stg.fdrgcp.com",
24
- username: "borrowbetter@seller.com", // OAuth client_id (also the seller_agent_email)
25
- password: process.env.FDR_OAUTH_PASSWORD!,
22
+ environment: "stg", // "dev" | "stg" | "prd" — drives both hosts
23
+ auth: {
24
+ username: "borrowbetter@seller.com", // OAuth client_id (also the seller_agent_email)
25
+ password: process.env.ARPC_OAUTH_PASSWORD!,
26
+ },
26
27
  });
27
28
 
28
- await sdk.authenticate(); // exchange credentials → bearer JWT (~900s TTL)
29
-
29
+ // The first api call authenticates automatically — it exchanges your
30
+ // credentials for a bearer JWT (~900s TTL), caches it, and reuses it.
30
31
  const elig = await sdk.api.checkEligibility({ /* … */ });
31
32
  const faid = elig.data.application?.applicant?.fdr_applicant_id;
32
33
 
@@ -40,9 +41,9 @@ Every operation returns `{ status, data, headers }` and **never throws on a non-
40
41
 
41
42
  Four things to hold in your head:
42
43
 
43
- - **Two hosts.** Auth happens on the **OAuth host** (`POST /v1/token`, HTTP Basic). Everything else goes to the **API gateway**. The SDK routes each call to the right one.
44
+ - **Two hosts, one switch.** Auth happens on the **OAuth host** (`POST /v1/token`, HTTP Basic). Everything else goes to the **API gateway**. You pick an `environment` and the SDK derives both — see [Environments](#environments).
44
45
  - **The passport.** `fdr_applicant_id` is minted once at eligibility and threads through *every* subsequent call. You carry it between operations.
45
- - **Token lifecycle.** `authenticate()` exchanges credentials for a bearer JWT with a **~900s (15 min) TTL**, stored and attached to every `api` call. Re-call to refresh on a long run.
46
+ - **Automatic token lifecycle.** Every `api` call carries a bearer JWT (**~900s / 15 min TTL**) that the SDK manages for you: it exchanges your credentials on the first call, caches the token, attaches it to every request, and re-exchanges ~30s before expiry. Concurrent calls share a single exchange. See [Token caching](#token-caching) to persist or share the token.
46
47
  - **No-throw responses.** Operations return `{ status, data, headers }`. Branch on `status` — don't wrap every call in try/catch.
47
48
 
48
49
  ## The enrollment workflow
@@ -56,7 +57,7 @@ sequenceDiagram
56
57
  participant GW as FDR API gateway
57
58
  participant DS as DocuSign
58
59
 
59
- App->>OAuth: authenticate() — client_credentials
60
+ App->>OAuth: (lazy, on first api call) client_credentials
60
61
  OAuth-->>App: bearer JWT (~900s)
61
62
 
62
63
  Note over App,GW: Phase 1 — identity
@@ -89,7 +90,7 @@ sequenceDiagram
89
90
 
90
91
  ### Step by step
91
92
 
92
- **Auth** — `sdk.authenticate()`. One call up front; refresh if the run runs long.
93
+ **Auth** — handled for you. The SDK authenticates on the first `api` call, caches the token, and refreshes it automatically. See [Token caching](#token-caching) to control where the token lives.
93
94
 
94
95
  **Phase 1 — identity**
95
96
  - `checkEligibility()` — stateless quote (no PII). Its critical job: it **mints `fdr_applicant_id`**. Registration doesn't return the id yet, so call eligibility first. *(FDR is working on making registerV2 return the id — not shipped yet.)*
@@ -141,10 +142,71 @@ sequenceDiagram
141
142
 
142
143
  Key property: **no inactivation.** Transferring does *not* close the digital lead — both stay active, and whichever path enrolls first wins. If retail enrolls first, a later digital enroll returns a duplicate / "already enrolled" error (expected). So calling it early is safe.
143
144
 
145
+ ## Environments
146
+
147
+ `environment` is required and selects both hosts. Note they sit on different domains — the OAuth host is `ffngcp.com`, the gateway is `fdrgcp.com`.
148
+
149
+ | `environment` | OAuth host | API gateway |
150
+ | --- | --- | --- |
151
+ | `"dev"` | `https://oauth.dev.ffngcp.com` | `https://apis-gateway-v2.dev.fdrgcp.com` |
152
+ | `"stg"` | `https://oauth.stg.ffngcp.com` | `https://apis-gateway-v2.stg.fdrgcp.com` |
153
+ | `"prd"` | `https://oauth.prd.ffngcp.com` | `https://apis-gateway-v2.prd.fdrgcp.com` |
154
+
155
+ Credentials are issued per environment — a STG client_id will not authenticate against PRD.
156
+
157
+ Point at a host the table doesn't cover — a local mock, say — by overriding either one via `urls`. Anything you omit still comes from `environment`.
158
+
159
+ ```typescript
160
+ const sdk = new ArpcSDK({
161
+ environment: "prd",
162
+ auth: { username, password },
163
+ urls: { gateway: "http://localhost:8080" }, // oauth still from "prd"
164
+ });
165
+ ```
166
+
167
+ The table is exported as `ARPC_ENDPOINTS` if you need to read a host without constructing an SDK. It's frozen — it backs both host resolution and environment validation, so mutating it isn't a supported way to redirect the SDK. Use `urls` for that.
168
+
169
+ ## Token caching
170
+
171
+ By default the bearer token lives in-memory, per-process — good enough for a single long-running service. Pass `auth.cache` to change where it lives: persist it across restarts, or share one token across workers/instances instead of each exchanging its own.
172
+
173
+ ```typescript
174
+ interface TokenCache {
175
+ get(): Promise<string | null>; // valid token, or null if absent/expired
176
+ set(token: string, expiresAt: Date): Promise<void>;
177
+ }
178
+ ```
179
+
180
+ The contract:
181
+
182
+ - **The cache owns expiry.** `get()` returns `null` once the token is stale; that `null` is what triggers a re-exchange. `expiresAt` is passed to `set()` already carrying the SDK's ~30s refresh skew, so honor it as-is.
183
+ - **The SDK single-flights the exchange** within a process, so a cold burst does one `/v1/token` call. Across processes, a brief overlap where two workers each exchange is possible and harmless — FDR issues independent tokens.
184
+
185
+ ```typescript
186
+ // Example: share the token across a fleet via Redis.
187
+ const cache: TokenCache = {
188
+ async get() {
189
+ const [token, exp] = await redis.mget("arpc:token", "arpc:exp");
190
+ return token && exp && Date.now() < Number(exp) ? token : null;
191
+ },
192
+ async set(token, expiresAt) {
193
+ await redis
194
+ .multi()
195
+ .set("arpc:token", token)
196
+ .set("arpc:exp", String(expiresAt.getTime()))
197
+ .exec();
198
+ },
199
+ };
200
+
201
+ const sdk = new ArpcSDK({
202
+ environment: "stg",
203
+ auth: { username, password, cache },
204
+ });
205
+ ```
206
+
144
207
  ## Development
145
208
 
146
209
  ```bash
147
- cp .env.example .env # fill in STG credentials
148
210
  pnpm install
149
211
  pnpm codegen # regenerate the typed client from the OpenAPI spec
150
212
  pnpm smoke # drive the full flow end-to-end against live STG
@@ -152,6 +214,15 @@ pnpm typecheck
152
214
  pnpm build # codegen + tsup → dist/ (esm + cjs + d.ts)
153
215
  ```
154
216
 
217
+ `pnpm smoke` reads its credentials from `.env` (via dotenv-flow):
218
+
219
+ ```bash
220
+ ARPC_OAUTH_USERNAME=...
221
+ ARPC_OAUTH_PASSWORD=...
222
+ ```
223
+
224
+ These are the smoke script's, not the SDK's — the library never reads `process.env`. The script pins `environment: "stg"` in code rather than taking it from `.env`: it drives a real enrollment against the canned STG test identity, so it must not be pointable at PRD.
225
+
155
226
  The typed client (`src/generated/`) is generated from the committed spec (`openapi/api-v2026.15.0.json`) and is not checked in — `pnpm codegen` (run automatically by `build`) reproduces it. To move to a newer spec, drop the JSON in `openapi/` and update `input.target` in `codegen.ts`.
156
227
 
157
228
  ## Releasing
package/dist/index.d.mts CHANGED
@@ -13,25 +13,50 @@ interface HttpResponse<T> {
13
13
  headers: Headers;
14
14
  }
15
15
 
16
- interface TokenResponse {
17
- access_token: string;
18
- token_type?: string;
19
- expires_in?: number;
20
- }
21
-
22
16
  /**
23
17
  * SDK configuration.
24
18
  *
25
19
  * The library never reads the environment itself — a consumer constructs
26
- * `new ArpcSDK(config)`, which calls `configure()`. The `configFromEnv()` helper
27
- * is a convenience for the dev scripts (smoke / server), which load `.env` via
28
- * dotenv-flow and pass the result in.
29
- */
30
- interface ArpcConfig {
20
+ * `new ArpcSDK(config)`, which calls `configure()`. Where that config comes
21
+ * from (env vars, a secrets manager, a config service) is the caller's business.
22
+ */
23
+ /**
24
+ * Bearer-token store. The SDK authenticates lazily on the first gateway call:
25
+ * it asks `get()` for a still-valid token and, on `null`, exchanges credentials
26
+ * and hands the fresh token to `set()`. Owning expiry is the cache's job —
27
+ * `get()` returns `null` once the token is stale (or near-stale). The default
28
+ * store is in-memory and per-process; supply one to persist across restarts or
29
+ * share across workers.
30
+ */
31
+ interface TokenCache {
32
+ /** Return a valid bearer token, or `null` if absent/expired. */
33
+ get(): Promise<string | null>;
34
+ /** Persist a freshly exchanged token. `expiresAt` already includes the SDK's refresh skew. */
35
+ set(token: string, expiresAt: Date): Promise<void>;
36
+ }
37
+ /** FDR deployment target. Selects both hosts — see `ARPC_ENDPOINTS`. */
38
+ type ArpcEnvironment = "dev" | "stg" | "prd";
39
+ /** The host pair a single environment resolves to. */
40
+ interface ArpcEndpoint {
31
41
  /** OAuth host — token exchange only (`POST /v1/token`). */
32
- oauthUrl: string;
42
+ readonly oauth: string;
33
43
  /** API gateway host — every non-auth call. */
34
- gatewayUrl: string;
44
+ readonly gateway: string;
45
+ }
46
+ /**
47
+ * Per-environment hosts. The two differ by domain, not just subdomain: the
48
+ * OAuth host is `ffngcp.com`, the gateway is `fdrgcp.com`.
49
+ *
50
+ * Frozen: this table backs both host resolution and environment validation, so
51
+ * a consumer mutating it would quietly change SDK behavior.
52
+ */
53
+ declare const ARPC_ENDPOINTS: Readonly<Record<ArpcEnvironment, ArpcEndpoint>>;
54
+ /**
55
+ * Narrow a raw string to an `ArpcEnvironment`. Useful when the value arrives
56
+ * untyped — an env var, a JSON config file, a request payload.
57
+ */
58
+ declare function isArpcEnvironment(value: string): value is ArpcEnvironment;
59
+ interface ArpcAuth {
35
60
  /**
36
61
  * OAuth client_id (HTTP Basic username). Also used as the lead's
37
62
  * `seller_agent_email` — the OAuth identity is a registered DRA agent.
@@ -39,6 +64,24 @@ interface ArpcConfig {
39
64
  username: string;
40
65
  /** OAuth client_secret (HTTP Basic password). */
41
66
  password: string;
67
+ /** Bearer-token store. Defaults to an in-memory, per-process cache. */
68
+ cache?: TokenCache;
69
+ }
70
+ /**
71
+ * Escape hatch — per-host overrides of the `environment` defaults. Omit a key
72
+ * (or leave it `undefined`) to keep that environment's host.
73
+ */
74
+ interface ArpcUrls {
75
+ /** OAuth host — token exchange only (`POST /v1/token`). */
76
+ oauth?: string;
77
+ /** API gateway host — every non-auth call. */
78
+ gateway?: string;
79
+ }
80
+ interface ArpcConfig {
81
+ /** Selects the FDR environment. Drives both hosts. */
82
+ environment: ArpcEnvironment;
83
+ auth: ArpcAuth;
84
+ urls?: ArpcUrls;
42
85
  }
43
86
 
44
87
  /**
@@ -2795,31 +2838,38 @@ interface UwSubmissionV2Response {
2795
2838
  * ARPC SDK — a typed, agnostic client for FDR's ARPC DEX API (Achieve
2796
2839
  * Resolution Partner Connect, Digital Enrollment Experience).
2797
2840
  *
2798
- * Thin by design: it owns the two-host routing, the OAuth token lifecycle, and
2799
- * bearer injection, and exposes the full endpoint surface as typed operations
2800
- * on `sdk.api`. It does not orchestrate the enrollment sequence — the caller
2841
+ * Thin by design: it owns the two-host routing (both derived from
2842
+ * `environment`), the OAuth token lifecycle, and bearer injection, and exposes
2843
+ * the full endpoint surface as typed operations on `sdk.api`. It does not
2844
+ * orchestrate the enrollment sequence — the caller
2801
2845
  * assembles payloads and threads the `fdr_applicant_id` between calls. See
2802
2846
  * `scripts/smoke.ts` for a full end-to-end reference sequence (incl. the program
2803
2847
  * poll loop).
2804
2848
  *
2849
+ * Auth is lazy: the first `api` call exchanges credentials for a bearer JWT and
2850
+ * caches it (~900s TTL), then re-exchanges shortly before it expires. There's no
2851
+ * explicit login step. Supply `auth.cache` to persist the token across restarts
2852
+ * or share it across workers; otherwise it's held in-process.
2853
+ *
2805
2854
  * Operations never throw on a non-2xx: each returns `{ status, data, headers }`,
2806
2855
  * so a business gate (e.g. a UW/DRA readiness failure) comes back as a normal
2807
2856
  * response you inspect via `status`/`data`. Only transport errors throw.
2808
2857
  *
2809
- * Note: config and the bearer token are process-global — construct one `ArpcSDK`
2810
- * per process. A second instance reconfigures the first; instances are not
2811
- * isolated (single-tenant by design).
2858
+ * Note: config and the default token cache are process-global — construct one
2859
+ * `ArpcSDK` per process. A second instance reconfigures the first; instances are
2860
+ * not isolated (single-tenant by design).
2812
2861
  *
2813
2862
  * @example
2814
2863
  * ```typescript
2815
2864
  * const sdk = new ArpcSDK({
2816
- * oauthUrl: "https://oauth.stg.ffngcp.com",
2817
- * gatewayUrl: "https://apis-gateway-v2.stg.fdrgcp.com",
2818
- * username: "borrowbetter@seller.com",
2819
- * password: process.env.FDR_OAUTH_PASSWORD!,
2865
+ * environment: "stg",
2866
+ * auth: {
2867
+ * username: "borrowbetter@seller.com",
2868
+ * password: process.env.ARPC_OAUTH_PASSWORD!,
2869
+ * },
2820
2870
  * });
2821
2871
  *
2822
- * await sdk.authenticate();
2872
+ * // The first call authenticates automatically and caches the token.
2823
2873
  * const elig = await sdk.api.checkEligibility({ ... });
2824
2874
  * const faid = elig.data.application?.applicant?.fdr_applicant_id;
2825
2875
  * ```
@@ -2844,11 +2894,6 @@ declare class ArpcSDK {
2844
2894
  createProgramSummaryTask: (id: string, programSummaryTaskRequest: ProgramSummaryTaskRequest, options?: Partial<RequestConfig> | undefined) => Promise<HttpResponse<ProgramSummaryTaskResponse>>;
2845
2895
  };
2846
2896
  constructor(config: ArpcConfig);
2847
- /**
2848
- * Exchange OAuth client credentials for a bearer JWT and store it for
2849
- * subsequent `api` calls. Token TTL is ~900s — re-call to refresh.
2850
- */
2851
- authenticate(): Promise<TokenResponse>;
2852
2897
  }
2853
2898
 
2854
- export { type ARPCErrorResponse, type Address, type Applicant, type ApplicationSendEmailResponse, type ArpcConfig, ArpcSDK, type AuditInfo, type BankDetails, type BankDetailsInput, type BankUpdateRequest, type Check, CheckType, type CoApplicant, type Consents, type CreateLeadRequest, type CreateLeadResponse, type CreditPullConsent, type DebtAccount, type DraApplicant, type DraApplicantInput, DraApplicantInputSigningType, type DraRequest, type DraResponse, type EligibilityAddress, type EligibilityApplicant, type EligibilityApplication, type EligibilityDebtAccount, type EligibilityRequest, type EligibilityResponse, type ErrorDetails, type FieldValidationError, FieldValidationErrorReasonCode, type HttpResponse, type IneligibleReason, type LeadApplicant, LeadApplicantEmploymentStatus, type LeadApplicantInput, LeadApplicantPayFrequency, type LeadApplication, LeadApplicationCreditPullSource, LeadApplicationFdrOrCss, LeadApplicationFixedIncome, LeadApplicationOwnOrRent, type LeadCondition, LeadConditionActive, type LeadCreditor, type LeadDebtAccount, LeadDebtAccountOwnership, LeadDebtAccountResponsibility, type LeadDistribution, type LeadPatchRequest, type LeadReadiness, type LeadResponse, type LeadTransferRequest, type LeadTransferResponse, LeadTransferResponseResponse, type MailingAddress, type PatchAddress, type PatchApplicant, type PatchCoApplicant, type PatchCondition, type PatchDebtAccount, type PaymentOption, type ProgramRequest, type ProgramSummaryTaskRequest, type ProgramSummaryTaskResponse, type ReadinessCheck, ReadinessCheckType, type ReadinessErrorResponse, ReadinessErrorResponseErrorCode, type ReadinessResponse, type RegisterApplicantResponse, type RegisterApplicantV2Address, type RegisterApplicantV2Request, type SpinwheelNetworkUserRequest, type SpinwheelNetworkUserResponse, type StatelessReadinessRequest, type TokenResponse, type Tracking, type UwSubmissionV2Response };
2899
+ export { type ARPCErrorResponse, ARPC_ENDPOINTS, type Address, type Applicant, type ApplicationSendEmailResponse, type ArpcAuth, type ArpcConfig, type ArpcEndpoint, type ArpcEnvironment, ArpcSDK, type ArpcUrls, type AuditInfo, type BankDetails, type BankDetailsInput, type BankUpdateRequest, type Check, CheckType, type CoApplicant, type Consents, type CreateLeadRequest, type CreateLeadResponse, type CreditPullConsent, type DebtAccount, type DraApplicant, type DraApplicantInput, DraApplicantInputSigningType, type DraRequest, type DraResponse, type EligibilityAddress, type EligibilityApplicant, type EligibilityApplication, type EligibilityDebtAccount, type EligibilityRequest, type EligibilityResponse, type ErrorDetails, type FieldValidationError, FieldValidationErrorReasonCode, type HttpResponse, type IneligibleReason, type LeadApplicant, LeadApplicantEmploymentStatus, type LeadApplicantInput, LeadApplicantPayFrequency, type LeadApplication, LeadApplicationCreditPullSource, LeadApplicationFdrOrCss, LeadApplicationFixedIncome, LeadApplicationOwnOrRent, type LeadCondition, LeadConditionActive, type LeadCreditor, type LeadDebtAccount, LeadDebtAccountOwnership, LeadDebtAccountResponsibility, type LeadDistribution, type LeadPatchRequest, type LeadReadiness, type LeadResponse, type LeadTransferRequest, type LeadTransferResponse, LeadTransferResponseResponse, type MailingAddress, type PatchAddress, type PatchApplicant, type PatchCoApplicant, type PatchCondition, type PatchDebtAccount, type PaymentOption, type ProgramRequest, type ProgramSummaryTaskRequest, type ProgramSummaryTaskResponse, type ReadinessCheck, ReadinessCheckType, type ReadinessErrorResponse, ReadinessErrorResponseErrorCode, type ReadinessResponse, type RegisterApplicantResponse, type RegisterApplicantV2Address, type RegisterApplicantV2Request, type SpinwheelNetworkUserRequest, type SpinwheelNetworkUserResponse, type StatelessReadinessRequest, type TokenCache, type Tracking, type UwSubmissionV2Response, isArpcEnvironment };
package/dist/index.d.ts CHANGED
@@ -13,25 +13,50 @@ interface HttpResponse<T> {
13
13
  headers: Headers;
14
14
  }
15
15
 
16
- interface TokenResponse {
17
- access_token: string;
18
- token_type?: string;
19
- expires_in?: number;
20
- }
21
-
22
16
  /**
23
17
  * SDK configuration.
24
18
  *
25
19
  * The library never reads the environment itself — a consumer constructs
26
- * `new ArpcSDK(config)`, which calls `configure()`. The `configFromEnv()` helper
27
- * is a convenience for the dev scripts (smoke / server), which load `.env` via
28
- * dotenv-flow and pass the result in.
29
- */
30
- interface ArpcConfig {
20
+ * `new ArpcSDK(config)`, which calls `configure()`. Where that config comes
21
+ * from (env vars, a secrets manager, a config service) is the caller's business.
22
+ */
23
+ /**
24
+ * Bearer-token store. The SDK authenticates lazily on the first gateway call:
25
+ * it asks `get()` for a still-valid token and, on `null`, exchanges credentials
26
+ * and hands the fresh token to `set()`. Owning expiry is the cache's job —
27
+ * `get()` returns `null` once the token is stale (or near-stale). The default
28
+ * store is in-memory and per-process; supply one to persist across restarts or
29
+ * share across workers.
30
+ */
31
+ interface TokenCache {
32
+ /** Return a valid bearer token, or `null` if absent/expired. */
33
+ get(): Promise<string | null>;
34
+ /** Persist a freshly exchanged token. `expiresAt` already includes the SDK's refresh skew. */
35
+ set(token: string, expiresAt: Date): Promise<void>;
36
+ }
37
+ /** FDR deployment target. Selects both hosts — see `ARPC_ENDPOINTS`. */
38
+ type ArpcEnvironment = "dev" | "stg" | "prd";
39
+ /** The host pair a single environment resolves to. */
40
+ interface ArpcEndpoint {
31
41
  /** OAuth host — token exchange only (`POST /v1/token`). */
32
- oauthUrl: string;
42
+ readonly oauth: string;
33
43
  /** API gateway host — every non-auth call. */
34
- gatewayUrl: string;
44
+ readonly gateway: string;
45
+ }
46
+ /**
47
+ * Per-environment hosts. The two differ by domain, not just subdomain: the
48
+ * OAuth host is `ffngcp.com`, the gateway is `fdrgcp.com`.
49
+ *
50
+ * Frozen: this table backs both host resolution and environment validation, so
51
+ * a consumer mutating it would quietly change SDK behavior.
52
+ */
53
+ declare const ARPC_ENDPOINTS: Readonly<Record<ArpcEnvironment, ArpcEndpoint>>;
54
+ /**
55
+ * Narrow a raw string to an `ArpcEnvironment`. Useful when the value arrives
56
+ * untyped — an env var, a JSON config file, a request payload.
57
+ */
58
+ declare function isArpcEnvironment(value: string): value is ArpcEnvironment;
59
+ interface ArpcAuth {
35
60
  /**
36
61
  * OAuth client_id (HTTP Basic username). Also used as the lead's
37
62
  * `seller_agent_email` — the OAuth identity is a registered DRA agent.
@@ -39,6 +64,24 @@ interface ArpcConfig {
39
64
  username: string;
40
65
  /** OAuth client_secret (HTTP Basic password). */
41
66
  password: string;
67
+ /** Bearer-token store. Defaults to an in-memory, per-process cache. */
68
+ cache?: TokenCache;
69
+ }
70
+ /**
71
+ * Escape hatch — per-host overrides of the `environment` defaults. Omit a key
72
+ * (or leave it `undefined`) to keep that environment's host.
73
+ */
74
+ interface ArpcUrls {
75
+ /** OAuth host — token exchange only (`POST /v1/token`). */
76
+ oauth?: string;
77
+ /** API gateway host — every non-auth call. */
78
+ gateway?: string;
79
+ }
80
+ interface ArpcConfig {
81
+ /** Selects the FDR environment. Drives both hosts. */
82
+ environment: ArpcEnvironment;
83
+ auth: ArpcAuth;
84
+ urls?: ArpcUrls;
42
85
  }
43
86
 
44
87
  /**
@@ -2795,31 +2838,38 @@ interface UwSubmissionV2Response {
2795
2838
  * ARPC SDK — a typed, agnostic client for FDR's ARPC DEX API (Achieve
2796
2839
  * Resolution Partner Connect, Digital Enrollment Experience).
2797
2840
  *
2798
- * Thin by design: it owns the two-host routing, the OAuth token lifecycle, and
2799
- * bearer injection, and exposes the full endpoint surface as typed operations
2800
- * on `sdk.api`. It does not orchestrate the enrollment sequence — the caller
2841
+ * Thin by design: it owns the two-host routing (both derived from
2842
+ * `environment`), the OAuth token lifecycle, and bearer injection, and exposes
2843
+ * the full endpoint surface as typed operations on `sdk.api`. It does not
2844
+ * orchestrate the enrollment sequence — the caller
2801
2845
  * assembles payloads and threads the `fdr_applicant_id` between calls. See
2802
2846
  * `scripts/smoke.ts` for a full end-to-end reference sequence (incl. the program
2803
2847
  * poll loop).
2804
2848
  *
2849
+ * Auth is lazy: the first `api` call exchanges credentials for a bearer JWT and
2850
+ * caches it (~900s TTL), then re-exchanges shortly before it expires. There's no
2851
+ * explicit login step. Supply `auth.cache` to persist the token across restarts
2852
+ * or share it across workers; otherwise it's held in-process.
2853
+ *
2805
2854
  * Operations never throw on a non-2xx: each returns `{ status, data, headers }`,
2806
2855
  * so a business gate (e.g. a UW/DRA readiness failure) comes back as a normal
2807
2856
  * response you inspect via `status`/`data`. Only transport errors throw.
2808
2857
  *
2809
- * Note: config and the bearer token are process-global — construct one `ArpcSDK`
2810
- * per process. A second instance reconfigures the first; instances are not
2811
- * isolated (single-tenant by design).
2858
+ * Note: config and the default token cache are process-global — construct one
2859
+ * `ArpcSDK` per process. A second instance reconfigures the first; instances are
2860
+ * not isolated (single-tenant by design).
2812
2861
  *
2813
2862
  * @example
2814
2863
  * ```typescript
2815
2864
  * const sdk = new ArpcSDK({
2816
- * oauthUrl: "https://oauth.stg.ffngcp.com",
2817
- * gatewayUrl: "https://apis-gateway-v2.stg.fdrgcp.com",
2818
- * username: "borrowbetter@seller.com",
2819
- * password: process.env.FDR_OAUTH_PASSWORD!,
2865
+ * environment: "stg",
2866
+ * auth: {
2867
+ * username: "borrowbetter@seller.com",
2868
+ * password: process.env.ARPC_OAUTH_PASSWORD!,
2869
+ * },
2820
2870
  * });
2821
2871
  *
2822
- * await sdk.authenticate();
2872
+ * // The first call authenticates automatically and caches the token.
2823
2873
  * const elig = await sdk.api.checkEligibility({ ... });
2824
2874
  * const faid = elig.data.application?.applicant?.fdr_applicant_id;
2825
2875
  * ```
@@ -2844,11 +2894,6 @@ declare class ArpcSDK {
2844
2894
  createProgramSummaryTask: (id: string, programSummaryTaskRequest: ProgramSummaryTaskRequest, options?: Partial<RequestConfig> | undefined) => Promise<HttpResponse<ProgramSummaryTaskResponse>>;
2845
2895
  };
2846
2896
  constructor(config: ArpcConfig);
2847
- /**
2848
- * Exchange OAuth client credentials for a bearer JWT and store it for
2849
- * subsequent `api` calls. Token TTL is ~900s — re-call to refresh.
2850
- */
2851
- authenticate(): Promise<TokenResponse>;
2852
2897
  }
2853
2898
 
2854
- export { type ARPCErrorResponse, type Address, type Applicant, type ApplicationSendEmailResponse, type ArpcConfig, ArpcSDK, type AuditInfo, type BankDetails, type BankDetailsInput, type BankUpdateRequest, type Check, CheckType, type CoApplicant, type Consents, type CreateLeadRequest, type CreateLeadResponse, type CreditPullConsent, type DebtAccount, type DraApplicant, type DraApplicantInput, DraApplicantInputSigningType, type DraRequest, type DraResponse, type EligibilityAddress, type EligibilityApplicant, type EligibilityApplication, type EligibilityDebtAccount, type EligibilityRequest, type EligibilityResponse, type ErrorDetails, type FieldValidationError, FieldValidationErrorReasonCode, type HttpResponse, type IneligibleReason, type LeadApplicant, LeadApplicantEmploymentStatus, type LeadApplicantInput, LeadApplicantPayFrequency, type LeadApplication, LeadApplicationCreditPullSource, LeadApplicationFdrOrCss, LeadApplicationFixedIncome, LeadApplicationOwnOrRent, type LeadCondition, LeadConditionActive, type LeadCreditor, type LeadDebtAccount, LeadDebtAccountOwnership, LeadDebtAccountResponsibility, type LeadDistribution, type LeadPatchRequest, type LeadReadiness, type LeadResponse, type LeadTransferRequest, type LeadTransferResponse, LeadTransferResponseResponse, type MailingAddress, type PatchAddress, type PatchApplicant, type PatchCoApplicant, type PatchCondition, type PatchDebtAccount, type PaymentOption, type ProgramRequest, type ProgramSummaryTaskRequest, type ProgramSummaryTaskResponse, type ReadinessCheck, ReadinessCheckType, type ReadinessErrorResponse, ReadinessErrorResponseErrorCode, type ReadinessResponse, type RegisterApplicantResponse, type RegisterApplicantV2Address, type RegisterApplicantV2Request, type SpinwheelNetworkUserRequest, type SpinwheelNetworkUserResponse, type StatelessReadinessRequest, type TokenResponse, type Tracking, type UwSubmissionV2Response };
2899
+ export { type ARPCErrorResponse, ARPC_ENDPOINTS, type Address, type Applicant, type ApplicationSendEmailResponse, type ArpcAuth, type ArpcConfig, type ArpcEndpoint, type ArpcEnvironment, ArpcSDK, type ArpcUrls, type AuditInfo, type BankDetails, type BankDetailsInput, type BankUpdateRequest, type Check, CheckType, type CoApplicant, type Consents, type CreateLeadRequest, type CreateLeadResponse, type CreditPullConsent, type DebtAccount, type DraApplicant, type DraApplicantInput, DraApplicantInputSigningType, type DraRequest, type DraResponse, type EligibilityAddress, type EligibilityApplicant, type EligibilityApplication, type EligibilityDebtAccount, type EligibilityRequest, type EligibilityResponse, type ErrorDetails, type FieldValidationError, FieldValidationErrorReasonCode, type HttpResponse, type IneligibleReason, type LeadApplicant, LeadApplicantEmploymentStatus, type LeadApplicantInput, LeadApplicantPayFrequency, type LeadApplication, LeadApplicationCreditPullSource, LeadApplicationFdrOrCss, LeadApplicationFixedIncome, LeadApplicationOwnOrRent, type LeadCondition, LeadConditionActive, type LeadCreditor, type LeadDebtAccount, LeadDebtAccountOwnership, LeadDebtAccountResponsibility, type LeadDistribution, type LeadPatchRequest, type LeadReadiness, type LeadResponse, type LeadTransferRequest, type LeadTransferResponse, LeadTransferResponseResponse, type MailingAddress, type PatchAddress, type PatchApplicant, type PatchCoApplicant, type PatchCondition, type PatchDebtAccount, type PaymentOption, type ProgramRequest, type ProgramSummaryTaskRequest, type ProgramSummaryTaskResponse, type ReadinessCheck, ReadinessCheckType, type ReadinessErrorResponse, ReadinessErrorResponseErrorCode, type ReadinessResponse, type RegisterApplicantResponse, type RegisterApplicantV2Address, type RegisterApplicantV2Request, type SpinwheelNetworkUserRequest, type SpinwheelNetworkUserResponse, type StatelessReadinessRequest, type TokenCache, type Tracking, type UwSubmissionV2Response, isArpcEnvironment };
package/dist/index.js CHANGED
@@ -6,12 +6,39 @@ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
6
6
 
7
7
  var ky__default = /*#__PURE__*/_interopDefault(ky);
8
8
 
9
- // src/auth.ts
10
-
11
9
  // src/config.ts
10
+ var ARPC_ENDPOINTS = Object.freeze({
11
+ dev: Object.freeze({
12
+ oauth: "https://oauth.dev.ffngcp.com",
13
+ gateway: "https://apis-gateway-v2.dev.fdrgcp.com"
14
+ }),
15
+ stg: Object.freeze({
16
+ oauth: "https://oauth.stg.ffngcp.com",
17
+ gateway: "https://apis-gateway-v2.stg.fdrgcp.com"
18
+ }),
19
+ prd: Object.freeze({
20
+ oauth: "https://oauth.prd.ffngcp.com",
21
+ gateway: "https://apis-gateway-v2.prd.fdrgcp.com"
22
+ })
23
+ });
24
+ function isArpcEnvironment(value) {
25
+ return Object.keys(ARPC_ENDPOINTS).includes(value);
26
+ }
12
27
  var current = null;
28
+ function resolveUrls(config) {
29
+ if (!isArpcEnvironment(config.environment)) {
30
+ throw new Error(
31
+ `Unknown environment: ${config.environment} (expected one of ${Object.keys(ARPC_ENDPOINTS).join(", ")})`
32
+ );
33
+ }
34
+ const defaults = ARPC_ENDPOINTS[config.environment];
35
+ return {
36
+ oauth: config.urls?.oauth ?? defaults.oauth,
37
+ gateway: config.urls?.gateway ?? defaults.gateway
38
+ };
39
+ }
13
40
  function configure(config) {
14
- current = config;
41
+ current = { ...config, urls: resolveUrls(config) };
15
42
  }
16
43
  function getConfig() {
17
44
  if (!current) {
@@ -21,14 +48,54 @@ function getConfig() {
21
48
  }
22
49
  return current;
23
50
  }
24
- var bearerToken = null;
25
- function setBearerToken(token) {
26
- bearerToken = token;
51
+ var TOKEN_SKEW_MS = 3e4;
52
+ var DEFAULT_TTL_S = 900;
53
+ async function exchangeToken() {
54
+ const { urls, auth } = getConfig();
55
+ const basic = Buffer.from(`${auth.username}:${auth.password}`).toString(
56
+ "base64"
57
+ );
58
+ return ky__default.default.post(`${urls.oauth}/v1/token`, {
59
+ headers: { Authorization: `Basic ${basic}` },
60
+ body: new URLSearchParams({ grant_type: "client_credentials" })
61
+ }).json();
62
+ }
63
+ var memToken = null;
64
+ var memExpiresAt = 0;
65
+ var defaultCache = {
66
+ async get() {
67
+ return memToken && Date.now() < memExpiresAt ? memToken : null;
68
+ },
69
+ async set(token, expiresAt) {
70
+ memToken = token;
71
+ memExpiresAt = expiresAt.getTime();
72
+ }
73
+ };
74
+ var inflight = null;
75
+ function resolveToken() {
76
+ const store = getConfig().auth.cache ?? defaultCache;
77
+ if (inflight) return inflight;
78
+ inflight = (async () => {
79
+ const cached = await store.get();
80
+ if (cached) return cached;
81
+ const res = await exchangeToken();
82
+ const ttl = (res.expires_in ?? DEFAULT_TTL_S) * 1e3;
83
+ await store.set(
84
+ res.access_token,
85
+ new Date(Date.now() + ttl - TOKEN_SKEW_MS)
86
+ );
87
+ return res.access_token;
88
+ })().finally(() => {
89
+ inflight = null;
90
+ });
91
+ return inflight;
27
92
  }
93
+
94
+ // src/http/client.ts
28
95
  var customInstance = async (request, options) => {
29
96
  const { url, method, params, data, signal } = { ...request, ...options };
30
97
  const headers = { ...request.headers, ...options?.headers };
31
- const target = new URL(url, getConfig().gatewayUrl);
98
+ const target = new URL(url, getConfig().urls.gateway);
32
99
  if (params) {
33
100
  for (const [key, value] of Object.entries(params)) {
34
101
  if (value !== void 0 && value !== null)
@@ -37,9 +104,16 @@ var customInstance = async (request, options) => {
37
104
  }
38
105
  const response = await ky__default.default(target, {
39
106
  method,
40
- headers: {
41
- ...headers,
42
- ...bearerToken ? { Authorization: `Bearer ${bearerToken}` } : {}
107
+ headers,
108
+ hooks: {
109
+ // Lazy bearer injection: resolveToken() exchanges on the first call
110
+ // and refreshes once the cached token goes stale. The OAuth exchange
111
+ // itself runs on a bare ky.post (no hook), so there's no recursion.
112
+ beforeRequest: [
113
+ async (req) => {
114
+ req.headers.set("Authorization", `Bearer ${await resolveToken()}`);
115
+ }
116
+ ]
43
117
  },
44
118
  ...data !== void 0 ? { json: data } : {},
45
119
  signal,
@@ -60,18 +134,6 @@ var customInstance = async (request, options) => {
60
134
  return { status: response.status, data: body, headers: response.headers };
61
135
  };
62
136
 
63
- // src/auth.ts
64
- async function exchangeToken() {
65
- const { oauthUrl, username, password } = getConfig();
66
- const basic = Buffer.from(`${username}:${password}`).toString("base64");
67
- const token = await ky__default.default.post(`${oauthUrl}/v1/token`, {
68
- headers: { Authorization: `Basic ${basic}` },
69
- body: new URLSearchParams({ grant_type: "client_credentials" })
70
- }).json();
71
- setBearerToken(token.access_token);
72
- return token;
73
- }
74
-
75
137
  // src/generated/arpc.ts
76
138
  var getARPCAchieveResolutionPartnerConnectAPI = () => {
77
139
  const createLeadSync = (createLeadRequest, options) => {
@@ -241,15 +303,10 @@ var ArpcSDK = class {
241
303
  this.api = getARPCAchieveResolutionPartnerConnectAPI();
242
304
  configure(config);
243
305
  }
244
- /**
245
- * Exchange OAuth client credentials for a bearer JWT and store it for
246
- * subsequent `api` calls. Token TTL is ~900s — re-call to refresh.
247
- */
248
- authenticate() {
249
- return exchangeToken();
250
- }
251
306
  };
252
307
 
308
+ exports.ARPC_ENDPOINTS = ARPC_ENDPOINTS;
253
309
  exports.ArpcSDK = ArpcSDK;
310
+ exports.isArpcEnvironment = isArpcEnvironment;
254
311
  //# sourceMappingURL=index.js.map
255
312
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/config.ts","../src/http/client.ts","../src/auth.ts","../src/generated/arpc.ts","../src/index.ts"],"names":["ky"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * SDK configuration.\n *\n * The library never reads the environment itself — a consumer constructs\n * `new ArpcSDK(config)`, which calls `configure()`. The `configFromEnv()` helper\n * is a convenience for the dev scripts (smoke / server), which load `.env` via\n * dotenv-flow and pass the result in.\n */\nexport interface ArpcConfig {\n\t/** OAuth host — token exchange only (`POST /v1/token`). */\n\toauthUrl: string;\n\t/** API gateway host — every non-auth call. */\n\tgatewayUrl: string;\n\t/**\n\t * OAuth client_id (HTTP Basic username). Also used as the lead's\n\t * `seller_agent_email` — the OAuth identity is a registered DRA agent.\n\t */\n\tusername: string;\n\t/** OAuth client_secret (HTTP Basic password). */\n\tpassword: string;\n}\n\nlet current: ArpcConfig | null = null;\n\n/** Set the active config. Called by the `ArpcSDK` constructor. */\nexport function configure(config: ArpcConfig): void {\n\tcurrent = config;\n}\n\n/** Read the active config. Throws if the SDK hasn't been constructed yet. */\nexport function getConfig(): ArpcConfig {\n\tif (!current) {\n\t\tthrow new Error(\n\t\t\t\"ArpcSDK is not configured — construct `new ArpcSDK(config)` before making calls\",\n\t\t);\n\t}\n\treturn current;\n}\n\n/**\n * Build a config from environment variables. Not used by the library itself —\n * only the dev scripts call this after loading `.env`.\n */\nexport function configFromEnv(\n\tenv: NodeJS.ProcessEnv = process.env,\n): ArpcConfig {\n\tconst need = (key: string): string => {\n\t\tconst value = env[key];\n\t\tif (!value)\n\t\t\tthrow new Error(`Missing required env var: ${key} (set it in .env)`);\n\t\treturn value;\n\t};\n\treturn {\n\t\toauthUrl: need(\"FDR_OAUTH_URL\"),\n\t\tgatewayUrl: need(\"FDR_API_GATEWAY_URL\"),\n\t\tusername: need(\"FDR_OAUTH_USERNAME\"),\n\t\tpassword: need(\"FDR_OAUTH_PASSWORD\"),\n\t};\n}\n","import ky from \"ky\";\nimport { getConfig } from \"../config\";\n\n// ---------------------------------------------------------------------------\n// Orval custom mutator (ky-backed).\n//\n// Every generated operation calls `customInstance<T>(config, options)`. We\n// route all calls to the API gateway, inject the bearer token, and return a\n// small { status, data, headers } wrapper WITHOUT throwing on non-2xx — the\n// caller inspects status codes itself (202 + Retry-After on program, 400 on a\n// UW/DRA gate, etc.).\n// ---------------------------------------------------------------------------\n\nlet bearerToken: string | null = null;\nexport function setBearerToken(token: string | null): void {\n\tbearerToken = token;\n}\n\n// Shape Orval passes as the first arg (axios-style request config).\nexport interface RequestConfig {\n\turl: string;\n\tmethod: string;\n\tparams?: Record<string, unknown>;\n\tdata?: unknown;\n\theaders?: Record<string, string>;\n\tsignal?: AbortSignal;\n\tresponseType?: string;\n}\n\nexport interface HttpResponse<T> {\n\tstatus: number;\n\tdata: T;\n\theaders: Headers;\n}\n\nexport const customInstance = async <T>(\n\trequest: RequestConfig,\n\toptions?: Partial<RequestConfig>,\n): Promise<HttpResponse<T>> => {\n\tconst { url, method, params, data, signal } = { ...request, ...options };\n\t// Merge headers from both sides — a per-call override (e.g. an\n\t// x-correlation-id in `options.headers`) must not drop the operation's\n\t// Content-Type. The bearer token is layered on last, below.\n\tconst headers = { ...request.headers, ...options?.headers };\n\n\tconst target = new URL(url, getConfig().gatewayUrl);\n\tif (params) {\n\t\tfor (const [key, value] of Object.entries(params)) {\n\t\t\tif (value !== undefined && value !== null)\n\t\t\t\ttarget.searchParams.set(key, String(value));\n\t\t}\n\t}\n\n\tconst response = await ky(target, {\n\t\tmethod,\n\t\theaders: {\n\t\t\t...headers,\n\t\t\t...(bearerToken ? { Authorization: `Bearer ${bearerToken}` } : {}),\n\t\t},\n\t\t...(data !== undefined ? { json: data } : {}),\n\t\tsignal,\n\t\tthrowHttpErrors: false,\n\t\tretry: 0,\n\t\t// Some gateway calls (program generation in particular) can take well over ky's\n\t\t// 10s default to respond. Raise the per-request ceiling; the program poll loop\n\t\t// handles 202/Retry-After on top of this.\n\t\ttimeout: 60_000,\n\t});\n\n\tconst text = await response.text();\n\tlet body: T;\n\ttry {\n\t\tbody = (text ? JSON.parse(text) : undefined) as T;\n\t} catch {\n\t\t// Non-JSON body (e.g. a gateway HTML error page on a transient 5xx). Don't throw —\n\t\t// hand back the raw text so callers can inspect status/body and retry as needed.\n\t\tbody = text as unknown as T;\n\t}\n\treturn { status: response.status, data: body, headers: response.headers };\n};\n","import ky from \"ky\";\nimport { getConfig } from \"./config\";\nimport { setBearerToken } from \"./http/client\";\n\n// Token exchange lives in the orchestration layer, not the generated client:\n// it targets the OAuth host (not the gateway), uses HTTP Basic, and is\n// form-encoded. We store the resulting JWT for the mutator to attach.\nexport interface TokenResponse {\n\taccess_token: string;\n\ttoken_type?: string;\n\texpires_in?: number;\n}\n\nexport async function exchangeToken(): Promise<TokenResponse> {\n\tconst { oauthUrl, username, password } = getConfig();\n\tconst basic = Buffer.from(`${username}:${password}`).toString(\"base64\");\n\n\tconst token = await ky\n\t\t.post(`${oauthUrl}/v1/token`, {\n\t\t\theaders: { Authorization: `Basic ${basic}` },\n\t\t\tbody: new URLSearchParams({ grant_type: \"client_credentials\" }),\n\t\t})\n\t\t.json<TokenResponse>();\n\n\tsetBearerToken(token.access_token);\n\treturn token;\n}\n","/**\n * Generated by orval v7.21.0 🍺\n * Do not edit manually.\n * ARPC — Achieve Resolution Partner Connect API\n * FDR Wholesale API — A Hybrid Integration Model designed to maximise partner velocity without compromising enterprise data standards. DEX (Digital Enrollment Experience) is the partner-facing integration layer that powers the full applicant lifecycle. The `fdr_applicant_id` acts as the applicant's passport across all systems, persisted from first eligibility check through enrollment. All endpoints require a valid Bearer JWT with the `arpc.all` role.\n * OpenAPI spec version: 2026.15.0\n */\nimport type {\n ARPCErrorResponse,\n ApplicationSendEmailResponse,\n BankUpdateRequest,\n CreateLeadRequest,\n CreateLeadResponse,\n DraRequest,\n DraResponse,\n EligibilityRequest,\n EligibilityResponse,\n LeadPatchRequest,\n LeadResponse,\n LeadTransferRequest,\n LeadTransferResponse,\n ProgramRequest,\n ProgramSummaryTaskRequest,\n ProgramSummaryTaskResponse,\n ReadinessResponse,\n RegisterApplicantResponse,\n RegisterApplicantV2Request,\n SpinwheelNetworkUserRequest,\n SpinwheelNetworkUserResponse,\n StatelessReadinessRequest,\n UwSubmissionV2Response\n} from './model';\n\nimport { customInstance } from '../http/client';\ntype SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];\n\n\n export const getARPCAchieveResolutionPartnerConnectAPI = () => {\n/**\n * Synchronous Orchestration: Handles both lead creation and income/expense data in one step, eliminating the async polling black box. Data authority shifts here — Achieve (SUIP SF) becomes the Source of Truth once the lead is formally promoted. Returns the full lead creation result including program options.\n * @summary Sync wholesale lead creation\n */\nconst createLeadSync = (\n createLeadRequest: CreateLeadRequest,\n options?: SecondParameter<typeof customInstance<CreateLeadResponse>>,) => {\n return customInstance<CreateLeadResponse>(\n {url: `/v3/application`, method: 'POST',\n headers: {'Content-Type': 'application/json', },\n data: createLeadRequest\n },\n options);\n }\n \n/**\n * Stateless Readiness Check: Pre-flight validation before initiating program calculations. Catches duplicate SSNs or emails early to prevent downstream failures. Requires a valid Authorization header.\n * @summary Check application readiness\n */\nconst checkApplicationReadiness = (\n statelessReadinessRequest: StatelessReadinessRequest,\n options?: SecondParameter<typeof customInstance<ReadinessResponse>>,) => {\n return customInstance<ReadinessResponse>(\n {url: `/v1/application/readiness`, method: 'POST',\n headers: {'Content-Type': 'application/json', },\n data: statelessReadinessRequest\n },\n options);\n }\n \n/**\n * Recovery endpoint — only required when the async Spinwheel user creation triggered by `POST /v1/applicant/register` fails due to a user error (e.g. invalid SSN or DOB). Not needed in the happy path. Authenticates the seller, resolves the law firm / program, creates an applicant ID when needed, and connects the applicant to the Spinwheel network. Requires a valid Authorization header.\n * @summary Create / connect Spinwheel network user\n */\nconst createSpinwheelNetworkUser = (\n spinwheelNetworkUserRequest: SpinwheelNetworkUserRequest,\n options?: SecondParameter<typeof customInstance<SpinwheelNetworkUserResponse>>,) => {\n return customInstance<SpinwheelNetworkUserResponse>(\n {url: `/v1/application/spinwheel-network-user`, method: 'POST',\n headers: {'Content-Type': 'application/json', },\n data: spinwheelNetworkUserRequest\n },\n options);\n }\n \n/**\n * Authenticates the seller, validates banking disclosure requirements, optionally validates the submitted account and routing numbers, patches the program, and returns the refreshed lead payload. Requires a valid Authorization header.\n * @summary Update applicant bank details\n */\nconst updateApplicationBankDetails = (\n applicantId: string,\n bankUpdateRequest: BankUpdateRequest,\n options?: SecondParameter<typeof customInstance<LeadResponse>>,) => {\n return customInstance<LeadResponse>(\n {url: `/v1/application/bank-update/${applicantId}`, method: 'PATCH',\n headers: {'Content-Type': 'application/json', },\n data: bankUpdateRequest\n },\n options);\n }\n \n/**\n * Banking Disclosure: Manually triggers the banking disclosure email. **This endpoint is optional** — it is only needed when the disclosure was not already sent automatically by `POST /v2/application/uw-submission/{fdrApplicantId}` (which auto-sends on full UW approval). Use this endpoint for conditional approvals or when the v1 UW submission path is in use. A critical legal prerequisite that must be completed before enrollment can proceed. Requires a valid Authorization header.\n * @summary Send banking disclosure email (optional — only if not auto-sent by v2 UW submission)\n */\nconst sendEmail = (\n fdrApplicantId: string,\n options?: SecondParameter<typeof customInstance<ApplicationSendEmailResponse>>,) => {\n return customInstance<ApplicationSendEmailResponse>(\n {url: `/v1/application/send-email/${fdrApplicantId}`, method: 'POST'\n },\n options);\n }\n \n/**\n * Submits a lead evaluation request. fdr_applicant_id is required in the request body. Requires a valid Authorization header with the arpc.all role.\n * @summary Transfer a lead to the debt-resolution program\n */\nconst leadTransfer = (\n leadTransferRequest: LeadTransferRequest,\n options?: SecondParameter<typeof customInstance<LeadTransferResponse>>,) => {\n return customInstance<LeadTransferResponse>(\n {url: `/v1/application/lead-transfer`, method: 'POST',\n headers: {'Content-Type': 'application/json', },\n data: leadTransferRequest\n },\n options);\n }\n \n/**\n * Fetches the current state of an application / lead record by FDR applicant ID. Requires a valid Authorization header.\n * @summary Read application / lead details\n */\nconst readLead = (\n fdrApplicantId: string,\n options?: SecondParameter<typeof customInstance<LeadResponse>>,) => {\n return customInstance<LeadResponse>(\n {url: `/v1/application/${fdrApplicantId}`, method: 'GET'\n },\n options);\n }\n \n/**\n * Applies a partial update to an existing wholesale lead record. Requires a valid Authorization header and an x-correlation-id header for request tracing.\n * @summary Partial update of application / lead record\n */\nconst patchLead = (\n fdrApplicantId: string,\n leadPatchRequest: LeadPatchRequest,\n options?: SecondParameter<typeof customInstance<LeadResponse>>,) => {\n return customInstance<LeadResponse>(\n {url: `/v1/application/${fdrApplicantId}`, method: 'PATCH',\n headers: {'Content-Type': 'application/json', },\n data: leadPatchRequest\n },\n options);\n }\n \n/**\n * DRA Readiness Check: Final pre-flight confirming the lead is approved and all prerequisites are met before initiating the embedded signing session. Requires a valid Authorization header.\n * @summary Get application readiness status\n */\nconst getApplicationReadiness = (\n applicantId: string,\n options?: SecondParameter<typeof customInstance<ReadinessResponse>>,) => {\n return customInstance<ReadinessResponse>(\n {url: `/v1/application/${applicantId}/readiness`, method: 'GET'\n },\n options);\n }\n \n/**\n * Embedded DRA Signing: Returns an Embedded Signing URL so the applicant signs the Debt Resolution Agreement via DocuSign directly within the Symple UI, preventing session breakage. Successful legal execution triggers automatic enrollment. Requires a valid Authorization header.\n * @summary Generate Debt Resolution Agreement (DRA)\n */\nconst generateDra = (\n applicantId: string,\n draRequest: DraRequest,\n options?: SecondParameter<typeof customInstance<DraResponse>>,) => {\n return customInstance<DraResponse>(\n {url: `/v2/application/${applicantId}/dra`, method: 'POST',\n headers: {'Content-Type': 'application/json', },\n data: draRequest\n },\n options);\n }\n \n/**\n * **Recommended endpoint.** `POST /v1/applicant/register` is deprecated and will be retired by end of July 2026 (deadline can be extended on request if your team needs more time). Identity Anchoring: Persists applicant PII to the Managed Package and generates the `fdr_applicant_id` — the applicant's passport across all systems. Routes to the correct Salesforce target (MP or WiB) based on seller identity, and triggers the async credit fetch. Requires a valid Authorization header.\n * @summary Seller-aware applicant registration (v2 — unified ARPCErrorResponse envelope) — recommended\n */\nconst registerApplicantV2 = (\n registerApplicantV2Request: RegisterApplicantV2Request,\n options?: SecondParameter<typeof customInstance<RegisterApplicantResponse>>,) => {\n return customInstance<RegisterApplicantResponse>(\n {url: `/v2/applicant/register`, method: 'POST',\n headers: {'Content-Type': 'application/json', },\n data: registerApplicantV2Request\n },\n options);\n }\n \n/**\n * Async Polling Loop: Call this single endpoint for both initial generation and refresh. Returns 202 Accepted with a Retry-After header while credit is still pending. Once ready, the Managed Package orchestrates the calculation and persists the program result. Requires a valid Authorization header.\n * @summary Generate application program (v1)\n */\nconst generateProgramV1 = (\n programRequest: ProgramRequest,\n options?: SecondParameter<typeof customInstance<LeadResponse>>,) => {\n return customInstance<LeadResponse>(\n {url: `/v1/application/program`, method: 'POST',\n headers: {'Content-Type': 'application/json', },\n data: programRequest\n },\n options);\n }\n \n/**\n * Eligibility Scout: A stateless, real-time quote based on anonymised data — no PII required. Returns estimated program options to anchor the conversation before any registration occurs. Requires a valid Authorization header.\n * @summary Check application eligibility\n */\nconst checkEligibility = (\n eligibilityRequest: EligibilityRequest,\n options?: SecondParameter<typeof customInstance<EligibilityResponse>>,) => {\n return customInstance<EligibilityResponse>(\n {url: `/v2/application/eligibility`, method: 'POST',\n headers: {'Content-Type': 'application/json', },\n data: eligibilityRequest\n },\n options);\n }\n \n/**\n * **Recommended endpoint — supersedes v1.** Submits a wholesale application to the underwriting system. On full UW approval (`sub_status = 'UW Approved'`) the banking disclosure email is sent automatically — no separate `send-email` call is needed in this path. Conditional approvals suppress the auto-send; in that case the manual `POST /v1/application/send-email/{fdrApplicantId}` endpoint can be used. Uses the SF Object API directly (no suip-proxy hop). Requires a valid Authorization header.\n * @summary Submit application to underwriting (v2 — auto banking disclosure) — recommended\n */\nconst uwSubmissionV2 = (\n fdrApplicantId: string,\n options?: SecondParameter<typeof customInstance<UwSubmissionV2Response>>,) => {\n return customInstance<UwSubmissionV2Response>(\n {url: `/v2/application/uw-submission/${fdrApplicantId}`, method: 'POST'\n },\n options);\n }\n \n/**\n * Creates a SUIP Salesforce 'Program Summary Recording' task for the applicant identified by the {id} path variable. For DEX (or missing header), the parent SF opportunity is additionally PATCHed with the seller-config partner default PSC. For DC, the task is created and the parent SF opportunity is PATCHed only when ws_psc_code is provided in the request payload.\n * @summary Create program-summary recording task\n */\nconst createProgramSummaryTask = (\n id: string,\n programSummaryTaskRequest: ProgramSummaryTaskRequest,\n options?: SecondParameter<typeof customInstance<ProgramSummaryTaskResponse>>,) => {\n return customInstance<ProgramSummaryTaskResponse>(\n {url: `/v2/application/program-summary-task/${id}`, method: 'POST',\n headers: {'Content-Type': 'application/json', },\n data: programSummaryTaskRequest\n },\n options);\n }\n \nreturn {createLeadSync,checkApplicationReadiness,createSpinwheelNetworkUser,updateApplicationBankDetails,sendEmail,leadTransfer,readLead,patchLead,getApplicationReadiness,generateDra,registerApplicantV2,generateProgramV1,checkEligibility,uwSubmissionV2,createProgramSummaryTask}};\nexport type CreateLeadSyncResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['createLeadSync']>>>\nexport type CheckApplicationReadinessResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['checkApplicationReadiness']>>>\nexport type CreateSpinwheelNetworkUserResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['createSpinwheelNetworkUser']>>>\nexport type UpdateApplicationBankDetailsResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['updateApplicationBankDetails']>>>\nexport type SendEmailResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['sendEmail']>>>\nexport type LeadTransferResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['leadTransfer']>>>\nexport type ReadLeadResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['readLead']>>>\nexport type PatchLeadResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['patchLead']>>>\nexport type GetApplicationReadinessResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['getApplicationReadiness']>>>\nexport type GenerateDraResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['generateDra']>>>\nexport type RegisterApplicantV2Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['registerApplicantV2']>>>\nexport type GenerateProgramV1Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['generateProgramV1']>>>\nexport type CheckEligibilityResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['checkEligibility']>>>\nexport type UwSubmissionV2Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['uwSubmissionV2']>>>\nexport type CreateProgramSummaryTaskResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['createProgramSummaryTask']>>>\n","import { exchangeToken, type TokenResponse } from \"./auth\";\nimport { type ArpcConfig, configure } from \"./config\";\nimport { getARPCAchieveResolutionPartnerConnectAPI } from \"./generated/arpc\";\n\nexport type { TokenResponse } from \"./auth\";\nexport type { ArpcConfig } from \"./config\";\nexport type * from \"./generated/model\";\nexport type { HttpResponse } from \"./http/client\";\n\n/**\n * ARPC SDK — a typed, agnostic client for FDR's ARPC DEX API (Achieve\n * Resolution Partner Connect, Digital Enrollment Experience).\n *\n * Thin by design: it owns the two-host routing, the OAuth token lifecycle, and\n * bearer injection, and exposes the full endpoint surface as typed operations\n * on `sdk.api`. It does not orchestrate the enrollment sequence — the caller\n * assembles payloads and threads the `fdr_applicant_id` between calls. See\n * `scripts/smoke.ts` for a full end-to-end reference sequence (incl. the program\n * poll loop).\n *\n * Operations never throw on a non-2xx: each returns `{ status, data, headers }`,\n * so a business gate (e.g. a UW/DRA readiness failure) comes back as a normal\n * response you inspect via `status`/`data`. Only transport errors throw.\n *\n * Note: config and the bearer token are process-global — construct one `ArpcSDK`\n * per process. A second instance reconfigures the first; instances are not\n * isolated (single-tenant by design).\n *\n * @example\n * ```typescript\n * const sdk = new ArpcSDK({\n * oauthUrl: \"https://oauth.stg.ffngcp.com\",\n * gatewayUrl: \"https://apis-gateway-v2.stg.fdrgcp.com\",\n * username: \"borrowbetter@seller.com\",\n * password: process.env.FDR_OAUTH_PASSWORD!,\n * });\n *\n * await sdk.authenticate();\n * const elig = await sdk.api.checkEligibility({ ... });\n * const faid = elig.data.application?.applicant?.fdr_applicant_id;\n * ```\n */\nexport class ArpcSDK {\n\t/** The full typed endpoint surface — one method per gateway operation. */\n\treadonly api = getARPCAchieveResolutionPartnerConnectAPI();\n\n\tconstructor(config: ArpcConfig) {\n\t\tconfigure(config);\n\t}\n\n\t/**\n\t * Exchange OAuth client credentials for a bearer JWT and store it for\n\t * subsequent `api` calls. Token TTL is ~900s — re-call to refresh.\n\t */\n\tauthenticate(): Promise<TokenResponse> {\n\t\treturn exchangeToken();\n\t}\n}\n"]}
1
+ {"version":3,"sources":["../src/config.ts","../src/auth.ts","../src/http/client.ts","../src/generated/arpc.ts","../src/index.ts"],"names":["ky"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * SDK configuration.\n *\n * The library never reads the environment itself — a consumer constructs\n * `new ArpcSDK(config)`, which calls `configure()`. Where that config comes\n * from (env vars, a secrets manager, a config service) is the caller's business.\n */\n/**\n * Bearer-token store. The SDK authenticates lazily on the first gateway call:\n * it asks `get()` for a still-valid token and, on `null`, exchanges credentials\n * and hands the fresh token to `set()`. Owning expiry is the cache's job —\n * `get()` returns `null` once the token is stale (or near-stale). The default\n * store is in-memory and per-process; supply one to persist across restarts or\n * share across workers.\n */\nexport interface TokenCache {\n\t/** Return a valid bearer token, or `null` if absent/expired. */\n\tget(): Promise<string | null>;\n\t/** Persist a freshly exchanged token. `expiresAt` already includes the SDK's refresh skew. */\n\tset(token: string, expiresAt: Date): Promise<void>;\n}\n\n/** FDR deployment target. Selects both hosts — see `ARPC_ENDPOINTS`. */\nexport type ArpcEnvironment = \"dev\" | \"stg\" | \"prd\";\n\n/** The host pair a single environment resolves to. */\nexport interface ArpcEndpoint {\n\t/** OAuth host — token exchange only (`POST /v1/token`). */\n\treadonly oauth: string;\n\t/** API gateway host — every non-auth call. */\n\treadonly gateway: string;\n}\n\n/**\n * Per-environment hosts. The two differ by domain, not just subdomain: the\n * OAuth host is `ffngcp.com`, the gateway is `fdrgcp.com`.\n *\n * Frozen: this table backs both host resolution and environment validation, so\n * a consumer mutating it would quietly change SDK behavior.\n */\nexport const ARPC_ENDPOINTS: Readonly<Record<ArpcEnvironment, ArpcEndpoint>> =\n\tObject.freeze({\n\t\tdev: Object.freeze({\n\t\t\toauth: \"https://oauth.dev.ffngcp.com\",\n\t\t\tgateway: \"https://apis-gateway-v2.dev.fdrgcp.com\",\n\t\t}),\n\t\tstg: Object.freeze({\n\t\t\toauth: \"https://oauth.stg.ffngcp.com\",\n\t\t\tgateway: \"https://apis-gateway-v2.stg.fdrgcp.com\",\n\t\t}),\n\t\tprd: Object.freeze({\n\t\t\toauth: \"https://oauth.prd.ffngcp.com\",\n\t\t\tgateway: \"https://apis-gateway-v2.prd.fdrgcp.com\",\n\t\t}),\n\t});\n\n/**\n * Narrow a raw string to an `ArpcEnvironment`. Useful when the value arrives\n * untyped — an env var, a JSON config file, a request payload.\n */\nexport function isArpcEnvironment(value: string): value is ArpcEnvironment {\n\t// Own keys only — `in` would also match inherited members like `toString`.\n\treturn Object.keys(ARPC_ENDPOINTS).includes(value);\n}\n\nexport interface ArpcAuth {\n\t/**\n\t * OAuth client_id (HTTP Basic username). Also used as the lead's\n\t * `seller_agent_email` — the OAuth identity is a registered DRA agent.\n\t */\n\tusername: string;\n\t/** OAuth client_secret (HTTP Basic password). */\n\tpassword: string;\n\t/** Bearer-token store. Defaults to an in-memory, per-process cache. */\n\tcache?: TokenCache;\n}\n\n/**\n * Escape hatch — per-host overrides of the `environment` defaults. Omit a key\n * (or leave it `undefined`) to keep that environment's host.\n */\nexport interface ArpcUrls {\n\t/** OAuth host — token exchange only (`POST /v1/token`). */\n\toauth?: string;\n\t/** API gateway host — every non-auth call. */\n\tgateway?: string;\n}\n\nexport interface ArpcConfig {\n\t/** Selects the FDR environment. Drives both hosts. */\n\tenvironment: ArpcEnvironment;\n\tauth: ArpcAuth;\n\turls?: ArpcUrls;\n}\n\n/** Config as the internals consume it — hosts resolved to concrete strings. */\nexport interface ResolvedArpcConfig extends ArpcConfig {\n\turls: Required<ArpcUrls>;\n}\n\nlet current: ResolvedArpcConfig | null = null;\n\n/**\n * Fold the environment defaults together with any caller overrides. The guard\n * looks unreachable given the types, but `environment` routinely arrives from\n * somewhere untyped (an env var, a JSON config, a JS caller) — catch it here\n * rather than letting `undefined` reach a request URL.\n */\nfunction resolveUrls(config: ArpcConfig): Required<ArpcUrls> {\n\tif (!isArpcEnvironment(config.environment)) {\n\t\tthrow new Error(\n\t\t\t`Unknown environment: ${config.environment} (expected one of ${Object.keys(ARPC_ENDPOINTS).join(\", \")})`,\n\t\t);\n\t}\n\tconst defaults = ARPC_ENDPOINTS[config.environment];\n\treturn {\n\t\toauth: config.urls?.oauth ?? defaults.oauth,\n\t\tgateway: config.urls?.gateway ?? defaults.gateway,\n\t};\n}\n\n/** Set the active config, resolving hosts once. Called by the `ArpcSDK` constructor. */\nexport function configure(config: ArpcConfig): void {\n\tcurrent = { ...config, urls: resolveUrls(config) };\n}\n\n/** Read the active config. Throws if the SDK hasn't been constructed yet. */\nexport function getConfig(): ResolvedArpcConfig {\n\tif (!current) {\n\t\tthrow new Error(\n\t\t\t\"ArpcSDK is not configured — construct `new ArpcSDK(config)` before making calls\",\n\t\t);\n\t}\n\treturn current;\n}\n","import ky from \"ky\";\nimport { getConfig, type TokenCache } from \"./config\";\n\n// Token lifecycle lives in the orchestration layer, not the generated client:\n// the exchange targets the OAuth host (not the gateway), uses HTTP Basic, and is\n// form-encoded. Authentication is lazy — `resolveToken()` runs from the ky\n// beforeRequest hook on the first gateway call (and again once the token goes\n// stale), so callers never manage the token by hand.\nexport interface TokenResponse {\n\taccess_token: string;\n\ttoken_type?: string;\n\texpires_in?: number;\n}\n\n// Refresh this many ms before the server's stated expiry, so a token never\n// lapses mid-flight. Applied once, when we compute the stored `expiresAt`.\nconst TOKEN_SKEW_MS = 30_000;\n// FDR tokens are ~900s; fall back to that if the response omits expires_in.\nconst DEFAULT_TTL_S = 900;\n\n/** Raw credentials → bearer JWT. No storage; `resolveToken()` owns that. */\nexport async function exchangeToken(): Promise<TokenResponse> {\n\tconst { urls, auth } = getConfig();\n\tconst basic = Buffer.from(`${auth.username}:${auth.password}`).toString(\n\t\t\"base64\",\n\t);\n\n\treturn ky\n\t\t.post(`${urls.oauth}/v1/token`, {\n\t\t\theaders: { Authorization: `Basic ${basic}` },\n\t\t\tbody: new URLSearchParams({ grant_type: \"client_credentials\" }),\n\t\t})\n\t\t.json<TokenResponse>();\n}\n\n// Default store: in-memory, per-process. Used when config omits `cache`.\nlet memToken: string | null = null;\nlet memExpiresAt = 0;\nconst defaultCache: TokenCache = {\n\tasync get() {\n\t\treturn memToken && Date.now() < memExpiresAt ? memToken : null;\n\t},\n\tasync set(token, expiresAt) {\n\t\tmemToken = token;\n\t\tmemExpiresAt = expiresAt.getTime();\n\t},\n};\n\n// Single-flight guard: a cold burst of concurrent gateway calls performs one\n// exchange, not one per call.\nlet inflight: Promise<string> | null = null;\n\n/**\n * Return a valid bearer token, exchanging + caching on a miss. Called by the\n * gateway beforeRequest hook; not part of the public surface.\n */\nexport function resolveToken(): Promise<string> {\n\tconst store = getConfig().auth.cache ?? defaultCache;\n\tif (inflight) return inflight;\n\n\tinflight = (async () => {\n\t\tconst cached = await store.get();\n\t\tif (cached) return cached;\n\t\tconst res = await exchangeToken();\n\t\tconst ttl = (res.expires_in ?? DEFAULT_TTL_S) * 1000;\n\t\tawait store.set(\n\t\t\tres.access_token,\n\t\t\tnew Date(Date.now() + ttl - TOKEN_SKEW_MS),\n\t\t);\n\t\treturn res.access_token;\n\t})().finally(() => {\n\t\tinflight = null;\n\t});\n\treturn inflight;\n}\n","import ky from \"ky\";\nimport { resolveToken } from \"../auth\";\nimport { getConfig } from \"../config\";\n\n// ---------------------------------------------------------------------------\n// Orval custom mutator (ky-backed).\n//\n// Every generated operation calls `customInstance<T>(config, options)`. We\n// route all calls to the API gateway, attach the bearer token via a\n// beforeRequest hook (lazy auth — `resolveToken()` exchanges/refreshes as\n// needed), and return a small { status, data, headers } wrapper WITHOUT\n// throwing on non-2xx — the caller inspects status codes itself (202 +\n// Retry-After on program, 400 on a UW/DRA gate, etc.).\n// ---------------------------------------------------------------------------\n\n// Shape Orval passes as the first arg (axios-style request config).\nexport interface RequestConfig {\n\turl: string;\n\tmethod: string;\n\tparams?: Record<string, unknown>;\n\tdata?: unknown;\n\theaders?: Record<string, string>;\n\tsignal?: AbortSignal;\n\tresponseType?: string;\n}\n\nexport interface HttpResponse<T> {\n\tstatus: number;\n\tdata: T;\n\theaders: Headers;\n}\n\nexport const customInstance = async <T>(\n\trequest: RequestConfig,\n\toptions?: Partial<RequestConfig>,\n): Promise<HttpResponse<T>> => {\n\tconst { url, method, params, data, signal } = { ...request, ...options };\n\t// Merge headers from both sides — a per-call override (e.g. an\n\t// x-correlation-id in `options.headers`) must not drop the operation's\n\t// Content-Type. The bearer token is layered on last, below.\n\tconst headers = { ...request.headers, ...options?.headers };\n\n\tconst target = new URL(url, getConfig().urls.gateway);\n\tif (params) {\n\t\tfor (const [key, value] of Object.entries(params)) {\n\t\t\tif (value !== undefined && value !== null)\n\t\t\t\ttarget.searchParams.set(key, String(value));\n\t\t}\n\t}\n\n\tconst response = await ky(target, {\n\t\tmethod,\n\t\theaders,\n\t\thooks: {\n\t\t\t// Lazy bearer injection: resolveToken() exchanges on the first call\n\t\t\t// and refreshes once the cached token goes stale. The OAuth exchange\n\t\t\t// itself runs on a bare ky.post (no hook), so there's no recursion.\n\t\t\tbeforeRequest: [\n\t\t\t\tasync (req) => {\n\t\t\t\t\treq.headers.set(\"Authorization\", `Bearer ${await resolveToken()}`);\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t\t...(data !== undefined ? { json: data } : {}),\n\t\tsignal,\n\t\tthrowHttpErrors: false,\n\t\tretry: 0,\n\t\t// Some gateway calls (program generation in particular) can take well over ky's\n\t\t// 10s default to respond. Raise the per-request ceiling; the program poll loop\n\t\t// handles 202/Retry-After on top of this.\n\t\ttimeout: 60_000,\n\t});\n\n\tconst text = await response.text();\n\tlet body: T;\n\ttry {\n\t\tbody = (text ? JSON.parse(text) : undefined) as T;\n\t} catch {\n\t\t// Non-JSON body (e.g. a gateway HTML error page on a transient 5xx). Don't throw —\n\t\t// hand back the raw text so callers can inspect status/body and retry as needed.\n\t\tbody = text as unknown as T;\n\t}\n\treturn { status: response.status, data: body, headers: response.headers };\n};\n","/**\n * Generated by orval v7.21.0 🍺\n * Do not edit manually.\n * ARPC — Achieve Resolution Partner Connect API\n * FDR Wholesale API — A Hybrid Integration Model designed to maximise partner velocity without compromising enterprise data standards. DEX (Digital Enrollment Experience) is the partner-facing integration layer that powers the full applicant lifecycle. The `fdr_applicant_id` acts as the applicant's passport across all systems, persisted from first eligibility check through enrollment. All endpoints require a valid Bearer JWT with the `arpc.all` role.\n * OpenAPI spec version: 2026.15.0\n */\nimport type {\n ARPCErrorResponse,\n ApplicationSendEmailResponse,\n BankUpdateRequest,\n CreateLeadRequest,\n CreateLeadResponse,\n DraRequest,\n DraResponse,\n EligibilityRequest,\n EligibilityResponse,\n LeadPatchRequest,\n LeadResponse,\n LeadTransferRequest,\n LeadTransferResponse,\n ProgramRequest,\n ProgramSummaryTaskRequest,\n ProgramSummaryTaskResponse,\n ReadinessResponse,\n RegisterApplicantResponse,\n RegisterApplicantV2Request,\n SpinwheelNetworkUserRequest,\n SpinwheelNetworkUserResponse,\n StatelessReadinessRequest,\n UwSubmissionV2Response\n} from './model';\n\nimport { customInstance } from '../http/client';\ntype SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];\n\n\n export const getARPCAchieveResolutionPartnerConnectAPI = () => {\n/**\n * Synchronous Orchestration: Handles both lead creation and income/expense data in one step, eliminating the async polling black box. Data authority shifts here — Achieve (SUIP SF) becomes the Source of Truth once the lead is formally promoted. Returns the full lead creation result including program options.\n * @summary Sync wholesale lead creation\n */\nconst createLeadSync = (\n createLeadRequest: CreateLeadRequest,\n options?: SecondParameter<typeof customInstance<CreateLeadResponse>>,) => {\n return customInstance<CreateLeadResponse>(\n {url: `/v3/application`, method: 'POST',\n headers: {'Content-Type': 'application/json', },\n data: createLeadRequest\n },\n options);\n }\n \n/**\n * Stateless Readiness Check: Pre-flight validation before initiating program calculations. Catches duplicate SSNs or emails early to prevent downstream failures. Requires a valid Authorization header.\n * @summary Check application readiness\n */\nconst checkApplicationReadiness = (\n statelessReadinessRequest: StatelessReadinessRequest,\n options?: SecondParameter<typeof customInstance<ReadinessResponse>>,) => {\n return customInstance<ReadinessResponse>(\n {url: `/v1/application/readiness`, method: 'POST',\n headers: {'Content-Type': 'application/json', },\n data: statelessReadinessRequest\n },\n options);\n }\n \n/**\n * Recovery endpoint — only required when the async Spinwheel user creation triggered by `POST /v1/applicant/register` fails due to a user error (e.g. invalid SSN or DOB). Not needed in the happy path. Authenticates the seller, resolves the law firm / program, creates an applicant ID when needed, and connects the applicant to the Spinwheel network. Requires a valid Authorization header.\n * @summary Create / connect Spinwheel network user\n */\nconst createSpinwheelNetworkUser = (\n spinwheelNetworkUserRequest: SpinwheelNetworkUserRequest,\n options?: SecondParameter<typeof customInstance<SpinwheelNetworkUserResponse>>,) => {\n return customInstance<SpinwheelNetworkUserResponse>(\n {url: `/v1/application/spinwheel-network-user`, method: 'POST',\n headers: {'Content-Type': 'application/json', },\n data: spinwheelNetworkUserRequest\n },\n options);\n }\n \n/**\n * Authenticates the seller, validates banking disclosure requirements, optionally validates the submitted account and routing numbers, patches the program, and returns the refreshed lead payload. Requires a valid Authorization header.\n * @summary Update applicant bank details\n */\nconst updateApplicationBankDetails = (\n applicantId: string,\n bankUpdateRequest: BankUpdateRequest,\n options?: SecondParameter<typeof customInstance<LeadResponse>>,) => {\n return customInstance<LeadResponse>(\n {url: `/v1/application/bank-update/${applicantId}`, method: 'PATCH',\n headers: {'Content-Type': 'application/json', },\n data: bankUpdateRequest\n },\n options);\n }\n \n/**\n * Banking Disclosure: Manually triggers the banking disclosure email. **This endpoint is optional** — it is only needed when the disclosure was not already sent automatically by `POST /v2/application/uw-submission/{fdrApplicantId}` (which auto-sends on full UW approval). Use this endpoint for conditional approvals or when the v1 UW submission path is in use. A critical legal prerequisite that must be completed before enrollment can proceed. Requires a valid Authorization header.\n * @summary Send banking disclosure email (optional — only if not auto-sent by v2 UW submission)\n */\nconst sendEmail = (\n fdrApplicantId: string,\n options?: SecondParameter<typeof customInstance<ApplicationSendEmailResponse>>,) => {\n return customInstance<ApplicationSendEmailResponse>(\n {url: `/v1/application/send-email/${fdrApplicantId}`, method: 'POST'\n },\n options);\n }\n \n/**\n * Submits a lead evaluation request. fdr_applicant_id is required in the request body. Requires a valid Authorization header with the arpc.all role.\n * @summary Transfer a lead to the debt-resolution program\n */\nconst leadTransfer = (\n leadTransferRequest: LeadTransferRequest,\n options?: SecondParameter<typeof customInstance<LeadTransferResponse>>,) => {\n return customInstance<LeadTransferResponse>(\n {url: `/v1/application/lead-transfer`, method: 'POST',\n headers: {'Content-Type': 'application/json', },\n data: leadTransferRequest\n },\n options);\n }\n \n/**\n * Fetches the current state of an application / lead record by FDR applicant ID. Requires a valid Authorization header.\n * @summary Read application / lead details\n */\nconst readLead = (\n fdrApplicantId: string,\n options?: SecondParameter<typeof customInstance<LeadResponse>>,) => {\n return customInstance<LeadResponse>(\n {url: `/v1/application/${fdrApplicantId}`, method: 'GET'\n },\n options);\n }\n \n/**\n * Applies a partial update to an existing wholesale lead record. Requires a valid Authorization header and an x-correlation-id header for request tracing.\n * @summary Partial update of application / lead record\n */\nconst patchLead = (\n fdrApplicantId: string,\n leadPatchRequest: LeadPatchRequest,\n options?: SecondParameter<typeof customInstance<LeadResponse>>,) => {\n return customInstance<LeadResponse>(\n {url: `/v1/application/${fdrApplicantId}`, method: 'PATCH',\n headers: {'Content-Type': 'application/json', },\n data: leadPatchRequest\n },\n options);\n }\n \n/**\n * DRA Readiness Check: Final pre-flight confirming the lead is approved and all prerequisites are met before initiating the embedded signing session. Requires a valid Authorization header.\n * @summary Get application readiness status\n */\nconst getApplicationReadiness = (\n applicantId: string,\n options?: SecondParameter<typeof customInstance<ReadinessResponse>>,) => {\n return customInstance<ReadinessResponse>(\n {url: `/v1/application/${applicantId}/readiness`, method: 'GET'\n },\n options);\n }\n \n/**\n * Embedded DRA Signing: Returns an Embedded Signing URL so the applicant signs the Debt Resolution Agreement via DocuSign directly within the Symple UI, preventing session breakage. Successful legal execution triggers automatic enrollment. Requires a valid Authorization header.\n * @summary Generate Debt Resolution Agreement (DRA)\n */\nconst generateDra = (\n applicantId: string,\n draRequest: DraRequest,\n options?: SecondParameter<typeof customInstance<DraResponse>>,) => {\n return customInstance<DraResponse>(\n {url: `/v2/application/${applicantId}/dra`, method: 'POST',\n headers: {'Content-Type': 'application/json', },\n data: draRequest\n },\n options);\n }\n \n/**\n * **Recommended endpoint.** `POST /v1/applicant/register` is deprecated and will be retired by end of July 2026 (deadline can be extended on request if your team needs more time). Identity Anchoring: Persists applicant PII to the Managed Package and generates the `fdr_applicant_id` — the applicant's passport across all systems. Routes to the correct Salesforce target (MP or WiB) based on seller identity, and triggers the async credit fetch. Requires a valid Authorization header.\n * @summary Seller-aware applicant registration (v2 — unified ARPCErrorResponse envelope) — recommended\n */\nconst registerApplicantV2 = (\n registerApplicantV2Request: RegisterApplicantV2Request,\n options?: SecondParameter<typeof customInstance<RegisterApplicantResponse>>,) => {\n return customInstance<RegisterApplicantResponse>(\n {url: `/v2/applicant/register`, method: 'POST',\n headers: {'Content-Type': 'application/json', },\n data: registerApplicantV2Request\n },\n options);\n }\n \n/**\n * Async Polling Loop: Call this single endpoint for both initial generation and refresh. Returns 202 Accepted with a Retry-After header while credit is still pending. Once ready, the Managed Package orchestrates the calculation and persists the program result. Requires a valid Authorization header.\n * @summary Generate application program (v1)\n */\nconst generateProgramV1 = (\n programRequest: ProgramRequest,\n options?: SecondParameter<typeof customInstance<LeadResponse>>,) => {\n return customInstance<LeadResponse>(\n {url: `/v1/application/program`, method: 'POST',\n headers: {'Content-Type': 'application/json', },\n data: programRequest\n },\n options);\n }\n \n/**\n * Eligibility Scout: A stateless, real-time quote based on anonymised data — no PII required. Returns estimated program options to anchor the conversation before any registration occurs. Requires a valid Authorization header.\n * @summary Check application eligibility\n */\nconst checkEligibility = (\n eligibilityRequest: EligibilityRequest,\n options?: SecondParameter<typeof customInstance<EligibilityResponse>>,) => {\n return customInstance<EligibilityResponse>(\n {url: `/v2/application/eligibility`, method: 'POST',\n headers: {'Content-Type': 'application/json', },\n data: eligibilityRequest\n },\n options);\n }\n \n/**\n * **Recommended endpoint — supersedes v1.** Submits a wholesale application to the underwriting system. On full UW approval (`sub_status = 'UW Approved'`) the banking disclosure email is sent automatically — no separate `send-email` call is needed in this path. Conditional approvals suppress the auto-send; in that case the manual `POST /v1/application/send-email/{fdrApplicantId}` endpoint can be used. Uses the SF Object API directly (no suip-proxy hop). Requires a valid Authorization header.\n * @summary Submit application to underwriting (v2 — auto banking disclosure) — recommended\n */\nconst uwSubmissionV2 = (\n fdrApplicantId: string,\n options?: SecondParameter<typeof customInstance<UwSubmissionV2Response>>,) => {\n return customInstance<UwSubmissionV2Response>(\n {url: `/v2/application/uw-submission/${fdrApplicantId}`, method: 'POST'\n },\n options);\n }\n \n/**\n * Creates a SUIP Salesforce 'Program Summary Recording' task for the applicant identified by the {id} path variable. For DEX (or missing header), the parent SF opportunity is additionally PATCHed with the seller-config partner default PSC. For DC, the task is created and the parent SF opportunity is PATCHed only when ws_psc_code is provided in the request payload.\n * @summary Create program-summary recording task\n */\nconst createProgramSummaryTask = (\n id: string,\n programSummaryTaskRequest: ProgramSummaryTaskRequest,\n options?: SecondParameter<typeof customInstance<ProgramSummaryTaskResponse>>,) => {\n return customInstance<ProgramSummaryTaskResponse>(\n {url: `/v2/application/program-summary-task/${id}`, method: 'POST',\n headers: {'Content-Type': 'application/json', },\n data: programSummaryTaskRequest\n },\n options);\n }\n \nreturn {createLeadSync,checkApplicationReadiness,createSpinwheelNetworkUser,updateApplicationBankDetails,sendEmail,leadTransfer,readLead,patchLead,getApplicationReadiness,generateDra,registerApplicantV2,generateProgramV1,checkEligibility,uwSubmissionV2,createProgramSummaryTask}};\nexport type CreateLeadSyncResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['createLeadSync']>>>\nexport type CheckApplicationReadinessResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['checkApplicationReadiness']>>>\nexport type CreateSpinwheelNetworkUserResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['createSpinwheelNetworkUser']>>>\nexport type UpdateApplicationBankDetailsResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['updateApplicationBankDetails']>>>\nexport type SendEmailResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['sendEmail']>>>\nexport type LeadTransferResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['leadTransfer']>>>\nexport type ReadLeadResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['readLead']>>>\nexport type PatchLeadResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['patchLead']>>>\nexport type GetApplicationReadinessResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['getApplicationReadiness']>>>\nexport type GenerateDraResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['generateDra']>>>\nexport type RegisterApplicantV2Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['registerApplicantV2']>>>\nexport type GenerateProgramV1Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['generateProgramV1']>>>\nexport type CheckEligibilityResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['checkEligibility']>>>\nexport type UwSubmissionV2Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['uwSubmissionV2']>>>\nexport type CreateProgramSummaryTaskResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['createProgramSummaryTask']>>>\n","import { type ArpcConfig, configure } from \"./config\";\nimport { getARPCAchieveResolutionPartnerConnectAPI } from \"./generated/arpc\";\n\nexport type {\n\tArpcAuth,\n\tArpcConfig,\n\tArpcEndpoint,\n\tArpcEnvironment,\n\tArpcUrls,\n\tTokenCache,\n} from \"./config\";\nexport { ARPC_ENDPOINTS, isArpcEnvironment } from \"./config\";\nexport type * from \"./generated/model\";\nexport type { HttpResponse } from \"./http/client\";\n\n/**\n * ARPC SDK — a typed, agnostic client for FDR's ARPC DEX API (Achieve\n * Resolution Partner Connect, Digital Enrollment Experience).\n *\n * Thin by design: it owns the two-host routing (both derived from\n * `environment`), the OAuth token lifecycle, and bearer injection, and exposes\n * the full endpoint surface as typed operations on `sdk.api`. It does not\n * orchestrate the enrollment sequence — the caller\n * assembles payloads and threads the `fdr_applicant_id` between calls. See\n * `scripts/smoke.ts` for a full end-to-end reference sequence (incl. the program\n * poll loop).\n *\n * Auth is lazy: the first `api` call exchanges credentials for a bearer JWT and\n * caches it (~900s TTL), then re-exchanges shortly before it expires. There's no\n * explicit login step. Supply `auth.cache` to persist the token across restarts\n * or share it across workers; otherwise it's held in-process.\n *\n * Operations never throw on a non-2xx: each returns `{ status, data, headers }`,\n * so a business gate (e.g. a UW/DRA readiness failure) comes back as a normal\n * response you inspect via `status`/`data`. Only transport errors throw.\n *\n * Note: config and the default token cache are process-global — construct one\n * `ArpcSDK` per process. A second instance reconfigures the first; instances are\n * not isolated (single-tenant by design).\n *\n * @example\n * ```typescript\n * const sdk = new ArpcSDK({\n * environment: \"stg\",\n * auth: {\n * username: \"borrowbetter@seller.com\",\n * password: process.env.ARPC_OAUTH_PASSWORD!,\n * },\n * });\n *\n * // The first call authenticates automatically and caches the token.\n * const elig = await sdk.api.checkEligibility({ ... });\n * const faid = elig.data.application?.applicant?.fdr_applicant_id;\n * ```\n */\nexport class ArpcSDK {\n\t/** The full typed endpoint surface — one method per gateway operation. */\n\treadonly api = getARPCAchieveResolutionPartnerConnectAPI();\n\n\tconstructor(config: ArpcConfig) {\n\t\tconfigure(config);\n\t}\n}\n"]}
package/dist/index.mjs CHANGED
@@ -1,11 +1,38 @@
1
1
  import ky from 'ky';
2
2
 
3
- // src/auth.ts
4
-
5
3
  // src/config.ts
4
+ var ARPC_ENDPOINTS = Object.freeze({
5
+ dev: Object.freeze({
6
+ oauth: "https://oauth.dev.ffngcp.com",
7
+ gateway: "https://apis-gateway-v2.dev.fdrgcp.com"
8
+ }),
9
+ stg: Object.freeze({
10
+ oauth: "https://oauth.stg.ffngcp.com",
11
+ gateway: "https://apis-gateway-v2.stg.fdrgcp.com"
12
+ }),
13
+ prd: Object.freeze({
14
+ oauth: "https://oauth.prd.ffngcp.com",
15
+ gateway: "https://apis-gateway-v2.prd.fdrgcp.com"
16
+ })
17
+ });
18
+ function isArpcEnvironment(value) {
19
+ return Object.keys(ARPC_ENDPOINTS).includes(value);
20
+ }
6
21
  var current = null;
22
+ function resolveUrls(config) {
23
+ if (!isArpcEnvironment(config.environment)) {
24
+ throw new Error(
25
+ `Unknown environment: ${config.environment} (expected one of ${Object.keys(ARPC_ENDPOINTS).join(", ")})`
26
+ );
27
+ }
28
+ const defaults = ARPC_ENDPOINTS[config.environment];
29
+ return {
30
+ oauth: config.urls?.oauth ?? defaults.oauth,
31
+ gateway: config.urls?.gateway ?? defaults.gateway
32
+ };
33
+ }
7
34
  function configure(config) {
8
- current = config;
35
+ current = { ...config, urls: resolveUrls(config) };
9
36
  }
10
37
  function getConfig() {
11
38
  if (!current) {
@@ -15,14 +42,54 @@ function getConfig() {
15
42
  }
16
43
  return current;
17
44
  }
18
- var bearerToken = null;
19
- function setBearerToken(token) {
20
- bearerToken = token;
45
+ var TOKEN_SKEW_MS = 3e4;
46
+ var DEFAULT_TTL_S = 900;
47
+ async function exchangeToken() {
48
+ const { urls, auth } = getConfig();
49
+ const basic = Buffer.from(`${auth.username}:${auth.password}`).toString(
50
+ "base64"
51
+ );
52
+ return ky.post(`${urls.oauth}/v1/token`, {
53
+ headers: { Authorization: `Basic ${basic}` },
54
+ body: new URLSearchParams({ grant_type: "client_credentials" })
55
+ }).json();
56
+ }
57
+ var memToken = null;
58
+ var memExpiresAt = 0;
59
+ var defaultCache = {
60
+ async get() {
61
+ return memToken && Date.now() < memExpiresAt ? memToken : null;
62
+ },
63
+ async set(token, expiresAt) {
64
+ memToken = token;
65
+ memExpiresAt = expiresAt.getTime();
66
+ }
67
+ };
68
+ var inflight = null;
69
+ function resolveToken() {
70
+ const store = getConfig().auth.cache ?? defaultCache;
71
+ if (inflight) return inflight;
72
+ inflight = (async () => {
73
+ const cached = await store.get();
74
+ if (cached) return cached;
75
+ const res = await exchangeToken();
76
+ const ttl = (res.expires_in ?? DEFAULT_TTL_S) * 1e3;
77
+ await store.set(
78
+ res.access_token,
79
+ new Date(Date.now() + ttl - TOKEN_SKEW_MS)
80
+ );
81
+ return res.access_token;
82
+ })().finally(() => {
83
+ inflight = null;
84
+ });
85
+ return inflight;
21
86
  }
87
+
88
+ // src/http/client.ts
22
89
  var customInstance = async (request, options) => {
23
90
  const { url, method, params, data, signal } = { ...request, ...options };
24
91
  const headers = { ...request.headers, ...options?.headers };
25
- const target = new URL(url, getConfig().gatewayUrl);
92
+ const target = new URL(url, getConfig().urls.gateway);
26
93
  if (params) {
27
94
  for (const [key, value] of Object.entries(params)) {
28
95
  if (value !== void 0 && value !== null)
@@ -31,9 +98,16 @@ var customInstance = async (request, options) => {
31
98
  }
32
99
  const response = await ky(target, {
33
100
  method,
34
- headers: {
35
- ...headers,
36
- ...bearerToken ? { Authorization: `Bearer ${bearerToken}` } : {}
101
+ headers,
102
+ hooks: {
103
+ // Lazy bearer injection: resolveToken() exchanges on the first call
104
+ // and refreshes once the cached token goes stale. The OAuth exchange
105
+ // itself runs on a bare ky.post (no hook), so there's no recursion.
106
+ beforeRequest: [
107
+ async (req) => {
108
+ req.headers.set("Authorization", `Bearer ${await resolveToken()}`);
109
+ }
110
+ ]
37
111
  },
38
112
  ...data !== void 0 ? { json: data } : {},
39
113
  signal,
@@ -54,18 +128,6 @@ var customInstance = async (request, options) => {
54
128
  return { status: response.status, data: body, headers: response.headers };
55
129
  };
56
130
 
57
- // src/auth.ts
58
- async function exchangeToken() {
59
- const { oauthUrl, username, password } = getConfig();
60
- const basic = Buffer.from(`${username}:${password}`).toString("base64");
61
- const token = await ky.post(`${oauthUrl}/v1/token`, {
62
- headers: { Authorization: `Basic ${basic}` },
63
- body: new URLSearchParams({ grant_type: "client_credentials" })
64
- }).json();
65
- setBearerToken(token.access_token);
66
- return token;
67
- }
68
-
69
131
  // src/generated/arpc.ts
70
132
  var getARPCAchieveResolutionPartnerConnectAPI = () => {
71
133
  const createLeadSync = (createLeadRequest, options) => {
@@ -235,15 +297,8 @@ var ArpcSDK = class {
235
297
  this.api = getARPCAchieveResolutionPartnerConnectAPI();
236
298
  configure(config);
237
299
  }
238
- /**
239
- * Exchange OAuth client credentials for a bearer JWT and store it for
240
- * subsequent `api` calls. Token TTL is ~900s — re-call to refresh.
241
- */
242
- authenticate() {
243
- return exchangeToken();
244
- }
245
300
  };
246
301
 
247
- export { ArpcSDK };
302
+ export { ARPC_ENDPOINTS, ArpcSDK, isArpcEnvironment };
248
303
  //# sourceMappingURL=index.mjs.map
249
304
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/config.ts","../src/http/client.ts","../src/auth.ts","../src/generated/arpc.ts","../src/index.ts"],"names":["ky"],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * SDK configuration.\n *\n * The library never reads the environment itself — a consumer constructs\n * `new ArpcSDK(config)`, which calls `configure()`. The `configFromEnv()` helper\n * is a convenience for the dev scripts (smoke / server), which load `.env` via\n * dotenv-flow and pass the result in.\n */\nexport interface ArpcConfig {\n\t/** OAuth host — token exchange only (`POST /v1/token`). */\n\toauthUrl: string;\n\t/** API gateway host — every non-auth call. */\n\tgatewayUrl: string;\n\t/**\n\t * OAuth client_id (HTTP Basic username). Also used as the lead's\n\t * `seller_agent_email` — the OAuth identity is a registered DRA agent.\n\t */\n\tusername: string;\n\t/** OAuth client_secret (HTTP Basic password). */\n\tpassword: string;\n}\n\nlet current: ArpcConfig | null = null;\n\n/** Set the active config. Called by the `ArpcSDK` constructor. */\nexport function configure(config: ArpcConfig): void {\n\tcurrent = config;\n}\n\n/** Read the active config. Throws if the SDK hasn't been constructed yet. */\nexport function getConfig(): ArpcConfig {\n\tif (!current) {\n\t\tthrow new Error(\n\t\t\t\"ArpcSDK is not configured — construct `new ArpcSDK(config)` before making calls\",\n\t\t);\n\t}\n\treturn current;\n}\n\n/**\n * Build a config from environment variables. Not used by the library itself —\n * only the dev scripts call this after loading `.env`.\n */\nexport function configFromEnv(\n\tenv: NodeJS.ProcessEnv = process.env,\n): ArpcConfig {\n\tconst need = (key: string): string => {\n\t\tconst value = env[key];\n\t\tif (!value)\n\t\t\tthrow new Error(`Missing required env var: ${key} (set it in .env)`);\n\t\treturn value;\n\t};\n\treturn {\n\t\toauthUrl: need(\"FDR_OAUTH_URL\"),\n\t\tgatewayUrl: need(\"FDR_API_GATEWAY_URL\"),\n\t\tusername: need(\"FDR_OAUTH_USERNAME\"),\n\t\tpassword: need(\"FDR_OAUTH_PASSWORD\"),\n\t};\n}\n","import ky from \"ky\";\nimport { getConfig } from \"../config\";\n\n// ---------------------------------------------------------------------------\n// Orval custom mutator (ky-backed).\n//\n// Every generated operation calls `customInstance<T>(config, options)`. We\n// route all calls to the API gateway, inject the bearer token, and return a\n// small { status, data, headers } wrapper WITHOUT throwing on non-2xx — the\n// caller inspects status codes itself (202 + Retry-After on program, 400 on a\n// UW/DRA gate, etc.).\n// ---------------------------------------------------------------------------\n\nlet bearerToken: string | null = null;\nexport function setBearerToken(token: string | null): void {\n\tbearerToken = token;\n}\n\n// Shape Orval passes as the first arg (axios-style request config).\nexport interface RequestConfig {\n\turl: string;\n\tmethod: string;\n\tparams?: Record<string, unknown>;\n\tdata?: unknown;\n\theaders?: Record<string, string>;\n\tsignal?: AbortSignal;\n\tresponseType?: string;\n}\n\nexport interface HttpResponse<T> {\n\tstatus: number;\n\tdata: T;\n\theaders: Headers;\n}\n\nexport const customInstance = async <T>(\n\trequest: RequestConfig,\n\toptions?: Partial<RequestConfig>,\n): Promise<HttpResponse<T>> => {\n\tconst { url, method, params, data, signal } = { ...request, ...options };\n\t// Merge headers from both sides — a per-call override (e.g. an\n\t// x-correlation-id in `options.headers`) must not drop the operation's\n\t// Content-Type. The bearer token is layered on last, below.\n\tconst headers = { ...request.headers, ...options?.headers };\n\n\tconst target = new URL(url, getConfig().gatewayUrl);\n\tif (params) {\n\t\tfor (const [key, value] of Object.entries(params)) {\n\t\t\tif (value !== undefined && value !== null)\n\t\t\t\ttarget.searchParams.set(key, String(value));\n\t\t}\n\t}\n\n\tconst response = await ky(target, {\n\t\tmethod,\n\t\theaders: {\n\t\t\t...headers,\n\t\t\t...(bearerToken ? { Authorization: `Bearer ${bearerToken}` } : {}),\n\t\t},\n\t\t...(data !== undefined ? { json: data } : {}),\n\t\tsignal,\n\t\tthrowHttpErrors: false,\n\t\tretry: 0,\n\t\t// Some gateway calls (program generation in particular) can take well over ky's\n\t\t// 10s default to respond. Raise the per-request ceiling; the program poll loop\n\t\t// handles 202/Retry-After on top of this.\n\t\ttimeout: 60_000,\n\t});\n\n\tconst text = await response.text();\n\tlet body: T;\n\ttry {\n\t\tbody = (text ? JSON.parse(text) : undefined) as T;\n\t} catch {\n\t\t// Non-JSON body (e.g. a gateway HTML error page on a transient 5xx). Don't throw —\n\t\t// hand back the raw text so callers can inspect status/body and retry as needed.\n\t\tbody = text as unknown as T;\n\t}\n\treturn { status: response.status, data: body, headers: response.headers };\n};\n","import ky from \"ky\";\nimport { getConfig } from \"./config\";\nimport { setBearerToken } from \"./http/client\";\n\n// Token exchange lives in the orchestration layer, not the generated client:\n// it targets the OAuth host (not the gateway), uses HTTP Basic, and is\n// form-encoded. We store the resulting JWT for the mutator to attach.\nexport interface TokenResponse {\n\taccess_token: string;\n\ttoken_type?: string;\n\texpires_in?: number;\n}\n\nexport async function exchangeToken(): Promise<TokenResponse> {\n\tconst { oauthUrl, username, password } = getConfig();\n\tconst basic = Buffer.from(`${username}:${password}`).toString(\"base64\");\n\n\tconst token = await ky\n\t\t.post(`${oauthUrl}/v1/token`, {\n\t\t\theaders: { Authorization: `Basic ${basic}` },\n\t\t\tbody: new URLSearchParams({ grant_type: \"client_credentials\" }),\n\t\t})\n\t\t.json<TokenResponse>();\n\n\tsetBearerToken(token.access_token);\n\treturn token;\n}\n","/**\n * Generated by orval v7.21.0 🍺\n * Do not edit manually.\n * ARPC — Achieve Resolution Partner Connect API\n * FDR Wholesale API — A Hybrid Integration Model designed to maximise partner velocity without compromising enterprise data standards. DEX (Digital Enrollment Experience) is the partner-facing integration layer that powers the full applicant lifecycle. The `fdr_applicant_id` acts as the applicant's passport across all systems, persisted from first eligibility check through enrollment. All endpoints require a valid Bearer JWT with the `arpc.all` role.\n * OpenAPI spec version: 2026.15.0\n */\nimport type {\n ARPCErrorResponse,\n ApplicationSendEmailResponse,\n BankUpdateRequest,\n CreateLeadRequest,\n CreateLeadResponse,\n DraRequest,\n DraResponse,\n EligibilityRequest,\n EligibilityResponse,\n LeadPatchRequest,\n LeadResponse,\n LeadTransferRequest,\n LeadTransferResponse,\n ProgramRequest,\n ProgramSummaryTaskRequest,\n ProgramSummaryTaskResponse,\n ReadinessResponse,\n RegisterApplicantResponse,\n RegisterApplicantV2Request,\n SpinwheelNetworkUserRequest,\n SpinwheelNetworkUserResponse,\n StatelessReadinessRequest,\n UwSubmissionV2Response\n} from './model';\n\nimport { customInstance } from '../http/client';\ntype SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];\n\n\n export const getARPCAchieveResolutionPartnerConnectAPI = () => {\n/**\n * Synchronous Orchestration: Handles both lead creation and income/expense data in one step, eliminating the async polling black box. Data authority shifts here — Achieve (SUIP SF) becomes the Source of Truth once the lead is formally promoted. Returns the full lead creation result including program options.\n * @summary Sync wholesale lead creation\n */\nconst createLeadSync = (\n createLeadRequest: CreateLeadRequest,\n options?: SecondParameter<typeof customInstance<CreateLeadResponse>>,) => {\n return customInstance<CreateLeadResponse>(\n {url: `/v3/application`, method: 'POST',\n headers: {'Content-Type': 'application/json', },\n data: createLeadRequest\n },\n options);\n }\n \n/**\n * Stateless Readiness Check: Pre-flight validation before initiating program calculations. Catches duplicate SSNs or emails early to prevent downstream failures. Requires a valid Authorization header.\n * @summary Check application readiness\n */\nconst checkApplicationReadiness = (\n statelessReadinessRequest: StatelessReadinessRequest,\n options?: SecondParameter<typeof customInstance<ReadinessResponse>>,) => {\n return customInstance<ReadinessResponse>(\n {url: `/v1/application/readiness`, method: 'POST',\n headers: {'Content-Type': 'application/json', },\n data: statelessReadinessRequest\n },\n options);\n }\n \n/**\n * Recovery endpoint — only required when the async Spinwheel user creation triggered by `POST /v1/applicant/register` fails due to a user error (e.g. invalid SSN or DOB). Not needed in the happy path. Authenticates the seller, resolves the law firm / program, creates an applicant ID when needed, and connects the applicant to the Spinwheel network. Requires a valid Authorization header.\n * @summary Create / connect Spinwheel network user\n */\nconst createSpinwheelNetworkUser = (\n spinwheelNetworkUserRequest: SpinwheelNetworkUserRequest,\n options?: SecondParameter<typeof customInstance<SpinwheelNetworkUserResponse>>,) => {\n return customInstance<SpinwheelNetworkUserResponse>(\n {url: `/v1/application/spinwheel-network-user`, method: 'POST',\n headers: {'Content-Type': 'application/json', },\n data: spinwheelNetworkUserRequest\n },\n options);\n }\n \n/**\n * Authenticates the seller, validates banking disclosure requirements, optionally validates the submitted account and routing numbers, patches the program, and returns the refreshed lead payload. Requires a valid Authorization header.\n * @summary Update applicant bank details\n */\nconst updateApplicationBankDetails = (\n applicantId: string,\n bankUpdateRequest: BankUpdateRequest,\n options?: SecondParameter<typeof customInstance<LeadResponse>>,) => {\n return customInstance<LeadResponse>(\n {url: `/v1/application/bank-update/${applicantId}`, method: 'PATCH',\n headers: {'Content-Type': 'application/json', },\n data: bankUpdateRequest\n },\n options);\n }\n \n/**\n * Banking Disclosure: Manually triggers the banking disclosure email. **This endpoint is optional** — it is only needed when the disclosure was not already sent automatically by `POST /v2/application/uw-submission/{fdrApplicantId}` (which auto-sends on full UW approval). Use this endpoint for conditional approvals or when the v1 UW submission path is in use. A critical legal prerequisite that must be completed before enrollment can proceed. Requires a valid Authorization header.\n * @summary Send banking disclosure email (optional — only if not auto-sent by v2 UW submission)\n */\nconst sendEmail = (\n fdrApplicantId: string,\n options?: SecondParameter<typeof customInstance<ApplicationSendEmailResponse>>,) => {\n return customInstance<ApplicationSendEmailResponse>(\n {url: `/v1/application/send-email/${fdrApplicantId}`, method: 'POST'\n },\n options);\n }\n \n/**\n * Submits a lead evaluation request. fdr_applicant_id is required in the request body. Requires a valid Authorization header with the arpc.all role.\n * @summary Transfer a lead to the debt-resolution program\n */\nconst leadTransfer = (\n leadTransferRequest: LeadTransferRequest,\n options?: SecondParameter<typeof customInstance<LeadTransferResponse>>,) => {\n return customInstance<LeadTransferResponse>(\n {url: `/v1/application/lead-transfer`, method: 'POST',\n headers: {'Content-Type': 'application/json', },\n data: leadTransferRequest\n },\n options);\n }\n \n/**\n * Fetches the current state of an application / lead record by FDR applicant ID. Requires a valid Authorization header.\n * @summary Read application / lead details\n */\nconst readLead = (\n fdrApplicantId: string,\n options?: SecondParameter<typeof customInstance<LeadResponse>>,) => {\n return customInstance<LeadResponse>(\n {url: `/v1/application/${fdrApplicantId}`, method: 'GET'\n },\n options);\n }\n \n/**\n * Applies a partial update to an existing wholesale lead record. Requires a valid Authorization header and an x-correlation-id header for request tracing.\n * @summary Partial update of application / lead record\n */\nconst patchLead = (\n fdrApplicantId: string,\n leadPatchRequest: LeadPatchRequest,\n options?: SecondParameter<typeof customInstance<LeadResponse>>,) => {\n return customInstance<LeadResponse>(\n {url: `/v1/application/${fdrApplicantId}`, method: 'PATCH',\n headers: {'Content-Type': 'application/json', },\n data: leadPatchRequest\n },\n options);\n }\n \n/**\n * DRA Readiness Check: Final pre-flight confirming the lead is approved and all prerequisites are met before initiating the embedded signing session. Requires a valid Authorization header.\n * @summary Get application readiness status\n */\nconst getApplicationReadiness = (\n applicantId: string,\n options?: SecondParameter<typeof customInstance<ReadinessResponse>>,) => {\n return customInstance<ReadinessResponse>(\n {url: `/v1/application/${applicantId}/readiness`, method: 'GET'\n },\n options);\n }\n \n/**\n * Embedded DRA Signing: Returns an Embedded Signing URL so the applicant signs the Debt Resolution Agreement via DocuSign directly within the Symple UI, preventing session breakage. Successful legal execution triggers automatic enrollment. Requires a valid Authorization header.\n * @summary Generate Debt Resolution Agreement (DRA)\n */\nconst generateDra = (\n applicantId: string,\n draRequest: DraRequest,\n options?: SecondParameter<typeof customInstance<DraResponse>>,) => {\n return customInstance<DraResponse>(\n {url: `/v2/application/${applicantId}/dra`, method: 'POST',\n headers: {'Content-Type': 'application/json', },\n data: draRequest\n },\n options);\n }\n \n/**\n * **Recommended endpoint.** `POST /v1/applicant/register` is deprecated and will be retired by end of July 2026 (deadline can be extended on request if your team needs more time). Identity Anchoring: Persists applicant PII to the Managed Package and generates the `fdr_applicant_id` — the applicant's passport across all systems. Routes to the correct Salesforce target (MP or WiB) based on seller identity, and triggers the async credit fetch. Requires a valid Authorization header.\n * @summary Seller-aware applicant registration (v2 — unified ARPCErrorResponse envelope) — recommended\n */\nconst registerApplicantV2 = (\n registerApplicantV2Request: RegisterApplicantV2Request,\n options?: SecondParameter<typeof customInstance<RegisterApplicantResponse>>,) => {\n return customInstance<RegisterApplicantResponse>(\n {url: `/v2/applicant/register`, method: 'POST',\n headers: {'Content-Type': 'application/json', },\n data: registerApplicantV2Request\n },\n options);\n }\n \n/**\n * Async Polling Loop: Call this single endpoint for both initial generation and refresh. Returns 202 Accepted with a Retry-After header while credit is still pending. Once ready, the Managed Package orchestrates the calculation and persists the program result. Requires a valid Authorization header.\n * @summary Generate application program (v1)\n */\nconst generateProgramV1 = (\n programRequest: ProgramRequest,\n options?: SecondParameter<typeof customInstance<LeadResponse>>,) => {\n return customInstance<LeadResponse>(\n {url: `/v1/application/program`, method: 'POST',\n headers: {'Content-Type': 'application/json', },\n data: programRequest\n },\n options);\n }\n \n/**\n * Eligibility Scout: A stateless, real-time quote based on anonymised data — no PII required. Returns estimated program options to anchor the conversation before any registration occurs. Requires a valid Authorization header.\n * @summary Check application eligibility\n */\nconst checkEligibility = (\n eligibilityRequest: EligibilityRequest,\n options?: SecondParameter<typeof customInstance<EligibilityResponse>>,) => {\n return customInstance<EligibilityResponse>(\n {url: `/v2/application/eligibility`, method: 'POST',\n headers: {'Content-Type': 'application/json', },\n data: eligibilityRequest\n },\n options);\n }\n \n/**\n * **Recommended endpoint — supersedes v1.** Submits a wholesale application to the underwriting system. On full UW approval (`sub_status = 'UW Approved'`) the banking disclosure email is sent automatically — no separate `send-email` call is needed in this path. Conditional approvals suppress the auto-send; in that case the manual `POST /v1/application/send-email/{fdrApplicantId}` endpoint can be used. Uses the SF Object API directly (no suip-proxy hop). Requires a valid Authorization header.\n * @summary Submit application to underwriting (v2 — auto banking disclosure) — recommended\n */\nconst uwSubmissionV2 = (\n fdrApplicantId: string,\n options?: SecondParameter<typeof customInstance<UwSubmissionV2Response>>,) => {\n return customInstance<UwSubmissionV2Response>(\n {url: `/v2/application/uw-submission/${fdrApplicantId}`, method: 'POST'\n },\n options);\n }\n \n/**\n * Creates a SUIP Salesforce 'Program Summary Recording' task for the applicant identified by the {id} path variable. For DEX (or missing header), the parent SF opportunity is additionally PATCHed with the seller-config partner default PSC. For DC, the task is created and the parent SF opportunity is PATCHed only when ws_psc_code is provided in the request payload.\n * @summary Create program-summary recording task\n */\nconst createProgramSummaryTask = (\n id: string,\n programSummaryTaskRequest: ProgramSummaryTaskRequest,\n options?: SecondParameter<typeof customInstance<ProgramSummaryTaskResponse>>,) => {\n return customInstance<ProgramSummaryTaskResponse>(\n {url: `/v2/application/program-summary-task/${id}`, method: 'POST',\n headers: {'Content-Type': 'application/json', },\n data: programSummaryTaskRequest\n },\n options);\n }\n \nreturn {createLeadSync,checkApplicationReadiness,createSpinwheelNetworkUser,updateApplicationBankDetails,sendEmail,leadTransfer,readLead,patchLead,getApplicationReadiness,generateDra,registerApplicantV2,generateProgramV1,checkEligibility,uwSubmissionV2,createProgramSummaryTask}};\nexport type CreateLeadSyncResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['createLeadSync']>>>\nexport type CheckApplicationReadinessResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['checkApplicationReadiness']>>>\nexport type CreateSpinwheelNetworkUserResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['createSpinwheelNetworkUser']>>>\nexport type UpdateApplicationBankDetailsResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['updateApplicationBankDetails']>>>\nexport type SendEmailResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['sendEmail']>>>\nexport type LeadTransferResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['leadTransfer']>>>\nexport type ReadLeadResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['readLead']>>>\nexport type PatchLeadResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['patchLead']>>>\nexport type GetApplicationReadinessResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['getApplicationReadiness']>>>\nexport type GenerateDraResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['generateDra']>>>\nexport type RegisterApplicantV2Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['registerApplicantV2']>>>\nexport type GenerateProgramV1Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['generateProgramV1']>>>\nexport type CheckEligibilityResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['checkEligibility']>>>\nexport type UwSubmissionV2Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['uwSubmissionV2']>>>\nexport type CreateProgramSummaryTaskResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['createProgramSummaryTask']>>>\n","import { exchangeToken, type TokenResponse } from \"./auth\";\nimport { type ArpcConfig, configure } from \"./config\";\nimport { getARPCAchieveResolutionPartnerConnectAPI } from \"./generated/arpc\";\n\nexport type { TokenResponse } from \"./auth\";\nexport type { ArpcConfig } from \"./config\";\nexport type * from \"./generated/model\";\nexport type { HttpResponse } from \"./http/client\";\n\n/**\n * ARPC SDK — a typed, agnostic client for FDR's ARPC DEX API (Achieve\n * Resolution Partner Connect, Digital Enrollment Experience).\n *\n * Thin by design: it owns the two-host routing, the OAuth token lifecycle, and\n * bearer injection, and exposes the full endpoint surface as typed operations\n * on `sdk.api`. It does not orchestrate the enrollment sequence — the caller\n * assembles payloads and threads the `fdr_applicant_id` between calls. See\n * `scripts/smoke.ts` for a full end-to-end reference sequence (incl. the program\n * poll loop).\n *\n * Operations never throw on a non-2xx: each returns `{ status, data, headers }`,\n * so a business gate (e.g. a UW/DRA readiness failure) comes back as a normal\n * response you inspect via `status`/`data`. Only transport errors throw.\n *\n * Note: config and the bearer token are process-global — construct one `ArpcSDK`\n * per process. A second instance reconfigures the first; instances are not\n * isolated (single-tenant by design).\n *\n * @example\n * ```typescript\n * const sdk = new ArpcSDK({\n * oauthUrl: \"https://oauth.stg.ffngcp.com\",\n * gatewayUrl: \"https://apis-gateway-v2.stg.fdrgcp.com\",\n * username: \"borrowbetter@seller.com\",\n * password: process.env.FDR_OAUTH_PASSWORD!,\n * });\n *\n * await sdk.authenticate();\n * const elig = await sdk.api.checkEligibility({ ... });\n * const faid = elig.data.application?.applicant?.fdr_applicant_id;\n * ```\n */\nexport class ArpcSDK {\n\t/** The full typed endpoint surface — one method per gateway operation. */\n\treadonly api = getARPCAchieveResolutionPartnerConnectAPI();\n\n\tconstructor(config: ArpcConfig) {\n\t\tconfigure(config);\n\t}\n\n\t/**\n\t * Exchange OAuth client credentials for a bearer JWT and store it for\n\t * subsequent `api` calls. Token TTL is ~900s — re-call to refresh.\n\t */\n\tauthenticate(): Promise<TokenResponse> {\n\t\treturn exchangeToken();\n\t}\n}\n"]}
1
+ {"version":3,"sources":["../src/config.ts","../src/auth.ts","../src/http/client.ts","../src/generated/arpc.ts","../src/index.ts"],"names":["ky"],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * SDK configuration.\n *\n * The library never reads the environment itself — a consumer constructs\n * `new ArpcSDK(config)`, which calls `configure()`. Where that config comes\n * from (env vars, a secrets manager, a config service) is the caller's business.\n */\n/**\n * Bearer-token store. The SDK authenticates lazily on the first gateway call:\n * it asks `get()` for a still-valid token and, on `null`, exchanges credentials\n * and hands the fresh token to `set()`. Owning expiry is the cache's job —\n * `get()` returns `null` once the token is stale (or near-stale). The default\n * store is in-memory and per-process; supply one to persist across restarts or\n * share across workers.\n */\nexport interface TokenCache {\n\t/** Return a valid bearer token, or `null` if absent/expired. */\n\tget(): Promise<string | null>;\n\t/** Persist a freshly exchanged token. `expiresAt` already includes the SDK's refresh skew. */\n\tset(token: string, expiresAt: Date): Promise<void>;\n}\n\n/** FDR deployment target. Selects both hosts — see `ARPC_ENDPOINTS`. */\nexport type ArpcEnvironment = \"dev\" | \"stg\" | \"prd\";\n\n/** The host pair a single environment resolves to. */\nexport interface ArpcEndpoint {\n\t/** OAuth host — token exchange only (`POST /v1/token`). */\n\treadonly oauth: string;\n\t/** API gateway host — every non-auth call. */\n\treadonly gateway: string;\n}\n\n/**\n * Per-environment hosts. The two differ by domain, not just subdomain: the\n * OAuth host is `ffngcp.com`, the gateway is `fdrgcp.com`.\n *\n * Frozen: this table backs both host resolution and environment validation, so\n * a consumer mutating it would quietly change SDK behavior.\n */\nexport const ARPC_ENDPOINTS: Readonly<Record<ArpcEnvironment, ArpcEndpoint>> =\n\tObject.freeze({\n\t\tdev: Object.freeze({\n\t\t\toauth: \"https://oauth.dev.ffngcp.com\",\n\t\t\tgateway: \"https://apis-gateway-v2.dev.fdrgcp.com\",\n\t\t}),\n\t\tstg: Object.freeze({\n\t\t\toauth: \"https://oauth.stg.ffngcp.com\",\n\t\t\tgateway: \"https://apis-gateway-v2.stg.fdrgcp.com\",\n\t\t}),\n\t\tprd: Object.freeze({\n\t\t\toauth: \"https://oauth.prd.ffngcp.com\",\n\t\t\tgateway: \"https://apis-gateway-v2.prd.fdrgcp.com\",\n\t\t}),\n\t});\n\n/**\n * Narrow a raw string to an `ArpcEnvironment`. Useful when the value arrives\n * untyped — an env var, a JSON config file, a request payload.\n */\nexport function isArpcEnvironment(value: string): value is ArpcEnvironment {\n\t// Own keys only — `in` would also match inherited members like `toString`.\n\treturn Object.keys(ARPC_ENDPOINTS).includes(value);\n}\n\nexport interface ArpcAuth {\n\t/**\n\t * OAuth client_id (HTTP Basic username). Also used as the lead's\n\t * `seller_agent_email` — the OAuth identity is a registered DRA agent.\n\t */\n\tusername: string;\n\t/** OAuth client_secret (HTTP Basic password). */\n\tpassword: string;\n\t/** Bearer-token store. Defaults to an in-memory, per-process cache. */\n\tcache?: TokenCache;\n}\n\n/**\n * Escape hatch — per-host overrides of the `environment` defaults. Omit a key\n * (or leave it `undefined`) to keep that environment's host.\n */\nexport interface ArpcUrls {\n\t/** OAuth host — token exchange only (`POST /v1/token`). */\n\toauth?: string;\n\t/** API gateway host — every non-auth call. */\n\tgateway?: string;\n}\n\nexport interface ArpcConfig {\n\t/** Selects the FDR environment. Drives both hosts. */\n\tenvironment: ArpcEnvironment;\n\tauth: ArpcAuth;\n\turls?: ArpcUrls;\n}\n\n/** Config as the internals consume it — hosts resolved to concrete strings. */\nexport interface ResolvedArpcConfig extends ArpcConfig {\n\turls: Required<ArpcUrls>;\n}\n\nlet current: ResolvedArpcConfig | null = null;\n\n/**\n * Fold the environment defaults together with any caller overrides. The guard\n * looks unreachable given the types, but `environment` routinely arrives from\n * somewhere untyped (an env var, a JSON config, a JS caller) — catch it here\n * rather than letting `undefined` reach a request URL.\n */\nfunction resolveUrls(config: ArpcConfig): Required<ArpcUrls> {\n\tif (!isArpcEnvironment(config.environment)) {\n\t\tthrow new Error(\n\t\t\t`Unknown environment: ${config.environment} (expected one of ${Object.keys(ARPC_ENDPOINTS).join(\", \")})`,\n\t\t);\n\t}\n\tconst defaults = ARPC_ENDPOINTS[config.environment];\n\treturn {\n\t\toauth: config.urls?.oauth ?? defaults.oauth,\n\t\tgateway: config.urls?.gateway ?? defaults.gateway,\n\t};\n}\n\n/** Set the active config, resolving hosts once. Called by the `ArpcSDK` constructor. */\nexport function configure(config: ArpcConfig): void {\n\tcurrent = { ...config, urls: resolveUrls(config) };\n}\n\n/** Read the active config. Throws if the SDK hasn't been constructed yet. */\nexport function getConfig(): ResolvedArpcConfig {\n\tif (!current) {\n\t\tthrow new Error(\n\t\t\t\"ArpcSDK is not configured — construct `new ArpcSDK(config)` before making calls\",\n\t\t);\n\t}\n\treturn current;\n}\n","import ky from \"ky\";\nimport { getConfig, type TokenCache } from \"./config\";\n\n// Token lifecycle lives in the orchestration layer, not the generated client:\n// the exchange targets the OAuth host (not the gateway), uses HTTP Basic, and is\n// form-encoded. Authentication is lazy — `resolveToken()` runs from the ky\n// beforeRequest hook on the first gateway call (and again once the token goes\n// stale), so callers never manage the token by hand.\nexport interface TokenResponse {\n\taccess_token: string;\n\ttoken_type?: string;\n\texpires_in?: number;\n}\n\n// Refresh this many ms before the server's stated expiry, so a token never\n// lapses mid-flight. Applied once, when we compute the stored `expiresAt`.\nconst TOKEN_SKEW_MS = 30_000;\n// FDR tokens are ~900s; fall back to that if the response omits expires_in.\nconst DEFAULT_TTL_S = 900;\n\n/** Raw credentials → bearer JWT. No storage; `resolveToken()` owns that. */\nexport async function exchangeToken(): Promise<TokenResponse> {\n\tconst { urls, auth } = getConfig();\n\tconst basic = Buffer.from(`${auth.username}:${auth.password}`).toString(\n\t\t\"base64\",\n\t);\n\n\treturn ky\n\t\t.post(`${urls.oauth}/v1/token`, {\n\t\t\theaders: { Authorization: `Basic ${basic}` },\n\t\t\tbody: new URLSearchParams({ grant_type: \"client_credentials\" }),\n\t\t})\n\t\t.json<TokenResponse>();\n}\n\n// Default store: in-memory, per-process. Used when config omits `cache`.\nlet memToken: string | null = null;\nlet memExpiresAt = 0;\nconst defaultCache: TokenCache = {\n\tasync get() {\n\t\treturn memToken && Date.now() < memExpiresAt ? memToken : null;\n\t},\n\tasync set(token, expiresAt) {\n\t\tmemToken = token;\n\t\tmemExpiresAt = expiresAt.getTime();\n\t},\n};\n\n// Single-flight guard: a cold burst of concurrent gateway calls performs one\n// exchange, not one per call.\nlet inflight: Promise<string> | null = null;\n\n/**\n * Return a valid bearer token, exchanging + caching on a miss. Called by the\n * gateway beforeRequest hook; not part of the public surface.\n */\nexport function resolveToken(): Promise<string> {\n\tconst store = getConfig().auth.cache ?? defaultCache;\n\tif (inflight) return inflight;\n\n\tinflight = (async () => {\n\t\tconst cached = await store.get();\n\t\tif (cached) return cached;\n\t\tconst res = await exchangeToken();\n\t\tconst ttl = (res.expires_in ?? DEFAULT_TTL_S) * 1000;\n\t\tawait store.set(\n\t\t\tres.access_token,\n\t\t\tnew Date(Date.now() + ttl - TOKEN_SKEW_MS),\n\t\t);\n\t\treturn res.access_token;\n\t})().finally(() => {\n\t\tinflight = null;\n\t});\n\treturn inflight;\n}\n","import ky from \"ky\";\nimport { resolveToken } from \"../auth\";\nimport { getConfig } from \"../config\";\n\n// ---------------------------------------------------------------------------\n// Orval custom mutator (ky-backed).\n//\n// Every generated operation calls `customInstance<T>(config, options)`. We\n// route all calls to the API gateway, attach the bearer token via a\n// beforeRequest hook (lazy auth — `resolveToken()` exchanges/refreshes as\n// needed), and return a small { status, data, headers } wrapper WITHOUT\n// throwing on non-2xx — the caller inspects status codes itself (202 +\n// Retry-After on program, 400 on a UW/DRA gate, etc.).\n// ---------------------------------------------------------------------------\n\n// Shape Orval passes as the first arg (axios-style request config).\nexport interface RequestConfig {\n\turl: string;\n\tmethod: string;\n\tparams?: Record<string, unknown>;\n\tdata?: unknown;\n\theaders?: Record<string, string>;\n\tsignal?: AbortSignal;\n\tresponseType?: string;\n}\n\nexport interface HttpResponse<T> {\n\tstatus: number;\n\tdata: T;\n\theaders: Headers;\n}\n\nexport const customInstance = async <T>(\n\trequest: RequestConfig,\n\toptions?: Partial<RequestConfig>,\n): Promise<HttpResponse<T>> => {\n\tconst { url, method, params, data, signal } = { ...request, ...options };\n\t// Merge headers from both sides — a per-call override (e.g. an\n\t// x-correlation-id in `options.headers`) must not drop the operation's\n\t// Content-Type. The bearer token is layered on last, below.\n\tconst headers = { ...request.headers, ...options?.headers };\n\n\tconst target = new URL(url, getConfig().urls.gateway);\n\tif (params) {\n\t\tfor (const [key, value] of Object.entries(params)) {\n\t\t\tif (value !== undefined && value !== null)\n\t\t\t\ttarget.searchParams.set(key, String(value));\n\t\t}\n\t}\n\n\tconst response = await ky(target, {\n\t\tmethod,\n\t\theaders,\n\t\thooks: {\n\t\t\t// Lazy bearer injection: resolveToken() exchanges on the first call\n\t\t\t// and refreshes once the cached token goes stale. The OAuth exchange\n\t\t\t// itself runs on a bare ky.post (no hook), so there's no recursion.\n\t\t\tbeforeRequest: [\n\t\t\t\tasync (req) => {\n\t\t\t\t\treq.headers.set(\"Authorization\", `Bearer ${await resolveToken()}`);\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t\t...(data !== undefined ? { json: data } : {}),\n\t\tsignal,\n\t\tthrowHttpErrors: false,\n\t\tretry: 0,\n\t\t// Some gateway calls (program generation in particular) can take well over ky's\n\t\t// 10s default to respond. Raise the per-request ceiling; the program poll loop\n\t\t// handles 202/Retry-After on top of this.\n\t\ttimeout: 60_000,\n\t});\n\n\tconst text = await response.text();\n\tlet body: T;\n\ttry {\n\t\tbody = (text ? JSON.parse(text) : undefined) as T;\n\t} catch {\n\t\t// Non-JSON body (e.g. a gateway HTML error page on a transient 5xx). Don't throw —\n\t\t// hand back the raw text so callers can inspect status/body and retry as needed.\n\t\tbody = text as unknown as T;\n\t}\n\treturn { status: response.status, data: body, headers: response.headers };\n};\n","/**\n * Generated by orval v7.21.0 🍺\n * Do not edit manually.\n * ARPC — Achieve Resolution Partner Connect API\n * FDR Wholesale API — A Hybrid Integration Model designed to maximise partner velocity without compromising enterprise data standards. DEX (Digital Enrollment Experience) is the partner-facing integration layer that powers the full applicant lifecycle. The `fdr_applicant_id` acts as the applicant's passport across all systems, persisted from first eligibility check through enrollment. All endpoints require a valid Bearer JWT with the `arpc.all` role.\n * OpenAPI spec version: 2026.15.0\n */\nimport type {\n ARPCErrorResponse,\n ApplicationSendEmailResponse,\n BankUpdateRequest,\n CreateLeadRequest,\n CreateLeadResponse,\n DraRequest,\n DraResponse,\n EligibilityRequest,\n EligibilityResponse,\n LeadPatchRequest,\n LeadResponse,\n LeadTransferRequest,\n LeadTransferResponse,\n ProgramRequest,\n ProgramSummaryTaskRequest,\n ProgramSummaryTaskResponse,\n ReadinessResponse,\n RegisterApplicantResponse,\n RegisterApplicantV2Request,\n SpinwheelNetworkUserRequest,\n SpinwheelNetworkUserResponse,\n StatelessReadinessRequest,\n UwSubmissionV2Response\n} from './model';\n\nimport { customInstance } from '../http/client';\ntype SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];\n\n\n export const getARPCAchieveResolutionPartnerConnectAPI = () => {\n/**\n * Synchronous Orchestration: Handles both lead creation and income/expense data in one step, eliminating the async polling black box. Data authority shifts here — Achieve (SUIP SF) becomes the Source of Truth once the lead is formally promoted. Returns the full lead creation result including program options.\n * @summary Sync wholesale lead creation\n */\nconst createLeadSync = (\n createLeadRequest: CreateLeadRequest,\n options?: SecondParameter<typeof customInstance<CreateLeadResponse>>,) => {\n return customInstance<CreateLeadResponse>(\n {url: `/v3/application`, method: 'POST',\n headers: {'Content-Type': 'application/json', },\n data: createLeadRequest\n },\n options);\n }\n \n/**\n * Stateless Readiness Check: Pre-flight validation before initiating program calculations. Catches duplicate SSNs or emails early to prevent downstream failures. Requires a valid Authorization header.\n * @summary Check application readiness\n */\nconst checkApplicationReadiness = (\n statelessReadinessRequest: StatelessReadinessRequest,\n options?: SecondParameter<typeof customInstance<ReadinessResponse>>,) => {\n return customInstance<ReadinessResponse>(\n {url: `/v1/application/readiness`, method: 'POST',\n headers: {'Content-Type': 'application/json', },\n data: statelessReadinessRequest\n },\n options);\n }\n \n/**\n * Recovery endpoint — only required when the async Spinwheel user creation triggered by `POST /v1/applicant/register` fails due to a user error (e.g. invalid SSN or DOB). Not needed in the happy path. Authenticates the seller, resolves the law firm / program, creates an applicant ID when needed, and connects the applicant to the Spinwheel network. Requires a valid Authorization header.\n * @summary Create / connect Spinwheel network user\n */\nconst createSpinwheelNetworkUser = (\n spinwheelNetworkUserRequest: SpinwheelNetworkUserRequest,\n options?: SecondParameter<typeof customInstance<SpinwheelNetworkUserResponse>>,) => {\n return customInstance<SpinwheelNetworkUserResponse>(\n {url: `/v1/application/spinwheel-network-user`, method: 'POST',\n headers: {'Content-Type': 'application/json', },\n data: spinwheelNetworkUserRequest\n },\n options);\n }\n \n/**\n * Authenticates the seller, validates banking disclosure requirements, optionally validates the submitted account and routing numbers, patches the program, and returns the refreshed lead payload. Requires a valid Authorization header.\n * @summary Update applicant bank details\n */\nconst updateApplicationBankDetails = (\n applicantId: string,\n bankUpdateRequest: BankUpdateRequest,\n options?: SecondParameter<typeof customInstance<LeadResponse>>,) => {\n return customInstance<LeadResponse>(\n {url: `/v1/application/bank-update/${applicantId}`, method: 'PATCH',\n headers: {'Content-Type': 'application/json', },\n data: bankUpdateRequest\n },\n options);\n }\n \n/**\n * Banking Disclosure: Manually triggers the banking disclosure email. **This endpoint is optional** — it is only needed when the disclosure was not already sent automatically by `POST /v2/application/uw-submission/{fdrApplicantId}` (which auto-sends on full UW approval). Use this endpoint for conditional approvals or when the v1 UW submission path is in use. A critical legal prerequisite that must be completed before enrollment can proceed. Requires a valid Authorization header.\n * @summary Send banking disclosure email (optional — only if not auto-sent by v2 UW submission)\n */\nconst sendEmail = (\n fdrApplicantId: string,\n options?: SecondParameter<typeof customInstance<ApplicationSendEmailResponse>>,) => {\n return customInstance<ApplicationSendEmailResponse>(\n {url: `/v1/application/send-email/${fdrApplicantId}`, method: 'POST'\n },\n options);\n }\n \n/**\n * Submits a lead evaluation request. fdr_applicant_id is required in the request body. Requires a valid Authorization header with the arpc.all role.\n * @summary Transfer a lead to the debt-resolution program\n */\nconst leadTransfer = (\n leadTransferRequest: LeadTransferRequest,\n options?: SecondParameter<typeof customInstance<LeadTransferResponse>>,) => {\n return customInstance<LeadTransferResponse>(\n {url: `/v1/application/lead-transfer`, method: 'POST',\n headers: {'Content-Type': 'application/json', },\n data: leadTransferRequest\n },\n options);\n }\n \n/**\n * Fetches the current state of an application / lead record by FDR applicant ID. Requires a valid Authorization header.\n * @summary Read application / lead details\n */\nconst readLead = (\n fdrApplicantId: string,\n options?: SecondParameter<typeof customInstance<LeadResponse>>,) => {\n return customInstance<LeadResponse>(\n {url: `/v1/application/${fdrApplicantId}`, method: 'GET'\n },\n options);\n }\n \n/**\n * Applies a partial update to an existing wholesale lead record. Requires a valid Authorization header and an x-correlation-id header for request tracing.\n * @summary Partial update of application / lead record\n */\nconst patchLead = (\n fdrApplicantId: string,\n leadPatchRequest: LeadPatchRequest,\n options?: SecondParameter<typeof customInstance<LeadResponse>>,) => {\n return customInstance<LeadResponse>(\n {url: `/v1/application/${fdrApplicantId}`, method: 'PATCH',\n headers: {'Content-Type': 'application/json', },\n data: leadPatchRequest\n },\n options);\n }\n \n/**\n * DRA Readiness Check: Final pre-flight confirming the lead is approved and all prerequisites are met before initiating the embedded signing session. Requires a valid Authorization header.\n * @summary Get application readiness status\n */\nconst getApplicationReadiness = (\n applicantId: string,\n options?: SecondParameter<typeof customInstance<ReadinessResponse>>,) => {\n return customInstance<ReadinessResponse>(\n {url: `/v1/application/${applicantId}/readiness`, method: 'GET'\n },\n options);\n }\n \n/**\n * Embedded DRA Signing: Returns an Embedded Signing URL so the applicant signs the Debt Resolution Agreement via DocuSign directly within the Symple UI, preventing session breakage. Successful legal execution triggers automatic enrollment. Requires a valid Authorization header.\n * @summary Generate Debt Resolution Agreement (DRA)\n */\nconst generateDra = (\n applicantId: string,\n draRequest: DraRequest,\n options?: SecondParameter<typeof customInstance<DraResponse>>,) => {\n return customInstance<DraResponse>(\n {url: `/v2/application/${applicantId}/dra`, method: 'POST',\n headers: {'Content-Type': 'application/json', },\n data: draRequest\n },\n options);\n }\n \n/**\n * **Recommended endpoint.** `POST /v1/applicant/register` is deprecated and will be retired by end of July 2026 (deadline can be extended on request if your team needs more time). Identity Anchoring: Persists applicant PII to the Managed Package and generates the `fdr_applicant_id` — the applicant's passport across all systems. Routes to the correct Salesforce target (MP or WiB) based on seller identity, and triggers the async credit fetch. Requires a valid Authorization header.\n * @summary Seller-aware applicant registration (v2 — unified ARPCErrorResponse envelope) — recommended\n */\nconst registerApplicantV2 = (\n registerApplicantV2Request: RegisterApplicantV2Request,\n options?: SecondParameter<typeof customInstance<RegisterApplicantResponse>>,) => {\n return customInstance<RegisterApplicantResponse>(\n {url: `/v2/applicant/register`, method: 'POST',\n headers: {'Content-Type': 'application/json', },\n data: registerApplicantV2Request\n },\n options);\n }\n \n/**\n * Async Polling Loop: Call this single endpoint for both initial generation and refresh. Returns 202 Accepted with a Retry-After header while credit is still pending. Once ready, the Managed Package orchestrates the calculation and persists the program result. Requires a valid Authorization header.\n * @summary Generate application program (v1)\n */\nconst generateProgramV1 = (\n programRequest: ProgramRequest,\n options?: SecondParameter<typeof customInstance<LeadResponse>>,) => {\n return customInstance<LeadResponse>(\n {url: `/v1/application/program`, method: 'POST',\n headers: {'Content-Type': 'application/json', },\n data: programRequest\n },\n options);\n }\n \n/**\n * Eligibility Scout: A stateless, real-time quote based on anonymised data — no PII required. Returns estimated program options to anchor the conversation before any registration occurs. Requires a valid Authorization header.\n * @summary Check application eligibility\n */\nconst checkEligibility = (\n eligibilityRequest: EligibilityRequest,\n options?: SecondParameter<typeof customInstance<EligibilityResponse>>,) => {\n return customInstance<EligibilityResponse>(\n {url: `/v2/application/eligibility`, method: 'POST',\n headers: {'Content-Type': 'application/json', },\n data: eligibilityRequest\n },\n options);\n }\n \n/**\n * **Recommended endpoint — supersedes v1.** Submits a wholesale application to the underwriting system. On full UW approval (`sub_status = 'UW Approved'`) the banking disclosure email is sent automatically — no separate `send-email` call is needed in this path. Conditional approvals suppress the auto-send; in that case the manual `POST /v1/application/send-email/{fdrApplicantId}` endpoint can be used. Uses the SF Object API directly (no suip-proxy hop). Requires a valid Authorization header.\n * @summary Submit application to underwriting (v2 — auto banking disclosure) — recommended\n */\nconst uwSubmissionV2 = (\n fdrApplicantId: string,\n options?: SecondParameter<typeof customInstance<UwSubmissionV2Response>>,) => {\n return customInstance<UwSubmissionV2Response>(\n {url: `/v2/application/uw-submission/${fdrApplicantId}`, method: 'POST'\n },\n options);\n }\n \n/**\n * Creates a SUIP Salesforce 'Program Summary Recording' task for the applicant identified by the {id} path variable. For DEX (or missing header), the parent SF opportunity is additionally PATCHed with the seller-config partner default PSC. For DC, the task is created and the parent SF opportunity is PATCHed only when ws_psc_code is provided in the request payload.\n * @summary Create program-summary recording task\n */\nconst createProgramSummaryTask = (\n id: string,\n programSummaryTaskRequest: ProgramSummaryTaskRequest,\n options?: SecondParameter<typeof customInstance<ProgramSummaryTaskResponse>>,) => {\n return customInstance<ProgramSummaryTaskResponse>(\n {url: `/v2/application/program-summary-task/${id}`, method: 'POST',\n headers: {'Content-Type': 'application/json', },\n data: programSummaryTaskRequest\n },\n options);\n }\n \nreturn {createLeadSync,checkApplicationReadiness,createSpinwheelNetworkUser,updateApplicationBankDetails,sendEmail,leadTransfer,readLead,patchLead,getApplicationReadiness,generateDra,registerApplicantV2,generateProgramV1,checkEligibility,uwSubmissionV2,createProgramSummaryTask}};\nexport type CreateLeadSyncResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['createLeadSync']>>>\nexport type CheckApplicationReadinessResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['checkApplicationReadiness']>>>\nexport type CreateSpinwheelNetworkUserResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['createSpinwheelNetworkUser']>>>\nexport type UpdateApplicationBankDetailsResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['updateApplicationBankDetails']>>>\nexport type SendEmailResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['sendEmail']>>>\nexport type LeadTransferResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['leadTransfer']>>>\nexport type ReadLeadResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['readLead']>>>\nexport type PatchLeadResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['patchLead']>>>\nexport type GetApplicationReadinessResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['getApplicationReadiness']>>>\nexport type GenerateDraResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['generateDra']>>>\nexport type RegisterApplicantV2Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['registerApplicantV2']>>>\nexport type GenerateProgramV1Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['generateProgramV1']>>>\nexport type CheckEligibilityResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['checkEligibility']>>>\nexport type UwSubmissionV2Result = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['uwSubmissionV2']>>>\nexport type CreateProgramSummaryTaskResult = NonNullable<Awaited<ReturnType<ReturnType<typeof getARPCAchieveResolutionPartnerConnectAPI>['createProgramSummaryTask']>>>\n","import { type ArpcConfig, configure } from \"./config\";\nimport { getARPCAchieveResolutionPartnerConnectAPI } from \"./generated/arpc\";\n\nexport type {\n\tArpcAuth,\n\tArpcConfig,\n\tArpcEndpoint,\n\tArpcEnvironment,\n\tArpcUrls,\n\tTokenCache,\n} from \"./config\";\nexport { ARPC_ENDPOINTS, isArpcEnvironment } from \"./config\";\nexport type * from \"./generated/model\";\nexport type { HttpResponse } from \"./http/client\";\n\n/**\n * ARPC SDK — a typed, agnostic client for FDR's ARPC DEX API (Achieve\n * Resolution Partner Connect, Digital Enrollment Experience).\n *\n * Thin by design: it owns the two-host routing (both derived from\n * `environment`), the OAuth token lifecycle, and bearer injection, and exposes\n * the full endpoint surface as typed operations on `sdk.api`. It does not\n * orchestrate the enrollment sequence — the caller\n * assembles payloads and threads the `fdr_applicant_id` between calls. See\n * `scripts/smoke.ts` for a full end-to-end reference sequence (incl. the program\n * poll loop).\n *\n * Auth is lazy: the first `api` call exchanges credentials for a bearer JWT and\n * caches it (~900s TTL), then re-exchanges shortly before it expires. There's no\n * explicit login step. Supply `auth.cache` to persist the token across restarts\n * or share it across workers; otherwise it's held in-process.\n *\n * Operations never throw on a non-2xx: each returns `{ status, data, headers }`,\n * so a business gate (e.g. a UW/DRA readiness failure) comes back as a normal\n * response you inspect via `status`/`data`. Only transport errors throw.\n *\n * Note: config and the default token cache are process-global — construct one\n * `ArpcSDK` per process. A second instance reconfigures the first; instances are\n * not isolated (single-tenant by design).\n *\n * @example\n * ```typescript\n * const sdk = new ArpcSDK({\n * environment: \"stg\",\n * auth: {\n * username: \"borrowbetter@seller.com\",\n * password: process.env.ARPC_OAUTH_PASSWORD!,\n * },\n * });\n *\n * // The first call authenticates automatically and caches the token.\n * const elig = await sdk.api.checkEligibility({ ... });\n * const faid = elig.data.application?.applicant?.fdr_applicant_id;\n * ```\n */\nexport class ArpcSDK {\n\t/** The full typed endpoint surface — one method per gateway operation. */\n\treadonly api = getARPCAchieveResolutionPartnerConnectAPI();\n\n\tconstructor(config: ArpcConfig) {\n\t\tconfigure(config);\n\t}\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@borrowbetter/arpcsdk",
3
- "version": "0.1.1",
3
+ "version": "0.3.0",
4
4
  "description": "Typed, agnostic client for FDR's ARPC DEX API — Achieve Resolution Partner Connect (Digital Enrollment Experience)",
5
5
  "author": "BorrowBetter",
6
6
  "license": "MIT",