@qcobro/sdk 1.11.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 +105 -0
- package/dist/client.d.ts +132 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +192 -0
- package/dist/client.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/resources/portfolios.d.ts +140 -0
- package/dist/resources/portfolios.d.ts.map +1 -0
- package/dist/resources/portfolios.js +59 -0
- package/dist/resources/portfolios.js.map +1 -0
- package/dist/schemas.d.ts +26 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/schemas.js +25 -0
- package/dist/schemas.js.map +1 -0
- package/package.json +37 -0
package/README.md
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# @qcobro/sdk
|
|
2
|
+
|
|
3
|
+
A developer-friendly TypeScript SDK for the [QCobro](https://qcobro.com) API. It wraps the
|
|
4
|
+
server's tRPC interface behind an ergonomic, fully-typed `Client` so you can manage QCobro
|
|
5
|
+
resources without touching transport details.
|
|
6
|
+
|
|
7
|
+
This release covers **portfolios**, including account synchronization. More resources land in
|
|
8
|
+
follow-up releases.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install @qcobro/sdk
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Requires a runtime with a global `fetch` (Node ≥18 or a modern browser). For older runtimes,
|
|
17
|
+
pass a `fetch` polyfill via the `fetch` option.
|
|
18
|
+
|
|
19
|
+
## Quick start
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import { Client } from "@qcobro/sdk";
|
|
23
|
+
|
|
24
|
+
const client = new Client({ endpoint: "https://api.qcobro.com" });
|
|
25
|
+
|
|
26
|
+
// Authenticate and pick the workspace to act in.
|
|
27
|
+
await client.login({ email: "me@acme.com", password: process.env.QCOBRO_PASSWORD! });
|
|
28
|
+
client.useWorkspace("WO6ueex0qan9ojhf820wgiae3qi5luy08y");
|
|
29
|
+
|
|
30
|
+
// ...or, for unattended/server-to-server integrations, use a workspace API key:
|
|
31
|
+
await client.loginWithApiKey({
|
|
32
|
+
accessKeyId: "WO6ueex0qan9ojhf820wgiae3qi5luy08y",
|
|
33
|
+
accessKeySecret: process.env.QCOBRO_API_SECRET!
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Manage portfolios.
|
|
37
|
+
const portfolio = await client.portfolios.create({
|
|
38
|
+
name: "Q3 delinquencies",
|
|
39
|
+
clientId: "acme",
|
|
40
|
+
currency: "USD"
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const { items, total } = await client.portfolios.listAccounts({ portfolioId: portfolio.id });
|
|
44
|
+
|
|
45
|
+
await client.portfolios.syncAccounts({
|
|
46
|
+
portfolioId: portfolio.id,
|
|
47
|
+
mode: "APPEND_ONLY",
|
|
48
|
+
rows: [{ externalId: "A-1", fullName: "Jane Doe", outstandingBalance: 1200.5 }]
|
|
49
|
+
});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Authentication & tokens
|
|
53
|
+
|
|
54
|
+
Authenticate one of two ways:
|
|
55
|
+
|
|
56
|
+
- `login({ email, password })` — interactive credentials login (returns id/access/refresh tokens).
|
|
57
|
+
- `loginWithApiKey({ accessKeyId, accessKeySecret })` — a workspace API key, for unattended
|
|
58
|
+
server-to-server use where there's no human to type a password.
|
|
59
|
+
|
|
60
|
+
The `Client` holds tokens **in memory**. Persisting them (so a session survives a restart) is
|
|
61
|
+
your responsibility:
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
// Save after login...
|
|
65
|
+
const tokens = client.getTokens();
|
|
66
|
+
// ...and restore later.
|
|
67
|
+
const client = new Client({ endpoint }).setTokens(tokens);
|
|
68
|
+
|
|
69
|
+
// Refresh an expired access token.
|
|
70
|
+
await client.refresh();
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**Auto-refresh.** By default, if a call returns `UNAUTHORIZED` and a refresh token is held, the
|
|
74
|
+
client refreshes the access token **once** and replays the request transparently — concurrent
|
|
75
|
+
failures share a single refresh. If the refresh itself fails, the original `UNAUTHORIZED` is
|
|
76
|
+
surfaced. Disable it with `new Client({ endpoint, autoRefresh: false })`.
|
|
77
|
+
|
|
78
|
+
## Validation & errors
|
|
79
|
+
|
|
80
|
+
Inputs are validated client-side against the shared QCobro schemas **before** a request is
|
|
81
|
+
sent. Invalid input throws a `ValidationError` with field-level details, and no network call is
|
|
82
|
+
made. Server-side authorization failures (e.g. an unauthenticated or wrong-workspace call)
|
|
83
|
+
surface as the server's error.
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
import { ValidationError } from "@qcobro/sdk";
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
await client.portfolios.create({ name: "", clientId: "acme", currency: "USD" });
|
|
90
|
+
} catch (err) {
|
|
91
|
+
if (err instanceof ValidationError) {
|
|
92
|
+
console.error(err.fieldErrors); // [{ field: "name", message: "...", code: "..." }]
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## API reference
|
|
98
|
+
|
|
99
|
+
Generate the markdown API reference with:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
npm run docs
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Output lands in `docs/`.
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { type CreateTRPCClient } from "@trpc/client";
|
|
2
|
+
import type { AppRouter } from "@qcobro/apiserver";
|
|
3
|
+
import type { LoginInput, ApiKeyLoginInput } from "@qcobro/common";
|
|
4
|
+
import { PortfoliosResource } from "./resources/portfolios.js";
|
|
5
|
+
/** Tokens issued by the QCobro Identity service. */
|
|
6
|
+
export interface Tokens {
|
|
7
|
+
/** Short-lived bearer token attached to every authenticated request. */
|
|
8
|
+
accessToken?: string;
|
|
9
|
+
/** Long-lived token used to obtain a fresh access token via {@link Client.refresh}. */
|
|
10
|
+
refreshToken?: string;
|
|
11
|
+
/** JWT describing the authenticated user (claims), when issued. */
|
|
12
|
+
idToken?: string;
|
|
13
|
+
}
|
|
14
|
+
/** Options for constructing a {@link Client}. */
|
|
15
|
+
export interface ClientOptions {
|
|
16
|
+
/**
|
|
17
|
+
* Base URL of the QCobro API. Defaults to `https://api.qcobro.com`; override
|
|
18
|
+
* only to target another environment (e.g. `http://localhost:3000`). The SDK
|
|
19
|
+
* appends the `/trpc` path itself.
|
|
20
|
+
*/
|
|
21
|
+
endpoint?: string;
|
|
22
|
+
/**
|
|
23
|
+
* `fetch` implementation to use. Defaults to the global `fetch` (Node ≥18 and
|
|
24
|
+
* modern browsers). Provide this to supply a polyfill in older runtimes.
|
|
25
|
+
*/
|
|
26
|
+
fetch?: typeof globalThis.fetch;
|
|
27
|
+
/** An access token to start authenticated, instead of calling {@link Client.login}. */
|
|
28
|
+
accessToken?: string;
|
|
29
|
+
/** A refresh token, enabling {@link Client.refresh} without re-login. */
|
|
30
|
+
refreshToken?: string;
|
|
31
|
+
/** The accessKeyId of the workspace to act in. Also settable via {@link Client.useWorkspace}. */
|
|
32
|
+
workspace?: string;
|
|
33
|
+
/**
|
|
34
|
+
* When `true` (the default), an `UNAUTHORIZED` response triggers a single
|
|
35
|
+
* token refresh and one replay of the failed request, provided a refresh
|
|
36
|
+
* token is held. Set to `false` to disable and surface `UNAUTHORIZED` directly.
|
|
37
|
+
*/
|
|
38
|
+
autoRefresh?: boolean;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The QCobro API client.
|
|
42
|
+
*
|
|
43
|
+
* A single `Client` owns the connection and the authentication lifecycle, and
|
|
44
|
+
* exposes resources as namespaces with friendly methods (e.g.
|
|
45
|
+
* {@link Client.portfolios}). It transparently attaches the bearer token and the
|
|
46
|
+
* active-workspace header to every request.
|
|
47
|
+
*
|
|
48
|
+
* Tokens are held **in memory only** — persisting them (if you want sessions to
|
|
49
|
+
* survive a restart) is the caller's responsibility via {@link Client.getTokens}
|
|
50
|
+
* and {@link Client.setTokens}.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```ts
|
|
54
|
+
* const client = new Client();
|
|
55
|
+
* await client.login({ email: "me@acme.com", password: process.env.QCOBRO_PASSWORD! });
|
|
56
|
+
* client.useWorkspace("WO6ueex0qan9ojhf820wgiae3qi5luy08y");
|
|
57
|
+
*
|
|
58
|
+
* await client.portfolios.create({ name: "Q3 delinquencies", clientId: "acme" });
|
|
59
|
+
* const portfolios = await client.portfolios.list();
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export declare class Client {
|
|
63
|
+
#private;
|
|
64
|
+
/**
|
|
65
|
+
* The underlying typed tRPC proxy. Exposed as an escape hatch for procedures
|
|
66
|
+
* the SDK does not yet wrap; prefer the resource namespaces where available.
|
|
67
|
+
*/
|
|
68
|
+
readonly trpc: CreateTRPCClient<AppRouter>;
|
|
69
|
+
/** Portfolio operations (list, get, create, update, delete, accounts, sync). */
|
|
70
|
+
readonly portfolios: PortfoliosResource;
|
|
71
|
+
constructor(options?: ClientOptions);
|
|
72
|
+
/**
|
|
73
|
+
* Run a request, transparently refreshing the access token once on an
|
|
74
|
+
* `UNAUTHORIZED` error and replaying the request. Used internally by the
|
|
75
|
+
* resource namespaces.
|
|
76
|
+
*
|
|
77
|
+
* Refresh happens at most once per failed request: if the replay also fails,
|
|
78
|
+
* that error is surfaced (no retry loop). Concurrent failures share a single
|
|
79
|
+
* in-flight refresh. If auto-refresh is disabled, no refresh token is held, or
|
|
80
|
+
* the refresh itself fails, the original error is surfaced unchanged.
|
|
81
|
+
*
|
|
82
|
+
* @internal
|
|
83
|
+
*/
|
|
84
|
+
request<T>(fn: () => Promise<T>): Promise<T>;
|
|
85
|
+
/**
|
|
86
|
+
* Authenticate with email and password. On success the issued access (and
|
|
87
|
+
* refresh) token is stored on the client and used for subsequent calls.
|
|
88
|
+
*
|
|
89
|
+
* @returns the issued tokens.
|
|
90
|
+
*/
|
|
91
|
+
login(input: LoginInput): Promise<Tokens>;
|
|
92
|
+
/**
|
|
93
|
+
* Authenticate with a workspace API key (accessKeyId + accessKeySecret),
|
|
94
|
+
* intended for unattended, server-to-server integrations. On success the
|
|
95
|
+
* issued access (and refresh) token is stored on the client and used for
|
|
96
|
+
* subsequent calls.
|
|
97
|
+
*
|
|
98
|
+
* @returns the issued tokens.
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```ts
|
|
102
|
+
* await client.loginWithApiKey({
|
|
103
|
+
* accessKeyId: "WO6ueex0qan9ojhf820wgiae3qi5luy08y",
|
|
104
|
+
* accessKeySecret: process.env.QCOBRO_API_SECRET!
|
|
105
|
+
* });
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
loginWithApiKey(input: ApiKeyLoginInput): Promise<Tokens>;
|
|
109
|
+
/**
|
|
110
|
+
* Exchange a refresh token for a fresh access token. Uses the refresh token
|
|
111
|
+
* held by the client unless one is passed explicitly. The new access token
|
|
112
|
+
* replaces the current one.
|
|
113
|
+
*
|
|
114
|
+
* @returns the issued tokens.
|
|
115
|
+
*/
|
|
116
|
+
refresh(refreshToken?: string): Promise<Tokens>;
|
|
117
|
+
/**
|
|
118
|
+
* Select the active workspace by its accessKeyId. Subsequent workspace-scoped
|
|
119
|
+
* calls act within this workspace.
|
|
120
|
+
*/
|
|
121
|
+
useWorkspace(accessKeyId: string): this;
|
|
122
|
+
/** The accessKeyId of the active workspace, or `undefined` if none is selected. */
|
|
123
|
+
get workspace(): string | undefined;
|
|
124
|
+
/** Returns the tokens currently held by the client (in memory). */
|
|
125
|
+
getTokens(): Tokens;
|
|
126
|
+
/**
|
|
127
|
+
* Replace the tokens held by the client. Useful to resume a session from
|
|
128
|
+
* tokens you persisted yourself.
|
|
129
|
+
*/
|
|
130
|
+
setTokens(tokens: Tokens): this;
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmC,KAAK,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACtF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAQ/D,oDAAoD;AACpD,MAAM,WAAW,MAAM;IACrB,wEAAwE;IACxE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uFAAuF;IACvF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mEAAmE;IACnE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,iDAAiD;AACjD,MAAM,WAAW,aAAa;IAC5B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAChC,uFAAuF;IACvF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,yEAAyE;IACzE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iGAAiG;IACjG,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,MAAM;;IACjB;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC,SAAS,CAAC,CAAC;IAE3C,gFAAgF;IAChF,QAAQ,CAAC,UAAU,EAAE,kBAAkB,CAAC;gBAS5B,OAAO,GAAE,aAAkB;IA2BvC;;;;;;;;;;;OAWG;IACG,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAgClD;;;;;OAKG;IACG,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC;IAO/C;;;;;;;;;;;;;;;OAeG;IACG,eAAe,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC;IAO/D;;;;;;OAMG;IACG,OAAO,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAWrD;;;OAGG;IACH,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI;IAKvC,mFAAmF;IACnF,IAAI,SAAS,IAAI,MAAM,GAAG,SAAS,CAElC;IAED,mEAAmE;IACnE,SAAS,IAAI,MAAM;IAInB;;;OAGG;IACH,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;CAKhC"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { createTRPCClient, httpBatchLink } from "@trpc/client";
|
|
2
|
+
import { PortfoliosResource } from "./resources/portfolios.js";
|
|
3
|
+
/** Header the apiserver reads to scope a request to a workspace. */
|
|
4
|
+
const WORKSPACE_HEADER = "x-workspace";
|
|
5
|
+
/** Default QCobro API base URL, used when no `endpoint` is provided. */
|
|
6
|
+
const DEFAULT_ENDPOINT = "https://api.qcobro.com";
|
|
7
|
+
/**
|
|
8
|
+
* The QCobro API client.
|
|
9
|
+
*
|
|
10
|
+
* A single `Client` owns the connection and the authentication lifecycle, and
|
|
11
|
+
* exposes resources as namespaces with friendly methods (e.g.
|
|
12
|
+
* {@link Client.portfolios}). It transparently attaches the bearer token and the
|
|
13
|
+
* active-workspace header to every request.
|
|
14
|
+
*
|
|
15
|
+
* Tokens are held **in memory only** — persisting them (if you want sessions to
|
|
16
|
+
* survive a restart) is the caller's responsibility via {@link Client.getTokens}
|
|
17
|
+
* and {@link Client.setTokens}.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* const client = new Client();
|
|
22
|
+
* await client.login({ email: "me@acme.com", password: process.env.QCOBRO_PASSWORD! });
|
|
23
|
+
* client.useWorkspace("WO6ueex0qan9ojhf820wgiae3qi5luy08y");
|
|
24
|
+
*
|
|
25
|
+
* await client.portfolios.create({ name: "Q3 delinquencies", clientId: "acme" });
|
|
26
|
+
* const portfolios = await client.portfolios.list();
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export class Client {
|
|
30
|
+
/**
|
|
31
|
+
* The underlying typed tRPC proxy. Exposed as an escape hatch for procedures
|
|
32
|
+
* the SDK does not yet wrap; prefer the resource namespaces where available.
|
|
33
|
+
*/
|
|
34
|
+
trpc;
|
|
35
|
+
/** Portfolio operations (list, get, create, update, delete, accounts, sync). */
|
|
36
|
+
portfolios;
|
|
37
|
+
#accessToken;
|
|
38
|
+
#refreshToken;
|
|
39
|
+
#workspace;
|
|
40
|
+
#autoRefresh;
|
|
41
|
+
// Shared in-flight refresh, so concurrent UNAUTHORIZED calls refresh once.
|
|
42
|
+
#refreshInFlight = null;
|
|
43
|
+
constructor(options = {}) {
|
|
44
|
+
this.#accessToken = options.accessToken;
|
|
45
|
+
this.#refreshToken = options.refreshToken;
|
|
46
|
+
this.#workspace = options.workspace;
|
|
47
|
+
this.#autoRefresh = options.autoRefresh ?? true;
|
|
48
|
+
const url = `${(options.endpoint ?? DEFAULT_ENDPOINT).replace(/\/+$/, "")}/trpc`;
|
|
49
|
+
this.trpc = createTRPCClient({
|
|
50
|
+
links: [
|
|
51
|
+
httpBatchLink({
|
|
52
|
+
url,
|
|
53
|
+
fetch: options.fetch,
|
|
54
|
+
// Read current auth state on every request so a `login()` or
|
|
55
|
+
// `useWorkspace()` after construction applies without rebuilding.
|
|
56
|
+
headers: () => {
|
|
57
|
+
const headers = {};
|
|
58
|
+
if (this.#accessToken)
|
|
59
|
+
headers.Authorization = `Bearer ${this.#accessToken}`;
|
|
60
|
+
if (this.#workspace)
|
|
61
|
+
headers[WORKSPACE_HEADER] = this.#workspace;
|
|
62
|
+
return headers;
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
]
|
|
66
|
+
});
|
|
67
|
+
this.portfolios = new PortfoliosResource(this.trpc, (fn) => this.request(fn));
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Run a request, transparently refreshing the access token once on an
|
|
71
|
+
* `UNAUTHORIZED` error and replaying the request. Used internally by the
|
|
72
|
+
* resource namespaces.
|
|
73
|
+
*
|
|
74
|
+
* Refresh happens at most once per failed request: if the replay also fails,
|
|
75
|
+
* that error is surfaced (no retry loop). Concurrent failures share a single
|
|
76
|
+
* in-flight refresh. If auto-refresh is disabled, no refresh token is held, or
|
|
77
|
+
* the refresh itself fails, the original error is surfaced unchanged.
|
|
78
|
+
*
|
|
79
|
+
* @internal
|
|
80
|
+
*/
|
|
81
|
+
async request(fn) {
|
|
82
|
+
try {
|
|
83
|
+
return await fn();
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
if (!this.#shouldRefresh(err))
|
|
87
|
+
throw err;
|
|
88
|
+
try {
|
|
89
|
+
await this.#refreshOnce();
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// Refresh failed (e.g. expired refresh token) — surface the original
|
|
93
|
+
// auth error rather than the refresh failure.
|
|
94
|
+
throw err;
|
|
95
|
+
}
|
|
96
|
+
return fn();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
#shouldRefresh(err) {
|
|
100
|
+
if (!this.#autoRefresh || !this.#refreshToken)
|
|
101
|
+
return false;
|
|
102
|
+
return err?.data?.code === "UNAUTHORIZED";
|
|
103
|
+
}
|
|
104
|
+
#refreshOnce() {
|
|
105
|
+
if (!this.#refreshInFlight) {
|
|
106
|
+
this.#refreshInFlight = this.refresh()
|
|
107
|
+
.then(() => undefined)
|
|
108
|
+
.finally(() => {
|
|
109
|
+
this.#refreshInFlight = null;
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
return this.#refreshInFlight;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Authenticate with email and password. On success the issued access (and
|
|
116
|
+
* refresh) token is stored on the client and used for subsequent calls.
|
|
117
|
+
*
|
|
118
|
+
* @returns the issued tokens.
|
|
119
|
+
*/
|
|
120
|
+
async login(input) {
|
|
121
|
+
const tokens = await this.trpc.auth.login.mutate(input);
|
|
122
|
+
this.#accessToken = tokens.accessToken;
|
|
123
|
+
this.#refreshToken = tokens.refreshToken;
|
|
124
|
+
return tokens;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Authenticate with a workspace API key (accessKeyId + accessKeySecret),
|
|
128
|
+
* intended for unattended, server-to-server integrations. On success the
|
|
129
|
+
* issued access (and refresh) token is stored on the client and used for
|
|
130
|
+
* subsequent calls.
|
|
131
|
+
*
|
|
132
|
+
* @returns the issued tokens.
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* ```ts
|
|
136
|
+
* await client.loginWithApiKey({
|
|
137
|
+
* accessKeyId: "WO6ueex0qan9ojhf820wgiae3qi5luy08y",
|
|
138
|
+
* accessKeySecret: process.env.QCOBRO_API_SECRET!
|
|
139
|
+
* });
|
|
140
|
+
* ```
|
|
141
|
+
*/
|
|
142
|
+
async loginWithApiKey(input) {
|
|
143
|
+
const tokens = await this.trpc.auth.exchangeApiKey.mutate(input);
|
|
144
|
+
this.#accessToken = tokens.accessToken;
|
|
145
|
+
this.#refreshToken = tokens.refreshToken;
|
|
146
|
+
return tokens;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Exchange a refresh token for a fresh access token. Uses the refresh token
|
|
150
|
+
* held by the client unless one is passed explicitly. The new access token
|
|
151
|
+
* replaces the current one.
|
|
152
|
+
*
|
|
153
|
+
* @returns the issued tokens.
|
|
154
|
+
*/
|
|
155
|
+
async refresh(refreshToken) {
|
|
156
|
+
const token = refreshToken ?? this.#refreshToken;
|
|
157
|
+
if (!token) {
|
|
158
|
+
throw new Error("No refresh token available; pass one or call login() first.");
|
|
159
|
+
}
|
|
160
|
+
const tokens = await this.trpc.auth.refresh.mutate({ refreshToken: token });
|
|
161
|
+
this.#accessToken = tokens.accessToken;
|
|
162
|
+
if (tokens.refreshToken)
|
|
163
|
+
this.#refreshToken = tokens.refreshToken;
|
|
164
|
+
return tokens;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Select the active workspace by its accessKeyId. Subsequent workspace-scoped
|
|
168
|
+
* calls act within this workspace.
|
|
169
|
+
*/
|
|
170
|
+
useWorkspace(accessKeyId) {
|
|
171
|
+
this.#workspace = accessKeyId;
|
|
172
|
+
return this;
|
|
173
|
+
}
|
|
174
|
+
/** The accessKeyId of the active workspace, or `undefined` if none is selected. */
|
|
175
|
+
get workspace() {
|
|
176
|
+
return this.#workspace;
|
|
177
|
+
}
|
|
178
|
+
/** Returns the tokens currently held by the client (in memory). */
|
|
179
|
+
getTokens() {
|
|
180
|
+
return { accessToken: this.#accessToken, refreshToken: this.#refreshToken };
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Replace the tokens held by the client. Useful to resume a session from
|
|
184
|
+
* tokens you persisted yourself.
|
|
185
|
+
*/
|
|
186
|
+
setTokens(tokens) {
|
|
187
|
+
this.#accessToken = tokens.accessToken;
|
|
188
|
+
this.#refreshToken = tokens.refreshToken;
|
|
189
|
+
return this;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAyB,MAAM,cAAc,CAAC;AAGtF,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAE/D,oEAAoE;AACpE,MAAM,gBAAgB,GAAG,aAAa,CAAC;AAEvC,wEAAwE;AACxE,MAAM,gBAAgB,GAAG,wBAAwB,CAAC;AAuClD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,OAAO,MAAM;IACjB;;;OAGG;IACM,IAAI,CAA8B;IAE3C,gFAAgF;IACvE,UAAU,CAAqB;IAExC,YAAY,CAAU;IACtB,aAAa,CAAU;IACvB,UAAU,CAAU;IACpB,YAAY,CAAU;IACtB,2EAA2E;IAC3E,gBAAgB,GAAyB,IAAI,CAAC;IAE9C,YAAY,UAAyB,EAAE;QACrC,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC;QACxC,IAAI,CAAC,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC;QAC1C,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC;QACpC,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC,WAAW,IAAI,IAAI,CAAC;QAEhD,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,QAAQ,IAAI,gBAAgB,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,OAAO,CAAC;QACjF,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAY;YACtC,KAAK,EAAE;gBACL,aAAa,CAAC;oBACZ,GAAG;oBACH,KAAK,EAAE,OAAO,CAAC,KAAK;oBACpB,6DAA6D;oBAC7D,kEAAkE;oBAClE,OAAO,EAAE,GAAG,EAAE;wBACZ,MAAM,OAAO,GAA2B,EAAE,CAAC;wBAC3C,IAAI,IAAI,CAAC,YAAY;4BAAE,OAAO,CAAC,aAAa,GAAG,UAAU,IAAI,CAAC,YAAY,EAAE,CAAC;wBAC7E,IAAI,IAAI,CAAC,UAAU;4BAAE,OAAO,CAAC,gBAAgB,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC;wBACjE,OAAO,OAAO,CAAC;oBACjB,CAAC;iBACF,CAAC;aACH;SACF,CAAC,CAAC;QAEH,IAAI,CAAC,UAAU,GAAG,IAAI,kBAAkB,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC;IAChF,CAAC;IAED;;;;;;;;;;;OAWG;IACH,KAAK,CAAC,OAAO,CAAI,EAAoB;QACnC,IAAI,CAAC;YACH,OAAO,MAAM,EAAE,EAAE,CAAC;QACpB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC;gBAAE,MAAM,GAAG,CAAC;YACzC,IAAI,CAAC;gBACH,MAAM,IAAI,CAAC,YAAY,EAAE,CAAC;YAC5B,CAAC;YAAC,MAAM,CAAC;gBACP,qEAAqE;gBACrE,8CAA8C;gBAC9C,MAAM,GAAG,CAAC;YACZ,CAAC;YACD,OAAO,EAAE,EAAE,CAAC;QACd,CAAC;IACH,CAAC;IAED,cAAc,CAAC,GAAY;QACzB,IAAI,CAAC,IAAI,CAAC,YAAY,IAAI,CAAC,IAAI,CAAC,aAAa;YAAE,OAAO,KAAK,CAAC;QAC5D,OAAQ,GAA2C,EAAE,IAAI,EAAE,IAAI,KAAK,cAAc,CAAC;IACrF,CAAC;IAED,YAAY;QACV,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAC3B,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC,OAAO,EAAE;iBACnC,IAAI,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC;iBACrB,OAAO,CAAC,GAAG,EAAE;gBACZ,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC;YAC/B,CAAC,CAAC,CAAC;QACP,CAAC;QACD,OAAO,IAAI,CAAC,gBAAgB,CAAC;IAC/B,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,KAAK,CAAC,KAAiB;QAC3B,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACxD,IAAI,CAAC,YAAY,GAAG,MAAM,CAAC,WAAW,CAAC;QACvC,IAAI,CAAC,aAAa,GAAG,MAAM,CAAC,YAAY,CAAC;QACzC,OAAO,MAAM,CAAC;IAChB,CAAC;IAED;;;;;;;;;;;;;;;OAeG;IACH,KAAK,CAAC,eAAe,CAAC,KAAuB;QAC3C,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACjE,IAAI,CAAC,YAAY,GAAG,MAAM,CAAC,WAAW,CAAC;QACvC,IAAI,CAAC,aAAa,GAAG,MAAM,CAAC,YAAY,CAAC;QACzC,OAAO,MAAM,CAAC;IAChB,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,OAAO,CAAC,YAAqB;QACjC,MAAM,KAAK,GAAG,YAAY,IAAI,IAAI,CAAC,aAAa,CAAC;QACjD,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,IAAI,KAAK,CAAC,6DAA6D,CAAC,CAAC;QACjF,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC,CAAC;QAC5E,IAAI,CAAC,YAAY,GAAG,MAAM,CAAC,WAAW,CAAC;QACvC,IAAI,MAAM,CAAC,YAAY;YAAE,IAAI,CAAC,aAAa,GAAG,MAAM,CAAC,YAAY,CAAC;QAClE,OAAO,MAAM,CAAC;IAChB,CAAC;IAED;;;OAGG;IACH,YAAY,CAAC,WAAmB;QAC9B,IAAI,CAAC,UAAU,GAAG,WAAW,CAAC;QAC9B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,mFAAmF;IACnF,IAAI,SAAS;QACX,OAAO,IAAI,CAAC,UAAU,CAAC;IACzB,CAAC;IAED,mEAAmE;IACnE,SAAS;QACP,OAAO,EAAE,WAAW,EAAE,IAAI,CAAC,YAAY,EAAE,YAAY,EAAE,IAAI,CAAC,aAAa,EAAE,CAAC;IAC9E,CAAC;IAED;;;OAGG;IACH,SAAS,CAAC,MAAc;QACtB,IAAI,CAAC,YAAY,GAAG,MAAM,CAAC,WAAW,CAAC;QACvC,IAAI,CAAC,aAAa,GAAG,MAAM,CAAC,YAAY,CAAC;QACzC,OAAO,IAAI,CAAC;IACd,CAAC;CACF"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@qcobro/sdk` — a developer-friendly TypeScript SDK for the QCobro API.
|
|
3
|
+
*
|
|
4
|
+
* Construct a {@link Client}, authenticate, select a workspace, and call typed
|
|
5
|
+
* resource methods (e.g. {@link Client.portfolios}).
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/
|
|
9
|
+
export { Client } from "./client.js";
|
|
10
|
+
export type { ClientOptions, Tokens } from "./client.js";
|
|
11
|
+
export { PortfoliosResource } from "./resources/portfolios.js";
|
|
12
|
+
export { ValidationError, type FieldError } from "@qcobro/common";
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,YAAY,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAI/D,OAAO,EAAE,eAAe,EAAE,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@qcobro/sdk` — a developer-friendly TypeScript SDK for the QCobro API.
|
|
3
|
+
*
|
|
4
|
+
* Construct a {@link Client}, authenticate, select a workspace, and call typed
|
|
5
|
+
* resource methods (e.g. {@link Client.portfolios}).
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/
|
|
9
|
+
export { Client } from "./client.js";
|
|
10
|
+
export { PortfoliosResource } from "./resources/portfolios.js";
|
|
11
|
+
// Re-export the shared structured error so callers can detect client-side
|
|
12
|
+
// validation failures without depending on `@qcobro/common` directly.
|
|
13
|
+
export { ValidationError } from "@qcobro/common";
|
|
14
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAE/D,0EAA0E;AAC1E,sEAAsE;AACtE,OAAO,EAAE,eAAe,EAAmB,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import type { CreateTRPCClient } from "@trpc/client";
|
|
2
|
+
import type { AppRouter } from "@qcobro/apiserver";
|
|
3
|
+
type RouterClient = CreateTRPCClient<AppRouter>;
|
|
4
|
+
type Portfolios = RouterClient["portfolios"];
|
|
5
|
+
/** Input accepted by each portfolios method, derived from the server router. */
|
|
6
|
+
type ListInput = Parameters<Portfolios["list"]["query"]>[0];
|
|
7
|
+
type GetInput = Parameters<Portfolios["get"]["query"]>[0];
|
|
8
|
+
type CreateInput = Parameters<Portfolios["create"]["mutate"]>[0];
|
|
9
|
+
type UpdateInput = Parameters<Portfolios["update"]["mutate"]>[0];
|
|
10
|
+
type DeleteInput = Parameters<Portfolios["delete"]["mutate"]>[0];
|
|
11
|
+
type ListAccountsInput = Parameters<Portfolios["listAccounts"]["query"]>[0];
|
|
12
|
+
type SyncAccountsInput = Parameters<Portfolios["syncAccounts"]["mutate"]>[0];
|
|
13
|
+
/**
|
|
14
|
+
* Portfolio operations.
|
|
15
|
+
*
|
|
16
|
+
* All methods are workspace-scoped: the {@link Client} must be authenticated and
|
|
17
|
+
* have an active workspace selected. Inputs are validated client-side against the
|
|
18
|
+
* shared `@qcobro/common` schemas before any request is sent — invalid input
|
|
19
|
+
* throws a {@link ValidationError} and never reaches the network.
|
|
20
|
+
*
|
|
21
|
+
* Obtain an instance via `client.portfolios`; do not construct it directly.
|
|
22
|
+
*/
|
|
23
|
+
/** Runs a request, transparently refreshing + replaying once on `UNAUTHORIZED`. */
|
|
24
|
+
type RequestRunner = <T>(fn: () => Promise<T>) => Promise<T>;
|
|
25
|
+
export declare class PortfoliosResource {
|
|
26
|
+
#private;
|
|
27
|
+
/** @internal */
|
|
28
|
+
constructor(trpc: RouterClient, request: RequestRunner);
|
|
29
|
+
/** List the active workspace's portfolios. Pass `includeArchived` to include archived ones. */
|
|
30
|
+
list(input?: ListInput): Promise<{
|
|
31
|
+
id: string;
|
|
32
|
+
name: string;
|
|
33
|
+
workspaceRef: string;
|
|
34
|
+
archivedAt: string | null;
|
|
35
|
+
createdAt: string;
|
|
36
|
+
updatedAt: string;
|
|
37
|
+
accountCount: number;
|
|
38
|
+
recoveredAmount: number;
|
|
39
|
+
clientId: string;
|
|
40
|
+
totalOutstandingBalance: number;
|
|
41
|
+
}[]>;
|
|
42
|
+
/** Get a single portfolio by id within the active workspace. */
|
|
43
|
+
get(input: GetInput): Promise<{
|
|
44
|
+
id: string;
|
|
45
|
+
name: string;
|
|
46
|
+
workspaceRef: string;
|
|
47
|
+
archivedAt: string | null;
|
|
48
|
+
createdAt: string;
|
|
49
|
+
updatedAt: string;
|
|
50
|
+
accountCount: number;
|
|
51
|
+
recoveredAmount: number;
|
|
52
|
+
clientId: string;
|
|
53
|
+
totalOutstandingBalance: number;
|
|
54
|
+
}>;
|
|
55
|
+
/** Create a portfolio in the active workspace. */
|
|
56
|
+
create(input: CreateInput): Promise<{
|
|
57
|
+
id: string;
|
|
58
|
+
name: string;
|
|
59
|
+
workspaceRef: string;
|
|
60
|
+
archivedAt: string | null;
|
|
61
|
+
createdAt: string;
|
|
62
|
+
updatedAt: string;
|
|
63
|
+
accountCount: number;
|
|
64
|
+
recoveredAmount: number;
|
|
65
|
+
clientId: string;
|
|
66
|
+
totalOutstandingBalance: number;
|
|
67
|
+
}>;
|
|
68
|
+
/** Update a portfolio. Set `archived: true` to archive it, `false` to restore it. */
|
|
69
|
+
update(input: UpdateInput): Promise<{
|
|
70
|
+
id: string;
|
|
71
|
+
name: string;
|
|
72
|
+
workspaceRef: string;
|
|
73
|
+
archivedAt: string | null;
|
|
74
|
+
createdAt: string;
|
|
75
|
+
updatedAt: string;
|
|
76
|
+
accountCount: number;
|
|
77
|
+
recoveredAmount: number;
|
|
78
|
+
clientId: string;
|
|
79
|
+
totalOutstandingBalance: number;
|
|
80
|
+
}>;
|
|
81
|
+
/** Delete a portfolio in the active workspace. */
|
|
82
|
+
delete(input: DeleteInput): Promise<{
|
|
83
|
+
id: string;
|
|
84
|
+
name: string;
|
|
85
|
+
workspaceRef: string;
|
|
86
|
+
archivedAt: string | null;
|
|
87
|
+
createdAt: string;
|
|
88
|
+
updatedAt: string;
|
|
89
|
+
accountCount: number;
|
|
90
|
+
recoveredAmount: number;
|
|
91
|
+
clientId: string;
|
|
92
|
+
totalOutstandingBalance: number;
|
|
93
|
+
}>;
|
|
94
|
+
/** List a page of a portfolio's accounts, with the total count. */
|
|
95
|
+
listAccounts(input: ListAccountsInput): Promise<{
|
|
96
|
+
items: {
|
|
97
|
+
id: string;
|
|
98
|
+
portfolioId: string;
|
|
99
|
+
archivedAt: string | null;
|
|
100
|
+
createdAt: string;
|
|
101
|
+
updatedAt: string;
|
|
102
|
+
outstandingBalance: number;
|
|
103
|
+
email: string | null;
|
|
104
|
+
phone: string | null;
|
|
105
|
+
externalId: string;
|
|
106
|
+
fullName: string;
|
|
107
|
+
preferredLanguage: string | null;
|
|
108
|
+
bestTimeToCall: string | null;
|
|
109
|
+
customerSegment: string | null;
|
|
110
|
+
principalAmount: number;
|
|
111
|
+
termsAmount: number;
|
|
112
|
+
termsFrequency: string | null;
|
|
113
|
+
termsLength: number;
|
|
114
|
+
daysPastDue: number;
|
|
115
|
+
missedInstallments: number;
|
|
116
|
+
lastPaymentDate: string | null;
|
|
117
|
+
lastPaymentAmount: number | null;
|
|
118
|
+
negotiationOptions: string | null;
|
|
119
|
+
lastContactedAt: string | null;
|
|
120
|
+
suppressUntil: string | null;
|
|
121
|
+
intentStatus: import("@prisma/client").$Enums.IntentStatus | null;
|
|
122
|
+
totalAttempts: number;
|
|
123
|
+
}[];
|
|
124
|
+
total: number;
|
|
125
|
+
}>;
|
|
126
|
+
/**
|
|
127
|
+
* Synchronize a batch of account rows into a portfolio.
|
|
128
|
+
*
|
|
129
|
+
* `mode` controls the merge strategy: `APPEND_ONLY` adds new rows,
|
|
130
|
+
* `UPDATE_EXISTING` updates rows that already exist, `REPLACE` replaces the set.
|
|
131
|
+
*/
|
|
132
|
+
syncAccounts(input: SyncAccountsInput): Promise<{
|
|
133
|
+
created: number;
|
|
134
|
+
updated: number;
|
|
135
|
+
archived: number;
|
|
136
|
+
total: number;
|
|
137
|
+
}>;
|
|
138
|
+
}
|
|
139
|
+
export {};
|
|
140
|
+
//# sourceMappingURL=portfolios.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"portfolios.d.ts","sourceRoot":"","sources":["../../src/resources/portfolios.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAWnD,KAAK,YAAY,GAAG,gBAAgB,CAAC,SAAS,CAAC,CAAC;AAChD,KAAK,UAAU,GAAG,YAAY,CAAC,YAAY,CAAC,CAAC;AAE7C,gFAAgF;AAChF,KAAK,SAAS,GAAG,UAAU,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC5D,KAAK,QAAQ,GAAG,UAAU,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACjE,KAAK,WAAW,GAAG,UAAU,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACjE,KAAK,WAAW,GAAG,UAAU,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACjE,KAAK,iBAAiB,GAAG,UAAU,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC5E,KAAK,iBAAiB,GAAG,UAAU,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAS7E;;;;;;;;;GASG;AACH,mFAAmF;AACnF,KAAK,aAAa,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;AAE7D,qBAAa,kBAAkB;;IAI7B,gBAAgB;gBACJ,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,aAAa;IAKtD,+FAA+F;IACzF,IAAI,CAAC,KAAK,CAAC,EAAE,SAAS;;;;;;;;;;;;IAK5B,gEAAgE;IAC1D,GAAG,CAAC,KAAK,EAAE,QAAQ;;;;;;;;;;;;IAKzB,kDAAkD;IAC5C,MAAM,CAAC,KAAK,EAAE,WAAW;;;;;;;;;;;;IAK/B,qFAAqF;IAC/E,MAAM,CAAC,KAAK,EAAE,WAAW;;;;;;;;;;;;IAK/B,kDAAkD;IAC5C,MAAM,CAAC,KAAK,EAAE,WAAW;;;;;;;;;;;;IAK/B,mEAAmE;IAC7D,YAAY,CAAC,KAAK,EAAE,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAK3C;;;;;OAKG;IACG,YAAY,CAAC,KAAK,EAAE,iBAAiB;;;;;;CAI5C"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { createPortfolioSchema, updatePortfolioSchema, deletePortfolioSchema, syncAccountsInputSchema, ValidationError } from "@qcobro/common";
|
|
2
|
+
import { listPortfoliosSchema, getPortfolioSchema, listAccountsSchema } from "../schemas.js";
|
|
3
|
+
/** Validate `input` against `schema`, throwing a structured {@link ValidationError} on failure. */
|
|
4
|
+
function parse(schema, input) {
|
|
5
|
+
const result = schema.safeParse(input);
|
|
6
|
+
if (!result.success)
|
|
7
|
+
throw new ValidationError(result.error);
|
|
8
|
+
return result.data;
|
|
9
|
+
}
|
|
10
|
+
export class PortfoliosResource {
|
|
11
|
+
#trpc;
|
|
12
|
+
#request;
|
|
13
|
+
/** @internal */
|
|
14
|
+
constructor(trpc, request) {
|
|
15
|
+
this.#trpc = trpc;
|
|
16
|
+
this.#request = request;
|
|
17
|
+
}
|
|
18
|
+
/** List the active workspace's portfolios. Pass `includeArchived` to include archived ones. */
|
|
19
|
+
async list(input) {
|
|
20
|
+
const parsed = parse(listPortfoliosSchema, input);
|
|
21
|
+
return this.#request(() => this.#trpc.portfolios.list.query(parsed));
|
|
22
|
+
}
|
|
23
|
+
/** Get a single portfolio by id within the active workspace. */
|
|
24
|
+
async get(input) {
|
|
25
|
+
const parsed = parse(getPortfolioSchema, input);
|
|
26
|
+
return this.#request(() => this.#trpc.portfolios.get.query(parsed));
|
|
27
|
+
}
|
|
28
|
+
/** Create a portfolio in the active workspace. */
|
|
29
|
+
async create(input) {
|
|
30
|
+
const parsed = parse(createPortfolioSchema, input);
|
|
31
|
+
return this.#request(() => this.#trpc.portfolios.create.mutate(parsed));
|
|
32
|
+
}
|
|
33
|
+
/** Update a portfolio. Set `archived: true` to archive it, `false` to restore it. */
|
|
34
|
+
async update(input) {
|
|
35
|
+
const parsed = parse(updatePortfolioSchema, input);
|
|
36
|
+
return this.#request(() => this.#trpc.portfolios.update.mutate(parsed));
|
|
37
|
+
}
|
|
38
|
+
/** Delete a portfolio in the active workspace. */
|
|
39
|
+
async delete(input) {
|
|
40
|
+
const parsed = parse(deletePortfolioSchema, input);
|
|
41
|
+
return this.#request(() => this.#trpc.portfolios.delete.mutate(parsed));
|
|
42
|
+
}
|
|
43
|
+
/** List a page of a portfolio's accounts, with the total count. */
|
|
44
|
+
async listAccounts(input) {
|
|
45
|
+
const parsed = parse(listAccountsSchema, input);
|
|
46
|
+
return this.#request(() => this.#trpc.portfolios.listAccounts.query(parsed));
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Synchronize a batch of account rows into a portfolio.
|
|
50
|
+
*
|
|
51
|
+
* `mode` controls the merge strategy: `APPEND_ONLY` adds new rows,
|
|
52
|
+
* `UPDATE_EXISTING` updates rows that already exist, `REPLACE` replaces the set.
|
|
53
|
+
*/
|
|
54
|
+
async syncAccounts(input) {
|
|
55
|
+
const parsed = parse(syncAccountsInputSchema, input);
|
|
56
|
+
return this.#request(() => this.#trpc.portfolios.syncAccounts.mutate(parsed));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=portfolios.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"portfolios.js","sourceRoot":"","sources":["../../src/resources/portfolios.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Local schemas for portfolio operations whose inputs the apiserver defines
|
|
4
|
+
* inline (rather than in `@qcobro/common`). Kept minimal and matched to the
|
|
5
|
+
* server's inline `z.object(...)` shapes so the SDK can validate client-side
|
|
6
|
+
* before a request is sent. Contract-bearing inputs (create/update/delete/
|
|
7
|
+
* syncAccounts) reuse the shared `@qcobro/common` schemas directly.
|
|
8
|
+
*/
|
|
9
|
+
/** Input for `portfolios.list`. */
|
|
10
|
+
export declare const listPortfoliosSchema: z.ZodOptional<z.ZodObject<{
|
|
11
|
+
includeArchived: z.ZodOptional<z.ZodBoolean>;
|
|
12
|
+
}, z.core.$strip>>;
|
|
13
|
+
export type ListPortfoliosInput = z.infer<typeof listPortfoliosSchema>;
|
|
14
|
+
/** Input for `portfolios.get`. */
|
|
15
|
+
export declare const getPortfolioSchema: z.ZodObject<{
|
|
16
|
+
id: z.ZodString;
|
|
17
|
+
}, z.core.$strip>;
|
|
18
|
+
export type GetPortfolioInput = z.infer<typeof getPortfolioSchema>;
|
|
19
|
+
/** Input for `portfolios.listAccounts`. */
|
|
20
|
+
export declare const listAccountsSchema: z.ZodObject<{
|
|
21
|
+
portfolioId: z.ZodString;
|
|
22
|
+
limit: z.ZodOptional<z.ZodNumber>;
|
|
23
|
+
offset: z.ZodOptional<z.ZodNumber>;
|
|
24
|
+
}, z.core.$strip>;
|
|
25
|
+
export type ListAccountsInput = z.infer<typeof listAccountsSchema>;
|
|
26
|
+
//# sourceMappingURL=schemas.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../src/schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;GAMG;AAEH,mCAAmC;AACnC,eAAO,MAAM,oBAAoB;;kBAIpB,CAAC;AACd,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAEvE,kCAAkC;AAClC,eAAO,MAAM,kBAAkB;;iBAE7B,CAAC;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAEnE,2CAA2C;AAC3C,eAAO,MAAM,kBAAkB;;;;iBAI7B,CAAC;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC"}
|
package/dist/schemas.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Local schemas for portfolio operations whose inputs the apiserver defines
|
|
4
|
+
* inline (rather than in `@qcobro/common`). Kept minimal and matched to the
|
|
5
|
+
* server's inline `z.object(...)` shapes so the SDK can validate client-side
|
|
6
|
+
* before a request is sent. Contract-bearing inputs (create/update/delete/
|
|
7
|
+
* syncAccounts) reuse the shared `@qcobro/common` schemas directly.
|
|
8
|
+
*/
|
|
9
|
+
/** Input for `portfolios.list`. */
|
|
10
|
+
export const listPortfoliosSchema = z
|
|
11
|
+
.object({
|
|
12
|
+
includeArchived: z.boolean().optional()
|
|
13
|
+
})
|
|
14
|
+
.optional();
|
|
15
|
+
/** Input for `portfolios.get`. */
|
|
16
|
+
export const getPortfolioSchema = z.object({
|
|
17
|
+
id: z.string().min(1)
|
|
18
|
+
});
|
|
19
|
+
/** Input for `portfolios.listAccounts`. */
|
|
20
|
+
export const listAccountsSchema = z.object({
|
|
21
|
+
portfolioId: z.string().min(1),
|
|
22
|
+
limit: z.number().int().min(1).max(200).optional(),
|
|
23
|
+
offset: z.number().int().min(0).optional()
|
|
24
|
+
});
|
|
25
|
+
//# sourceMappingURL=schemas.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schemas.js","sourceRoot":"","sources":["../src/schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;GAMG;AAEH,mCAAmC;AACnC,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC;KAClC,MAAM,CAAC;IACN,eAAe,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;CACxC,CAAC;KACD,QAAQ,EAAE,CAAC;AAGd,kCAAkC;AAClC,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IACzC,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;CACtB,CAAC,CAAC;AAGH,2CAA2C;AAC3C,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IACzC,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC9B,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE;IAClD,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;CAC3C,CAAC,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@qcobro/sdk",
|
|
3
|
+
"version": "1.11.0",
|
|
4
|
+
"description": "Developer-friendly TypeScript SDK for the QCobro API.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"publishConfig": {
|
|
13
|
+
"access": "public"
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc -b --force",
|
|
17
|
+
"clean": "rm -rf dist docs *.tsbuildinfo",
|
|
18
|
+
"typecheck": "tsc --noEmit",
|
|
19
|
+
"test": "node --import tsx --test \"src/**/*.test.ts\"",
|
|
20
|
+
"docs": "typedoc"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@qcobro/common": "^1.11.0",
|
|
24
|
+
"@trpc/client": "^11.0.0",
|
|
25
|
+
"zod": "^4.0.0"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@qcobro/apiserver": "^1.11.0",
|
|
29
|
+
"@trpc/server": "^11.0.0",
|
|
30
|
+
"@types/express": "^5.0.0",
|
|
31
|
+
"express": "^5.0.0",
|
|
32
|
+
"tsx": "^4.0.0",
|
|
33
|
+
"typedoc": "^0.28.0",
|
|
34
|
+
"typedoc-plugin-markdown": "^4.0.0",
|
|
35
|
+
"typescript": "^5.9.0"
|
|
36
|
+
}
|
|
37
|
+
}
|