@velobaseai/billing 0.1.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/LICENSE +21 -0
- package/README.md +282 -0
- package/dist/index.d.mts +140 -0
- package/dist/index.d.ts +140 -0
- package/dist/index.js +268 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +235 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +48 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Velobase
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
# @velobaseai/billing
|
|
2
|
+
|
|
3
|
+
Official Velobase Billing SDK for JavaScript and TypeScript.
|
|
4
|
+
|
|
5
|
+
- Zero runtime dependencies — uses native `fetch`
|
|
6
|
+
- Works with Node.js 18+, Deno, Bun, and Cloudflare Workers
|
|
7
|
+
- ESM and CommonJS dual build with full TypeScript declarations
|
|
8
|
+
- Automatic retries with exponential backoff
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install @velobaseai/billing
|
|
14
|
+
# or
|
|
15
|
+
pnpm add @velobaseai/billing
|
|
16
|
+
# or
|
|
17
|
+
yarn add @velobaseai/billing
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import Velobase from '@velobaseai/billing';
|
|
24
|
+
|
|
25
|
+
const vb = new Velobase({ apiKey: 'vb_live_xxx' });
|
|
26
|
+
|
|
27
|
+
// 1. Deposit credits to a customer (creates the customer if new)
|
|
28
|
+
const deposit = await vb.customers.deposit({
|
|
29
|
+
customerId: 'user_123',
|
|
30
|
+
amount: 1000,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// 2. Check balance
|
|
34
|
+
const customer = await vb.customers.get('user_123');
|
|
35
|
+
console.log(customer.balance.available); // 1000
|
|
36
|
+
|
|
37
|
+
// 3. Freeze credits before doing work
|
|
38
|
+
const freeze = await vb.billing.freeze({
|
|
39
|
+
customerId: 'user_123',
|
|
40
|
+
amount: 50,
|
|
41
|
+
businessId: 'job_abc',
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// 4a. Job succeeded — consume (supports partial)
|
|
45
|
+
const consume = await vb.billing.consume({
|
|
46
|
+
businessId: 'job_abc',
|
|
47
|
+
actualAmount: 32, // only charge 32, return 18
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// 4b. Or if the job failed — unfreeze to return all
|
|
51
|
+
const unfreeze = await vb.billing.unfreeze({
|
|
52
|
+
businessId: 'job_abc',
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## How It Works
|
|
57
|
+
|
|
58
|
+
Velobase Billing uses a **freeze-then-consume** pattern to safely manage credits:
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
deposit → freeze → consume (normal flow)
|
|
62
|
+
→ unfreeze (failure/cancellation)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
1. **Deposit** — Add credits to a customer's account. Creates the customer automatically on first deposit.
|
|
66
|
+
2. **Freeze** — Pre-authorize an amount before performing work. The frozen credits are deducted from `available` but not yet `used`. Each freeze is identified by a unique `businessId` you provide.
|
|
67
|
+
3. **Consume** — After the work is done, settle the frozen amount. You can pass `actualAmount` to charge less than what was frozen; the difference is automatically returned.
|
|
68
|
+
4. **Unfreeze** — If the work fails or is cancelled, release the full frozen amount back to the customer.
|
|
69
|
+
|
|
70
|
+
All write operations are **idempotent** — repeating the same `businessId` (freeze/consume/unfreeze) or `idempotencyKey` (deposit) returns the original result without double-charging.
|
|
71
|
+
|
|
72
|
+
## Configuration
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
const vb = new Velobase({
|
|
76
|
+
apiKey: 'vb_live_xxx', // Required. Your Velobase API key.
|
|
77
|
+
baseUrl: 'https://api.velobase.io', // Optional. Override the API endpoint.
|
|
78
|
+
timeout: 30000, // Optional. Request timeout in ms (default: 30s).
|
|
79
|
+
maxRetries: 2, // Optional. Retry count on 5xx/network errors (default: 2).
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Usage Examples
|
|
84
|
+
|
|
85
|
+
### Deposit with idempotency
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
// Safe to retry — the second call returns the same result without double-charging
|
|
89
|
+
const result = await vb.customers.deposit({
|
|
90
|
+
customerId: 'user_123',
|
|
91
|
+
amount: 500,
|
|
92
|
+
idempotencyKey: 'order_abc_payment',
|
|
93
|
+
description: 'Purchase of 500 credits',
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
console.log(result.addedAmount); // 500
|
|
97
|
+
console.log(result.isIdempotentReplay); // false on first call, true on retries
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Deposit with customer metadata
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
const result = await vb.customers.deposit({
|
|
104
|
+
customerId: 'user_123',
|
|
105
|
+
amount: 1000,
|
|
106
|
+
name: 'Alice',
|
|
107
|
+
email: 'alice@example.com',
|
|
108
|
+
metadata: { plan: 'pro', source: 'stripe' },
|
|
109
|
+
});
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Full billing flow
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
const CUSTOMER = 'user_123';
|
|
116
|
+
const JOB_ID = 'video_gen_001';
|
|
117
|
+
|
|
118
|
+
// Check balance before starting
|
|
119
|
+
const before = await vb.customers.get(CUSTOMER);
|
|
120
|
+
console.log('Available:', before.balance.available);
|
|
121
|
+
|
|
122
|
+
// Freeze the estimated cost
|
|
123
|
+
await vb.billing.freeze({
|
|
124
|
+
customerId: CUSTOMER,
|
|
125
|
+
amount: 100,
|
|
126
|
+
businessId: JOB_ID,
|
|
127
|
+
businessType: 'video_generation',
|
|
128
|
+
description: '1080p video, ~60s',
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// ... do the work ...
|
|
132
|
+
|
|
133
|
+
// Settle with the actual cost (partial consumption)
|
|
134
|
+
const result = await vb.billing.consume({
|
|
135
|
+
businessId: JOB_ID,
|
|
136
|
+
actualAmount: 73,
|
|
137
|
+
});
|
|
138
|
+
console.log('Charged:', result.consumedAmount); // 73
|
|
139
|
+
console.log('Returned:', result.returnedAmount); // 27
|
|
140
|
+
|
|
141
|
+
// Verify final balance
|
|
142
|
+
const after = await vb.customers.get(CUSTOMER);
|
|
143
|
+
console.log('Available:', after.balance.available);
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Customer balance structure
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
const customer = await vb.customers.get('user_123');
|
|
150
|
+
|
|
151
|
+
// Aggregate balance across all accounts
|
|
152
|
+
customer.balance.total; // total deposited
|
|
153
|
+
customer.balance.used; // total consumed
|
|
154
|
+
customer.balance.frozen; // currently frozen (pending)
|
|
155
|
+
customer.balance.available; // total - used - frozen
|
|
156
|
+
|
|
157
|
+
// Individual accounts (e.g., different credit types/expiry)
|
|
158
|
+
for (const account of customer.accounts) {
|
|
159
|
+
console.log(account.accountType); // 'CREDIT'
|
|
160
|
+
console.log(account.subAccountType); // 'DEFAULT'
|
|
161
|
+
console.log(account.available);
|
|
162
|
+
console.log(account.expiresAt); // null or ISO date string
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## API Reference
|
|
167
|
+
|
|
168
|
+
### `vb.customers.deposit(params): Promise<DepositResponse>`
|
|
169
|
+
|
|
170
|
+
Deposit credits. Creates the customer if they don't exist.
|
|
171
|
+
|
|
172
|
+
| Parameter | Type | Required | Description |
|
|
173
|
+
|---|---|---|---|
|
|
174
|
+
| `customerId` | `string` | Yes | Your unique customer identifier |
|
|
175
|
+
| `amount` | `number` | Yes | Amount to deposit (must be > 0) |
|
|
176
|
+
| `idempotencyKey` | `string` | No | Prevents duplicate deposits on retry |
|
|
177
|
+
| `name` | `string \| null` | No | Customer display name |
|
|
178
|
+
| `email` | `string \| null` | No | Customer email |
|
|
179
|
+
| `metadata` | `object` | No | Arbitrary key-value metadata |
|
|
180
|
+
| `description` | `string` | No | Description for the deposit |
|
|
181
|
+
|
|
182
|
+
**Returns:** `{ customerId, accountId, totalAmount, addedAmount, recordId, isIdempotentReplay }`
|
|
183
|
+
|
|
184
|
+
### `vb.customers.get(customerId): Promise<CustomerResponse>`
|
|
185
|
+
|
|
186
|
+
Retrieve a customer's balance and account details.
|
|
187
|
+
|
|
188
|
+
**Returns:** `{ id, name, email, metadata, balance, accounts, createdAt }`
|
|
189
|
+
|
|
190
|
+
### `vb.billing.freeze(params): Promise<FreezeResponse>`
|
|
191
|
+
|
|
192
|
+
Freeze credits before performing work.
|
|
193
|
+
|
|
194
|
+
| Parameter | Type | Required | Description |
|
|
195
|
+
|---|---|---|---|
|
|
196
|
+
| `customerId` | `string` | Yes | Customer identifier |
|
|
197
|
+
| `amount` | `number` | Yes | Amount to freeze (must be > 0) |
|
|
198
|
+
| `businessId` | `string` | Yes | Your unique ID for this operation (idempotency key) |
|
|
199
|
+
| `businessType` | `string` | No | Category label (e.g., `'video_generation'`) |
|
|
200
|
+
| `description` | `string` | No | Human-readable description |
|
|
201
|
+
|
|
202
|
+
**Returns:** `{ businessId, frozenAmount, freezeDetails, isIdempotentReplay }`
|
|
203
|
+
|
|
204
|
+
### `vb.billing.consume(params): Promise<ConsumeResponse>`
|
|
205
|
+
|
|
206
|
+
Settle a frozen amount. Supports partial consumption.
|
|
207
|
+
|
|
208
|
+
| Parameter | Type | Required | Description |
|
|
209
|
+
|---|---|---|---|
|
|
210
|
+
| `businessId` | `string` | Yes | The `businessId` from the freeze |
|
|
211
|
+
| `actualAmount` | `number` | No | Actual amount to charge. Defaults to full frozen amount. |
|
|
212
|
+
|
|
213
|
+
**Returns:** `{ businessId, consumedAmount, returnedAmount, consumeDetails, consumedAt, isIdempotentReplay }`
|
|
214
|
+
|
|
215
|
+
### `vb.billing.unfreeze(params): Promise<UnfreezeResponse>`
|
|
216
|
+
|
|
217
|
+
Release a frozen amount back to the customer.
|
|
218
|
+
|
|
219
|
+
| Parameter | Type | Required | Description |
|
|
220
|
+
|---|---|---|---|
|
|
221
|
+
| `businessId` | `string` | Yes | The `businessId` from the freeze |
|
|
222
|
+
|
|
223
|
+
**Returns:** `{ businessId, unfrozenAmount, unfreezeDetails, unfrozenAt, isIdempotentReplay }`
|
|
224
|
+
|
|
225
|
+
## Error Handling
|
|
226
|
+
|
|
227
|
+
All API errors throw typed exceptions that extend `VelobaseError`:
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
import {
|
|
231
|
+
VelobaseError,
|
|
232
|
+
VelobaseAuthenticationError,
|
|
233
|
+
VelobaseValidationError,
|
|
234
|
+
VelobaseNotFoundError,
|
|
235
|
+
} from '@velobaseai/billing';
|
|
236
|
+
|
|
237
|
+
try {
|
|
238
|
+
await vb.billing.freeze({
|
|
239
|
+
customerId: 'user_123',
|
|
240
|
+
amount: 999999,
|
|
241
|
+
businessId: 'job_xyz',
|
|
242
|
+
});
|
|
243
|
+
} catch (err) {
|
|
244
|
+
if (err instanceof VelobaseValidationError) {
|
|
245
|
+
// 400 — bad request or insufficient balance
|
|
246
|
+
console.error(err.message); // "insufficient balance"
|
|
247
|
+
} else if (err instanceof VelobaseAuthenticationError) {
|
|
248
|
+
// 401 — invalid or missing API key
|
|
249
|
+
} else if (err instanceof VelobaseNotFoundError) {
|
|
250
|
+
// 404 — customer not found
|
|
251
|
+
} else if (err instanceof VelobaseError) {
|
|
252
|
+
// catch-all for other API errors
|
|
253
|
+
console.error(err.status, err.type, err.message);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
| Error Class | HTTP Status | When |
|
|
259
|
+
|---|---|---|
|
|
260
|
+
| `VelobaseAuthenticationError` | 401 | Invalid or missing API key |
|
|
261
|
+
| `VelobaseValidationError` | 400 | Bad params, insufficient balance |
|
|
262
|
+
| `VelobaseNotFoundError` | 404 | Customer or resource not found |
|
|
263
|
+
| `VelobaseConflictError` | 409 | Conflicting operation |
|
|
264
|
+
| `VelobaseInternalError` | 500 | Server-side error (auto-retried) |
|
|
265
|
+
|
|
266
|
+
## Retries
|
|
267
|
+
|
|
268
|
+
The SDK automatically retries on 5xx errors and network failures with exponential backoff (500ms, 1s, 2s..., capped at 5s). Retries are safe because all Velobase write operations are idempotent.
|
|
269
|
+
|
|
270
|
+
4xx errors (validation, auth, not found) are never retried.
|
|
271
|
+
|
|
272
|
+
## CommonJS
|
|
273
|
+
|
|
274
|
+
```javascript
|
|
275
|
+
const { Velobase } = require('@velobaseai/billing');
|
|
276
|
+
|
|
277
|
+
const vb = new Velobase({ apiKey: 'vb_live_xxx' });
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## License
|
|
281
|
+
|
|
282
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
interface HttpClientOptions {
|
|
2
|
+
baseUrl: string;
|
|
3
|
+
apiKey: string;
|
|
4
|
+
timeout: number;
|
|
5
|
+
maxRetries: number;
|
|
6
|
+
}
|
|
7
|
+
declare class HttpClient {
|
|
8
|
+
private baseUrl;
|
|
9
|
+
private apiKey;
|
|
10
|
+
private timeout;
|
|
11
|
+
private maxRetries;
|
|
12
|
+
constructor(opts: HttpClientOptions);
|
|
13
|
+
request<T>(method: string, path: string, body?: unknown, headers?: Record<string, string>): Promise<T>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface FreezeParams {
|
|
17
|
+
customerId: string;
|
|
18
|
+
amount: number;
|
|
19
|
+
businessId: string;
|
|
20
|
+
businessType?: string;
|
|
21
|
+
description?: string;
|
|
22
|
+
}
|
|
23
|
+
interface FreezeResponse {
|
|
24
|
+
businessId: string;
|
|
25
|
+
frozenAmount: number;
|
|
26
|
+
freezeDetails: unknown[];
|
|
27
|
+
isIdempotentReplay: boolean;
|
|
28
|
+
}
|
|
29
|
+
interface ConsumeParams {
|
|
30
|
+
businessId: string;
|
|
31
|
+
actualAmount?: number;
|
|
32
|
+
}
|
|
33
|
+
interface ConsumeResponse {
|
|
34
|
+
businessId: string;
|
|
35
|
+
consumedAmount: number;
|
|
36
|
+
returnedAmount?: number;
|
|
37
|
+
consumeDetails: unknown[];
|
|
38
|
+
consumedAt: string;
|
|
39
|
+
isIdempotentReplay: boolean;
|
|
40
|
+
}
|
|
41
|
+
interface UnfreezeParams {
|
|
42
|
+
businessId: string;
|
|
43
|
+
}
|
|
44
|
+
interface UnfreezeResponse {
|
|
45
|
+
businessId: string;
|
|
46
|
+
unfrozenAmount: number;
|
|
47
|
+
unfreezeDetails: unknown[];
|
|
48
|
+
unfrozenAt: string;
|
|
49
|
+
isIdempotentReplay: boolean;
|
|
50
|
+
}
|
|
51
|
+
interface DepositParams {
|
|
52
|
+
customerId: string;
|
|
53
|
+
amount: number;
|
|
54
|
+
idempotencyKey?: string;
|
|
55
|
+
name?: string | null;
|
|
56
|
+
email?: string | null;
|
|
57
|
+
metadata?: Record<string, unknown>;
|
|
58
|
+
description?: string;
|
|
59
|
+
}
|
|
60
|
+
interface DepositResponse {
|
|
61
|
+
customerId: string;
|
|
62
|
+
accountId: string;
|
|
63
|
+
totalAmount: number;
|
|
64
|
+
addedAmount: number;
|
|
65
|
+
recordId: string;
|
|
66
|
+
isIdempotentReplay: boolean;
|
|
67
|
+
}
|
|
68
|
+
interface CustomerBalance {
|
|
69
|
+
total: number;
|
|
70
|
+
used: number;
|
|
71
|
+
frozen: number;
|
|
72
|
+
available: number;
|
|
73
|
+
}
|
|
74
|
+
interface CustomerAccount {
|
|
75
|
+
accountType: string;
|
|
76
|
+
subAccountType: string;
|
|
77
|
+
total: number;
|
|
78
|
+
used: number;
|
|
79
|
+
frozen: number;
|
|
80
|
+
available: number;
|
|
81
|
+
startsAt: string | null;
|
|
82
|
+
expiresAt: string | null;
|
|
83
|
+
}
|
|
84
|
+
interface CustomerResponse {
|
|
85
|
+
id: string;
|
|
86
|
+
name: string | null;
|
|
87
|
+
email: string | null;
|
|
88
|
+
metadata: Record<string, unknown> | null;
|
|
89
|
+
balance: CustomerBalance;
|
|
90
|
+
accounts: CustomerAccount[];
|
|
91
|
+
createdAt: string;
|
|
92
|
+
}
|
|
93
|
+
interface VelobaseOptions {
|
|
94
|
+
apiKey: string;
|
|
95
|
+
baseUrl?: string;
|
|
96
|
+
timeout?: number;
|
|
97
|
+
maxRetries?: number;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
declare class BillingResource {
|
|
101
|
+
private http;
|
|
102
|
+
constructor(http: HttpClient);
|
|
103
|
+
freeze(params: FreezeParams): Promise<FreezeResponse>;
|
|
104
|
+
consume(params: ConsumeParams): Promise<ConsumeResponse>;
|
|
105
|
+
unfreeze(params: UnfreezeParams): Promise<UnfreezeResponse>;
|
|
106
|
+
}
|
|
107
|
+
declare class CustomersResource {
|
|
108
|
+
private http;
|
|
109
|
+
constructor(http: HttpClient);
|
|
110
|
+
deposit(params: DepositParams): Promise<DepositResponse>;
|
|
111
|
+
get(customerId: string): Promise<CustomerResponse>;
|
|
112
|
+
}
|
|
113
|
+
declare class Velobase {
|
|
114
|
+
readonly billing: BillingResource;
|
|
115
|
+
readonly customers: CustomersResource;
|
|
116
|
+
constructor(opts: VelobaseOptions);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
declare class VelobaseError extends Error {
|
|
120
|
+
status: number;
|
|
121
|
+
type: string;
|
|
122
|
+
constructor(message: string, status: number, type: string);
|
|
123
|
+
}
|
|
124
|
+
declare class VelobaseAuthenticationError extends VelobaseError {
|
|
125
|
+
constructor(message: string);
|
|
126
|
+
}
|
|
127
|
+
declare class VelobaseValidationError extends VelobaseError {
|
|
128
|
+
constructor(message: string);
|
|
129
|
+
}
|
|
130
|
+
declare class VelobaseNotFoundError extends VelobaseError {
|
|
131
|
+
constructor(message: string);
|
|
132
|
+
}
|
|
133
|
+
declare class VelobaseConflictError extends VelobaseError {
|
|
134
|
+
constructor(message: string);
|
|
135
|
+
}
|
|
136
|
+
declare class VelobaseInternalError extends VelobaseError {
|
|
137
|
+
constructor(message: string);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export { type ConsumeParams, type ConsumeResponse, type CustomerAccount, type CustomerBalance, type CustomerResponse, type DepositParams, type DepositResponse, type FreezeParams, type FreezeResponse, type UnfreezeParams, type UnfreezeResponse, Velobase, VelobaseAuthenticationError, VelobaseConflictError, VelobaseError, VelobaseInternalError, VelobaseNotFoundError, type VelobaseOptions, VelobaseValidationError, Velobase as default };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
interface HttpClientOptions {
|
|
2
|
+
baseUrl: string;
|
|
3
|
+
apiKey: string;
|
|
4
|
+
timeout: number;
|
|
5
|
+
maxRetries: number;
|
|
6
|
+
}
|
|
7
|
+
declare class HttpClient {
|
|
8
|
+
private baseUrl;
|
|
9
|
+
private apiKey;
|
|
10
|
+
private timeout;
|
|
11
|
+
private maxRetries;
|
|
12
|
+
constructor(opts: HttpClientOptions);
|
|
13
|
+
request<T>(method: string, path: string, body?: unknown, headers?: Record<string, string>): Promise<T>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface FreezeParams {
|
|
17
|
+
customerId: string;
|
|
18
|
+
amount: number;
|
|
19
|
+
businessId: string;
|
|
20
|
+
businessType?: string;
|
|
21
|
+
description?: string;
|
|
22
|
+
}
|
|
23
|
+
interface FreezeResponse {
|
|
24
|
+
businessId: string;
|
|
25
|
+
frozenAmount: number;
|
|
26
|
+
freezeDetails: unknown[];
|
|
27
|
+
isIdempotentReplay: boolean;
|
|
28
|
+
}
|
|
29
|
+
interface ConsumeParams {
|
|
30
|
+
businessId: string;
|
|
31
|
+
actualAmount?: number;
|
|
32
|
+
}
|
|
33
|
+
interface ConsumeResponse {
|
|
34
|
+
businessId: string;
|
|
35
|
+
consumedAmount: number;
|
|
36
|
+
returnedAmount?: number;
|
|
37
|
+
consumeDetails: unknown[];
|
|
38
|
+
consumedAt: string;
|
|
39
|
+
isIdempotentReplay: boolean;
|
|
40
|
+
}
|
|
41
|
+
interface UnfreezeParams {
|
|
42
|
+
businessId: string;
|
|
43
|
+
}
|
|
44
|
+
interface UnfreezeResponse {
|
|
45
|
+
businessId: string;
|
|
46
|
+
unfrozenAmount: number;
|
|
47
|
+
unfreezeDetails: unknown[];
|
|
48
|
+
unfrozenAt: string;
|
|
49
|
+
isIdempotentReplay: boolean;
|
|
50
|
+
}
|
|
51
|
+
interface DepositParams {
|
|
52
|
+
customerId: string;
|
|
53
|
+
amount: number;
|
|
54
|
+
idempotencyKey?: string;
|
|
55
|
+
name?: string | null;
|
|
56
|
+
email?: string | null;
|
|
57
|
+
metadata?: Record<string, unknown>;
|
|
58
|
+
description?: string;
|
|
59
|
+
}
|
|
60
|
+
interface DepositResponse {
|
|
61
|
+
customerId: string;
|
|
62
|
+
accountId: string;
|
|
63
|
+
totalAmount: number;
|
|
64
|
+
addedAmount: number;
|
|
65
|
+
recordId: string;
|
|
66
|
+
isIdempotentReplay: boolean;
|
|
67
|
+
}
|
|
68
|
+
interface CustomerBalance {
|
|
69
|
+
total: number;
|
|
70
|
+
used: number;
|
|
71
|
+
frozen: number;
|
|
72
|
+
available: number;
|
|
73
|
+
}
|
|
74
|
+
interface CustomerAccount {
|
|
75
|
+
accountType: string;
|
|
76
|
+
subAccountType: string;
|
|
77
|
+
total: number;
|
|
78
|
+
used: number;
|
|
79
|
+
frozen: number;
|
|
80
|
+
available: number;
|
|
81
|
+
startsAt: string | null;
|
|
82
|
+
expiresAt: string | null;
|
|
83
|
+
}
|
|
84
|
+
interface CustomerResponse {
|
|
85
|
+
id: string;
|
|
86
|
+
name: string | null;
|
|
87
|
+
email: string | null;
|
|
88
|
+
metadata: Record<string, unknown> | null;
|
|
89
|
+
balance: CustomerBalance;
|
|
90
|
+
accounts: CustomerAccount[];
|
|
91
|
+
createdAt: string;
|
|
92
|
+
}
|
|
93
|
+
interface VelobaseOptions {
|
|
94
|
+
apiKey: string;
|
|
95
|
+
baseUrl?: string;
|
|
96
|
+
timeout?: number;
|
|
97
|
+
maxRetries?: number;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
declare class BillingResource {
|
|
101
|
+
private http;
|
|
102
|
+
constructor(http: HttpClient);
|
|
103
|
+
freeze(params: FreezeParams): Promise<FreezeResponse>;
|
|
104
|
+
consume(params: ConsumeParams): Promise<ConsumeResponse>;
|
|
105
|
+
unfreeze(params: UnfreezeParams): Promise<UnfreezeResponse>;
|
|
106
|
+
}
|
|
107
|
+
declare class CustomersResource {
|
|
108
|
+
private http;
|
|
109
|
+
constructor(http: HttpClient);
|
|
110
|
+
deposit(params: DepositParams): Promise<DepositResponse>;
|
|
111
|
+
get(customerId: string): Promise<CustomerResponse>;
|
|
112
|
+
}
|
|
113
|
+
declare class Velobase {
|
|
114
|
+
readonly billing: BillingResource;
|
|
115
|
+
readonly customers: CustomersResource;
|
|
116
|
+
constructor(opts: VelobaseOptions);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
declare class VelobaseError extends Error {
|
|
120
|
+
status: number;
|
|
121
|
+
type: string;
|
|
122
|
+
constructor(message: string, status: number, type: string);
|
|
123
|
+
}
|
|
124
|
+
declare class VelobaseAuthenticationError extends VelobaseError {
|
|
125
|
+
constructor(message: string);
|
|
126
|
+
}
|
|
127
|
+
declare class VelobaseValidationError extends VelobaseError {
|
|
128
|
+
constructor(message: string);
|
|
129
|
+
}
|
|
130
|
+
declare class VelobaseNotFoundError extends VelobaseError {
|
|
131
|
+
constructor(message: string);
|
|
132
|
+
}
|
|
133
|
+
declare class VelobaseConflictError extends VelobaseError {
|
|
134
|
+
constructor(message: string);
|
|
135
|
+
}
|
|
136
|
+
declare class VelobaseInternalError extends VelobaseError {
|
|
137
|
+
constructor(message: string);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export { type ConsumeParams, type ConsumeResponse, type CustomerAccount, type CustomerBalance, type CustomerResponse, type DepositParams, type DepositResponse, type FreezeParams, type FreezeResponse, type UnfreezeParams, type UnfreezeResponse, Velobase, VelobaseAuthenticationError, VelobaseConflictError, VelobaseError, VelobaseInternalError, VelobaseNotFoundError, type VelobaseOptions, VelobaseValidationError, Velobase as default };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
Velobase: () => Velobase,
|
|
24
|
+
VelobaseAuthenticationError: () => VelobaseAuthenticationError,
|
|
25
|
+
VelobaseConflictError: () => VelobaseConflictError,
|
|
26
|
+
VelobaseError: () => VelobaseError,
|
|
27
|
+
VelobaseInternalError: () => VelobaseInternalError,
|
|
28
|
+
VelobaseNotFoundError: () => VelobaseNotFoundError,
|
|
29
|
+
VelobaseValidationError: () => VelobaseValidationError,
|
|
30
|
+
default: () => Velobase
|
|
31
|
+
});
|
|
32
|
+
module.exports = __toCommonJS(index_exports);
|
|
33
|
+
|
|
34
|
+
// src/errors.ts
|
|
35
|
+
var VelobaseError = class extends Error {
|
|
36
|
+
status;
|
|
37
|
+
type;
|
|
38
|
+
constructor(message, status, type) {
|
|
39
|
+
super(message);
|
|
40
|
+
this.name = "VelobaseError";
|
|
41
|
+
this.status = status;
|
|
42
|
+
this.type = type;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
var VelobaseAuthenticationError = class extends VelobaseError {
|
|
46
|
+
constructor(message) {
|
|
47
|
+
super(message, 401, "auth_error");
|
|
48
|
+
this.name = "VelobaseAuthenticationError";
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
var VelobaseValidationError = class extends VelobaseError {
|
|
52
|
+
constructor(message) {
|
|
53
|
+
super(message, 400, "validation_error");
|
|
54
|
+
this.name = "VelobaseValidationError";
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
var VelobaseNotFoundError = class extends VelobaseError {
|
|
58
|
+
constructor(message) {
|
|
59
|
+
super(message, 404, "not_found");
|
|
60
|
+
this.name = "VelobaseNotFoundError";
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
var VelobaseConflictError = class extends VelobaseError {
|
|
64
|
+
constructor(message) {
|
|
65
|
+
super(message, 409, "conflict");
|
|
66
|
+
this.name = "VelobaseConflictError";
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
var VelobaseInternalError = class extends VelobaseError {
|
|
70
|
+
constructor(message) {
|
|
71
|
+
super(message, 500, "server_error");
|
|
72
|
+
this.name = "VelobaseInternalError";
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// src/http.ts
|
|
77
|
+
function toSnakeCase(str) {
|
|
78
|
+
return str.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`);
|
|
79
|
+
}
|
|
80
|
+
function toCamelCase(str) {
|
|
81
|
+
return str.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
|
|
82
|
+
}
|
|
83
|
+
function convertKeys(obj, converter) {
|
|
84
|
+
if (Array.isArray(obj)) {
|
|
85
|
+
return obj.map((item) => convertKeys(item, converter));
|
|
86
|
+
}
|
|
87
|
+
if (obj !== null && typeof obj === "object") {
|
|
88
|
+
const result = {};
|
|
89
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
90
|
+
result[converter(key)] = convertKeys(value, converter);
|
|
91
|
+
}
|
|
92
|
+
return result;
|
|
93
|
+
}
|
|
94
|
+
return obj;
|
|
95
|
+
}
|
|
96
|
+
function toSnakeCaseKeys(obj) {
|
|
97
|
+
return convertKeys(obj, toSnakeCase);
|
|
98
|
+
}
|
|
99
|
+
function toCamelCaseKeys(obj) {
|
|
100
|
+
return convertKeys(obj, toCamelCase);
|
|
101
|
+
}
|
|
102
|
+
function isRetryable(status) {
|
|
103
|
+
return status >= 500 || status === 429;
|
|
104
|
+
}
|
|
105
|
+
function throwForStatus(status, message, type) {
|
|
106
|
+
switch (status) {
|
|
107
|
+
case 400:
|
|
108
|
+
throw new VelobaseValidationError(message);
|
|
109
|
+
case 401:
|
|
110
|
+
throw new VelobaseAuthenticationError(message);
|
|
111
|
+
case 404:
|
|
112
|
+
throw new VelobaseNotFoundError(message);
|
|
113
|
+
case 409:
|
|
114
|
+
throw new VelobaseConflictError(message);
|
|
115
|
+
case 500:
|
|
116
|
+
throw new VelobaseInternalError(message);
|
|
117
|
+
default:
|
|
118
|
+
throw new VelobaseError(message, status, type);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
var HttpClient = class {
|
|
122
|
+
baseUrl;
|
|
123
|
+
apiKey;
|
|
124
|
+
timeout;
|
|
125
|
+
maxRetries;
|
|
126
|
+
constructor(opts) {
|
|
127
|
+
this.baseUrl = opts.baseUrl.replace(/\/+$/, "");
|
|
128
|
+
this.apiKey = opts.apiKey;
|
|
129
|
+
this.timeout = opts.timeout;
|
|
130
|
+
this.maxRetries = opts.maxRetries;
|
|
131
|
+
}
|
|
132
|
+
async request(method, path, body, headers) {
|
|
133
|
+
const url = `${this.baseUrl}${path}`;
|
|
134
|
+
let lastError;
|
|
135
|
+
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
136
|
+
if (attempt > 0) {
|
|
137
|
+
const delay = Math.min(500 * 2 ** (attempt - 1), 5e3);
|
|
138
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
139
|
+
}
|
|
140
|
+
const controller = new AbortController();
|
|
141
|
+
const timer = setTimeout(() => controller.abort(), this.timeout);
|
|
142
|
+
try {
|
|
143
|
+
const res = await fetch(url, {
|
|
144
|
+
method,
|
|
145
|
+
headers: {
|
|
146
|
+
"Authorization": `Bearer ${this.apiKey}`,
|
|
147
|
+
"Content-Type": "application/json",
|
|
148
|
+
...headers
|
|
149
|
+
},
|
|
150
|
+
body: body ? JSON.stringify(toSnakeCaseKeys(body)) : void 0,
|
|
151
|
+
signal: controller.signal
|
|
152
|
+
});
|
|
153
|
+
clearTimeout(timer);
|
|
154
|
+
if (!res.ok) {
|
|
155
|
+
const json2 = await res.json().catch(() => ({}));
|
|
156
|
+
const msg = json2.error?.message ?? `HTTP ${res.status}`;
|
|
157
|
+
const type = json2.error?.type ?? "unknown_error";
|
|
158
|
+
if (isRetryable(res.status) && attempt < this.maxRetries) {
|
|
159
|
+
lastError = new VelobaseError(msg, res.status, type);
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
throwForStatus(res.status, msg, type);
|
|
163
|
+
}
|
|
164
|
+
const json = await res.json();
|
|
165
|
+
return toCamelCaseKeys(json);
|
|
166
|
+
} catch (err) {
|
|
167
|
+
clearTimeout(timer);
|
|
168
|
+
if (err instanceof VelobaseError) {
|
|
169
|
+
throw err;
|
|
170
|
+
}
|
|
171
|
+
lastError = err;
|
|
172
|
+
if (attempt < this.maxRetries) {
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
throw new VelobaseError(
|
|
176
|
+
`Request failed: ${lastError.message}`,
|
|
177
|
+
0,
|
|
178
|
+
"network_error"
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
throw lastError;
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
// src/client.ts
|
|
187
|
+
var DEFAULT_BASE_URL = "https://api.velobase.io";
|
|
188
|
+
var DEFAULT_TIMEOUT = 3e4;
|
|
189
|
+
var DEFAULT_MAX_RETRIES = 2;
|
|
190
|
+
var BillingResource = class {
|
|
191
|
+
constructor(http) {
|
|
192
|
+
this.http = http;
|
|
193
|
+
}
|
|
194
|
+
async freeze(params) {
|
|
195
|
+
return this.http.request(
|
|
196
|
+
"POST",
|
|
197
|
+
"/v1/billing/freeze",
|
|
198
|
+
params
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
async consume(params) {
|
|
202
|
+
return this.http.request(
|
|
203
|
+
"POST",
|
|
204
|
+
"/v1/billing/consume",
|
|
205
|
+
params
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
async unfreeze(params) {
|
|
209
|
+
return this.http.request(
|
|
210
|
+
"POST",
|
|
211
|
+
"/v1/billing/unfreeze",
|
|
212
|
+
params
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
var CustomersResource = class {
|
|
217
|
+
constructor(http) {
|
|
218
|
+
this.http = http;
|
|
219
|
+
}
|
|
220
|
+
async deposit(params) {
|
|
221
|
+
const headers = {};
|
|
222
|
+
if (params.idempotencyKey) {
|
|
223
|
+
headers["Idempotency-Key"] = params.idempotencyKey;
|
|
224
|
+
}
|
|
225
|
+
return this.http.request(
|
|
226
|
+
"POST",
|
|
227
|
+
"/v1/customers/deposit",
|
|
228
|
+
params,
|
|
229
|
+
headers
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
async get(customerId) {
|
|
233
|
+
return this.http.request(
|
|
234
|
+
"GET",
|
|
235
|
+
`/v1/customers/${encodeURIComponent(customerId)}`
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
var Velobase = class {
|
|
240
|
+
billing;
|
|
241
|
+
customers;
|
|
242
|
+
constructor(opts) {
|
|
243
|
+
if (!opts.apiKey) {
|
|
244
|
+
throw new Error(
|
|
245
|
+
"apiKey is required. Get your API key at https://velobase.io"
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
const http = new HttpClient({
|
|
249
|
+
baseUrl: opts.baseUrl ?? DEFAULT_BASE_URL,
|
|
250
|
+
apiKey: opts.apiKey,
|
|
251
|
+
timeout: opts.timeout ?? DEFAULT_TIMEOUT,
|
|
252
|
+
maxRetries: opts.maxRetries ?? DEFAULT_MAX_RETRIES
|
|
253
|
+
});
|
|
254
|
+
this.billing = new BillingResource(http);
|
|
255
|
+
this.customers = new CustomersResource(http);
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
259
|
+
0 && (module.exports = {
|
|
260
|
+
Velobase,
|
|
261
|
+
VelobaseAuthenticationError,
|
|
262
|
+
VelobaseConflictError,
|
|
263
|
+
VelobaseError,
|
|
264
|
+
VelobaseInternalError,
|
|
265
|
+
VelobaseNotFoundError,
|
|
266
|
+
VelobaseValidationError
|
|
267
|
+
});
|
|
268
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/http.ts","../src/client.ts"],"sourcesContent":["export { Velobase } from \"./client\";\nexport {\n VelobaseError,\n VelobaseAuthenticationError,\n VelobaseValidationError,\n VelobaseNotFoundError,\n VelobaseConflictError,\n VelobaseInternalError,\n} from \"./errors\";\nexport type {\n VelobaseOptions,\n FreezeParams,\n FreezeResponse,\n ConsumeParams,\n ConsumeResponse,\n UnfreezeParams,\n UnfreezeResponse,\n DepositParams,\n DepositResponse,\n CustomerBalance,\n CustomerAccount,\n CustomerResponse,\n} from \"./types\";\n\n// default export for convenience\nexport { Velobase as default } from \"./client\";\n","export class VelobaseError extends Error {\n status: number;\n type: string;\n\n constructor(message: string, status: number, type: string) {\n super(message);\n this.name = \"VelobaseError\";\n this.status = status;\n this.type = type;\n }\n}\n\nexport class VelobaseAuthenticationError extends VelobaseError {\n constructor(message: string) {\n super(message, 401, \"auth_error\");\n this.name = \"VelobaseAuthenticationError\";\n }\n}\n\nexport class VelobaseValidationError extends VelobaseError {\n constructor(message: string) {\n super(message, 400, \"validation_error\");\n this.name = \"VelobaseValidationError\";\n }\n}\n\nexport class VelobaseNotFoundError extends VelobaseError {\n constructor(message: string) {\n super(message, 404, \"not_found\");\n this.name = \"VelobaseNotFoundError\";\n }\n}\n\nexport class VelobaseConflictError extends VelobaseError {\n constructor(message: string) {\n super(message, 409, \"conflict\");\n this.name = \"VelobaseConflictError\";\n }\n}\n\nexport class VelobaseInternalError extends VelobaseError {\n constructor(message: string) {\n super(message, 500, \"server_error\");\n this.name = \"VelobaseInternalError\";\n }\n}\n","import {\n VelobaseAuthenticationError,\n VelobaseConflictError,\n VelobaseError,\n VelobaseInternalError,\n VelobaseNotFoundError,\n VelobaseValidationError,\n} from \"./errors\";\n\nexport interface HttpClientOptions {\n baseUrl: string;\n apiKey: string;\n timeout: number;\n maxRetries: number;\n}\n\nfunction toSnakeCase(str: string): string {\n return str.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`);\n}\n\nfunction toCamelCase(str: string): string {\n return str.replace(/_([a-z])/g, (_, c: string) => c.toUpperCase());\n}\n\nfunction convertKeys(\n obj: unknown,\n converter: (key: string) => string,\n): unknown {\n if (Array.isArray(obj)) {\n return obj.map((item) => convertKeys(item, converter));\n }\n if (obj !== null && typeof obj === \"object\") {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj as Record<string, unknown>)) {\n result[converter(key)] = convertKeys(value, converter);\n }\n return result;\n }\n return obj;\n}\n\nexport function toSnakeCaseKeys<T>(obj: T): unknown {\n return convertKeys(obj, toSnakeCase);\n}\n\nexport function toCamelCaseKeys<T>(obj: T): unknown {\n return convertKeys(obj, toCamelCase);\n}\n\nfunction isRetryable(status: number): boolean {\n return status >= 500 || status === 429;\n}\n\nfunction throwForStatus(\n status: number,\n message: string,\n type: string,\n): never {\n switch (status) {\n case 400:\n throw new VelobaseValidationError(message);\n case 401:\n throw new VelobaseAuthenticationError(message);\n case 404:\n throw new VelobaseNotFoundError(message);\n case 409:\n throw new VelobaseConflictError(message);\n case 500:\n throw new VelobaseInternalError(message);\n default:\n throw new VelobaseError(message, status, type);\n }\n}\n\nexport class HttpClient {\n private baseUrl: string;\n private apiKey: string;\n private timeout: number;\n private maxRetries: number;\n\n constructor(opts: HttpClientOptions) {\n this.baseUrl = opts.baseUrl.replace(/\\/+$/, \"\");\n this.apiKey = opts.apiKey;\n this.timeout = opts.timeout;\n this.maxRetries = opts.maxRetries;\n }\n\n async request<T>(\n method: string,\n path: string,\n body?: unknown,\n headers?: Record<string, string>,\n ): Promise<T> {\n const url = `${this.baseUrl}${path}`;\n let lastError: Error | undefined;\n\n for (let attempt = 0; attempt <= this.maxRetries; attempt++) {\n if (attempt > 0) {\n const delay = Math.min(500 * 2 ** (attempt - 1), 5000);\n await new Promise((r) => setTimeout(r, delay));\n }\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const res = await fetch(url, {\n method,\n headers: {\n \"Authorization\": `Bearer ${this.apiKey}`,\n \"Content-Type\": \"application/json\",\n ...headers,\n },\n body: body ? JSON.stringify(toSnakeCaseKeys(body)) : undefined,\n signal: controller.signal,\n });\n\n clearTimeout(timer);\n\n if (!res.ok) {\n const json = (await res.json().catch(() => ({}))) as {\n error?: { message?: string; type?: string };\n };\n const msg =\n json.error?.message ?? `HTTP ${res.status}`;\n const type = json.error?.type ?? \"unknown_error\";\n\n if (isRetryable(res.status) && attempt < this.maxRetries) {\n lastError = new VelobaseError(msg, res.status, type);\n continue;\n }\n\n throwForStatus(res.status, msg, type);\n }\n\n const json = await res.json();\n return toCamelCaseKeys(json) as T;\n } catch (err) {\n clearTimeout(timer);\n\n if (err instanceof VelobaseError) {\n throw err;\n }\n\n lastError = err as Error;\n\n if (attempt < this.maxRetries) {\n continue;\n }\n\n throw new VelobaseError(\n `Request failed: ${lastError.message}`,\n 0,\n \"network_error\",\n );\n }\n }\n\n throw lastError;\n }\n}\n","import { HttpClient } from \"./http\";\nimport type {\n ConsumeParams,\n ConsumeResponse,\n CustomerResponse,\n DepositParams,\n DepositResponse,\n FreezeParams,\n FreezeResponse,\n UnfreezeParams,\n UnfreezeResponse,\n VelobaseOptions,\n} from \"./types\";\n\nconst DEFAULT_BASE_URL = \"https://api.velobase.io\";\nconst DEFAULT_TIMEOUT = 30_000;\nconst DEFAULT_MAX_RETRIES = 2;\n\nclass BillingResource {\n constructor(private http: HttpClient) {}\n\n async freeze(params: FreezeParams): Promise<FreezeResponse> {\n return this.http.request<FreezeResponse>(\n \"POST\",\n \"/v1/billing/freeze\",\n params,\n );\n }\n\n async consume(params: ConsumeParams): Promise<ConsumeResponse> {\n return this.http.request<ConsumeResponse>(\n \"POST\",\n \"/v1/billing/consume\",\n params,\n );\n }\n\n async unfreeze(params: UnfreezeParams): Promise<UnfreezeResponse> {\n return this.http.request<UnfreezeResponse>(\n \"POST\",\n \"/v1/billing/unfreeze\",\n params,\n );\n }\n}\n\nclass CustomersResource {\n constructor(private http: HttpClient) {}\n\n async deposit(params: DepositParams): Promise<DepositResponse> {\n const headers: Record<string, string> = {};\n if (params.idempotencyKey) {\n headers[\"Idempotency-Key\"] = params.idempotencyKey;\n }\n return this.http.request<DepositResponse>(\n \"POST\",\n \"/v1/customers/deposit\",\n params,\n headers,\n );\n }\n\n async get(customerId: string): Promise<CustomerResponse> {\n return this.http.request<CustomerResponse>(\n \"GET\",\n `/v1/customers/${encodeURIComponent(customerId)}`,\n );\n }\n}\n\nexport class Velobase {\n readonly billing: BillingResource;\n readonly customers: CustomersResource;\n\n constructor(opts: VelobaseOptions) {\n if (!opts.apiKey) {\n throw new Error(\n \"apiKey is required. Get your API key at https://velobase.io\",\n );\n }\n\n const http = new HttpClient({\n baseUrl: opts.baseUrl ?? DEFAULT_BASE_URL,\n apiKey: opts.apiKey,\n timeout: opts.timeout ?? DEFAULT_TIMEOUT,\n maxRetries: opts.maxRetries ?? DEFAULT_MAX_RETRIES,\n });\n\n this.billing = new BillingResource(http);\n this.customers = new CustomersResource(http);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAO,IAAM,gBAAN,cAA4B,MAAM;AAAA,EACvC;AAAA,EACA;AAAA,EAEA,YAAY,SAAiB,QAAgB,MAAc;AACzD,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,SAAS;AACd,SAAK,OAAO;AAAA,EACd;AACF;AAEO,IAAM,8BAAN,cAA0C,cAAc;AAAA,EAC7D,YAAY,SAAiB;AAC3B,UAAM,SAAS,KAAK,YAAY;AAChC,SAAK,OAAO;AAAA,EACd;AACF;AAEO,IAAM,0BAAN,cAAsC,cAAc;AAAA,EACzD,YAAY,SAAiB;AAC3B,UAAM,SAAS,KAAK,kBAAkB;AACtC,SAAK,OAAO;AAAA,EACd;AACF;AAEO,IAAM,wBAAN,cAAoC,cAAc;AAAA,EACvD,YAAY,SAAiB;AAC3B,UAAM,SAAS,KAAK,WAAW;AAC/B,SAAK,OAAO;AAAA,EACd;AACF;AAEO,IAAM,wBAAN,cAAoC,cAAc;AAAA,EACvD,YAAY,SAAiB;AAC3B,UAAM,SAAS,KAAK,UAAU;AAC9B,SAAK,OAAO;AAAA,EACd;AACF;AAEO,IAAM,wBAAN,cAAoC,cAAc;AAAA,EACvD,YAAY,SAAiB;AAC3B,UAAM,SAAS,KAAK,cAAc;AAClC,SAAK,OAAO;AAAA,EACd;AACF;;;AC7BA,SAAS,YAAY,KAAqB;AACxC,SAAO,IAAI,QAAQ,UAAU,CAAC,MAAM,IAAI,EAAE,YAAY,CAAC,EAAE;AAC3D;AAEA,SAAS,YAAY,KAAqB;AACxC,SAAO,IAAI,QAAQ,aAAa,CAAC,GAAG,MAAc,EAAE,YAAY,CAAC;AACnE;AAEA,SAAS,YACP,KACA,WACS;AACT,MAAI,MAAM,QAAQ,GAAG,GAAG;AACtB,WAAO,IAAI,IAAI,CAAC,SAAS,YAAY,MAAM,SAAS,CAAC;AAAA,EACvD;AACA,MAAI,QAAQ,QAAQ,OAAO,QAAQ,UAAU;AAC3C,UAAM,SAAkC,CAAC;AACzC,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,GAA8B,GAAG;AACzE,aAAO,UAAU,GAAG,CAAC,IAAI,YAAY,OAAO,SAAS;AAAA,IACvD;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEO,SAAS,gBAAmB,KAAiB;AAClD,SAAO,YAAY,KAAK,WAAW;AACrC;AAEO,SAAS,gBAAmB,KAAiB;AAClD,SAAO,YAAY,KAAK,WAAW;AACrC;AAEA,SAAS,YAAY,QAAyB;AAC5C,SAAO,UAAU,OAAO,WAAW;AACrC;AAEA,SAAS,eACP,QACA,SACA,MACO;AACP,UAAQ,QAAQ;AAAA,IACd,KAAK;AACH,YAAM,IAAI,wBAAwB,OAAO;AAAA,IAC3C,KAAK;AACH,YAAM,IAAI,4BAA4B,OAAO;AAAA,IAC/C,KAAK;AACH,YAAM,IAAI,sBAAsB,OAAO;AAAA,IACzC,KAAK;AACH,YAAM,IAAI,sBAAsB,OAAO;AAAA,IACzC,KAAK;AACH,YAAM,IAAI,sBAAsB,OAAO;AAAA,IACzC;AACE,YAAM,IAAI,cAAc,SAAS,QAAQ,IAAI;AAAA,EACjD;AACF;AAEO,IAAM,aAAN,MAAiB;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAER,YAAY,MAAyB;AACnC,SAAK,UAAU,KAAK,QAAQ,QAAQ,QAAQ,EAAE;AAC9C,SAAK,SAAS,KAAK;AACnB,SAAK,UAAU,KAAK;AACpB,SAAK,aAAa,KAAK;AAAA,EACzB;AAAA,EAEA,MAAM,QACJ,QACA,MACA,MACA,SACY;AACZ,UAAM,MAAM,GAAG,KAAK,OAAO,GAAG,IAAI;AAClC,QAAI;AAEJ,aAAS,UAAU,GAAG,WAAW,KAAK,YAAY,WAAW;AAC3D,UAAI,UAAU,GAAG;AACf,cAAM,QAAQ,KAAK,IAAI,MAAM,MAAM,UAAU,IAAI,GAAI;AACrD,cAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,KAAK,CAAC;AAAA,MAC/C;AAEA,YAAM,aAAa,IAAI,gBAAgB;AACvC,YAAM,QAAQ,WAAW,MAAM,WAAW,MAAM,GAAG,KAAK,OAAO;AAE/D,UAAI;AACF,cAAM,MAAM,MAAM,MAAM,KAAK;AAAA,UAC3B;AAAA,UACA,SAAS;AAAA,YACP,iBAAiB,UAAU,KAAK,MAAM;AAAA,YACtC,gBAAgB;AAAA,YAChB,GAAG;AAAA,UACL;AAAA,UACA,MAAM,OAAO,KAAK,UAAU,gBAAgB,IAAI,CAAC,IAAI;AAAA,UACrD,QAAQ,WAAW;AAAA,QACrB,CAAC;AAED,qBAAa,KAAK;AAElB,YAAI,CAAC,IAAI,IAAI;AACX,gBAAMA,QAAQ,MAAM,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAG/C,gBAAM,MACJA,MAAK,OAAO,WAAW,QAAQ,IAAI,MAAM;AAC3C,gBAAM,OAAOA,MAAK,OAAO,QAAQ;AAEjC,cAAI,YAAY,IAAI,MAAM,KAAK,UAAU,KAAK,YAAY;AACxD,wBAAY,IAAI,cAAc,KAAK,IAAI,QAAQ,IAAI;AACnD;AAAA,UACF;AAEA,yBAAe,IAAI,QAAQ,KAAK,IAAI;AAAA,QACtC;AAEA,cAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,eAAO,gBAAgB,IAAI;AAAA,MAC7B,SAAS,KAAK;AACZ,qBAAa,KAAK;AAElB,YAAI,eAAe,eAAe;AAChC,gBAAM;AAAA,QACR;AAEA,oBAAY;AAEZ,YAAI,UAAU,KAAK,YAAY;AAC7B;AAAA,QACF;AAEA,cAAM,IAAI;AAAA,UACR,mBAAmB,UAAU,OAAO;AAAA,UACpC;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,UAAM;AAAA,EACR;AACF;;;AClJA,IAAM,mBAAmB;AACzB,IAAM,kBAAkB;AACxB,IAAM,sBAAsB;AAE5B,IAAM,kBAAN,MAAsB;AAAA,EACpB,YAAoB,MAAkB;AAAlB;AAAA,EAAmB;AAAA,EAEvC,MAAM,OAAO,QAA+C;AAC1D,WAAO,KAAK,KAAK;AAAA,MACf;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,QAAQ,QAAiD;AAC7D,WAAO,KAAK,KAAK;AAAA,MACf;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,SAAS,QAAmD;AAChE,WAAO,KAAK,KAAK;AAAA,MACf;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;AAEA,IAAM,oBAAN,MAAwB;AAAA,EACtB,YAAoB,MAAkB;AAAlB;AAAA,EAAmB;AAAA,EAEvC,MAAM,QAAQ,QAAiD;AAC7D,UAAM,UAAkC,CAAC;AACzC,QAAI,OAAO,gBAAgB;AACzB,cAAQ,iBAAiB,IAAI,OAAO;AAAA,IACtC;AACA,WAAO,KAAK,KAAK;AAAA,MACf;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,IAAI,YAA+C;AACvD,WAAO,KAAK,KAAK;AAAA,MACf;AAAA,MACA,iBAAiB,mBAAmB,UAAU,CAAC;AAAA,IACjD;AAAA,EACF;AACF;AAEO,IAAM,WAAN,MAAe;AAAA,EACX;AAAA,EACA;AAAA,EAET,YAAY,MAAuB;AACjC,QAAI,CAAC,KAAK,QAAQ;AAChB,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,UAAM,OAAO,IAAI,WAAW;AAAA,MAC1B,SAAS,KAAK,WAAW;AAAA,MACzB,QAAQ,KAAK;AAAA,MACb,SAAS,KAAK,WAAW;AAAA,MACzB,YAAY,KAAK,cAAc;AAAA,IACjC,CAAC;AAED,SAAK,UAAU,IAAI,gBAAgB,IAAI;AACvC,SAAK,YAAY,IAAI,kBAAkB,IAAI;AAAA,EAC7C;AACF;","names":["json"]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var VelobaseError = class extends Error {
|
|
3
|
+
status;
|
|
4
|
+
type;
|
|
5
|
+
constructor(message, status, type) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "VelobaseError";
|
|
8
|
+
this.status = status;
|
|
9
|
+
this.type = type;
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
var VelobaseAuthenticationError = class extends VelobaseError {
|
|
13
|
+
constructor(message) {
|
|
14
|
+
super(message, 401, "auth_error");
|
|
15
|
+
this.name = "VelobaseAuthenticationError";
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
var VelobaseValidationError = class extends VelobaseError {
|
|
19
|
+
constructor(message) {
|
|
20
|
+
super(message, 400, "validation_error");
|
|
21
|
+
this.name = "VelobaseValidationError";
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
var VelobaseNotFoundError = class extends VelobaseError {
|
|
25
|
+
constructor(message) {
|
|
26
|
+
super(message, 404, "not_found");
|
|
27
|
+
this.name = "VelobaseNotFoundError";
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
var VelobaseConflictError = class extends VelobaseError {
|
|
31
|
+
constructor(message) {
|
|
32
|
+
super(message, 409, "conflict");
|
|
33
|
+
this.name = "VelobaseConflictError";
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
var VelobaseInternalError = class extends VelobaseError {
|
|
37
|
+
constructor(message) {
|
|
38
|
+
super(message, 500, "server_error");
|
|
39
|
+
this.name = "VelobaseInternalError";
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// src/http.ts
|
|
44
|
+
function toSnakeCase(str) {
|
|
45
|
+
return str.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`);
|
|
46
|
+
}
|
|
47
|
+
function toCamelCase(str) {
|
|
48
|
+
return str.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
|
|
49
|
+
}
|
|
50
|
+
function convertKeys(obj, converter) {
|
|
51
|
+
if (Array.isArray(obj)) {
|
|
52
|
+
return obj.map((item) => convertKeys(item, converter));
|
|
53
|
+
}
|
|
54
|
+
if (obj !== null && typeof obj === "object") {
|
|
55
|
+
const result = {};
|
|
56
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
57
|
+
result[converter(key)] = convertKeys(value, converter);
|
|
58
|
+
}
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
return obj;
|
|
62
|
+
}
|
|
63
|
+
function toSnakeCaseKeys(obj) {
|
|
64
|
+
return convertKeys(obj, toSnakeCase);
|
|
65
|
+
}
|
|
66
|
+
function toCamelCaseKeys(obj) {
|
|
67
|
+
return convertKeys(obj, toCamelCase);
|
|
68
|
+
}
|
|
69
|
+
function isRetryable(status) {
|
|
70
|
+
return status >= 500 || status === 429;
|
|
71
|
+
}
|
|
72
|
+
function throwForStatus(status, message, type) {
|
|
73
|
+
switch (status) {
|
|
74
|
+
case 400:
|
|
75
|
+
throw new VelobaseValidationError(message);
|
|
76
|
+
case 401:
|
|
77
|
+
throw new VelobaseAuthenticationError(message);
|
|
78
|
+
case 404:
|
|
79
|
+
throw new VelobaseNotFoundError(message);
|
|
80
|
+
case 409:
|
|
81
|
+
throw new VelobaseConflictError(message);
|
|
82
|
+
case 500:
|
|
83
|
+
throw new VelobaseInternalError(message);
|
|
84
|
+
default:
|
|
85
|
+
throw new VelobaseError(message, status, type);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
var HttpClient = class {
|
|
89
|
+
baseUrl;
|
|
90
|
+
apiKey;
|
|
91
|
+
timeout;
|
|
92
|
+
maxRetries;
|
|
93
|
+
constructor(opts) {
|
|
94
|
+
this.baseUrl = opts.baseUrl.replace(/\/+$/, "");
|
|
95
|
+
this.apiKey = opts.apiKey;
|
|
96
|
+
this.timeout = opts.timeout;
|
|
97
|
+
this.maxRetries = opts.maxRetries;
|
|
98
|
+
}
|
|
99
|
+
async request(method, path, body, headers) {
|
|
100
|
+
const url = `${this.baseUrl}${path}`;
|
|
101
|
+
let lastError;
|
|
102
|
+
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
103
|
+
if (attempt > 0) {
|
|
104
|
+
const delay = Math.min(500 * 2 ** (attempt - 1), 5e3);
|
|
105
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
106
|
+
}
|
|
107
|
+
const controller = new AbortController();
|
|
108
|
+
const timer = setTimeout(() => controller.abort(), this.timeout);
|
|
109
|
+
try {
|
|
110
|
+
const res = await fetch(url, {
|
|
111
|
+
method,
|
|
112
|
+
headers: {
|
|
113
|
+
"Authorization": `Bearer ${this.apiKey}`,
|
|
114
|
+
"Content-Type": "application/json",
|
|
115
|
+
...headers
|
|
116
|
+
},
|
|
117
|
+
body: body ? JSON.stringify(toSnakeCaseKeys(body)) : void 0,
|
|
118
|
+
signal: controller.signal
|
|
119
|
+
});
|
|
120
|
+
clearTimeout(timer);
|
|
121
|
+
if (!res.ok) {
|
|
122
|
+
const json2 = await res.json().catch(() => ({}));
|
|
123
|
+
const msg = json2.error?.message ?? `HTTP ${res.status}`;
|
|
124
|
+
const type = json2.error?.type ?? "unknown_error";
|
|
125
|
+
if (isRetryable(res.status) && attempt < this.maxRetries) {
|
|
126
|
+
lastError = new VelobaseError(msg, res.status, type);
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
throwForStatus(res.status, msg, type);
|
|
130
|
+
}
|
|
131
|
+
const json = await res.json();
|
|
132
|
+
return toCamelCaseKeys(json);
|
|
133
|
+
} catch (err) {
|
|
134
|
+
clearTimeout(timer);
|
|
135
|
+
if (err instanceof VelobaseError) {
|
|
136
|
+
throw err;
|
|
137
|
+
}
|
|
138
|
+
lastError = err;
|
|
139
|
+
if (attempt < this.maxRetries) {
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
throw new VelobaseError(
|
|
143
|
+
`Request failed: ${lastError.message}`,
|
|
144
|
+
0,
|
|
145
|
+
"network_error"
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
throw lastError;
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
// src/client.ts
|
|
154
|
+
var DEFAULT_BASE_URL = "https://api.velobase.io";
|
|
155
|
+
var DEFAULT_TIMEOUT = 3e4;
|
|
156
|
+
var DEFAULT_MAX_RETRIES = 2;
|
|
157
|
+
var BillingResource = class {
|
|
158
|
+
constructor(http) {
|
|
159
|
+
this.http = http;
|
|
160
|
+
}
|
|
161
|
+
async freeze(params) {
|
|
162
|
+
return this.http.request(
|
|
163
|
+
"POST",
|
|
164
|
+
"/v1/billing/freeze",
|
|
165
|
+
params
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
async consume(params) {
|
|
169
|
+
return this.http.request(
|
|
170
|
+
"POST",
|
|
171
|
+
"/v1/billing/consume",
|
|
172
|
+
params
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
async unfreeze(params) {
|
|
176
|
+
return this.http.request(
|
|
177
|
+
"POST",
|
|
178
|
+
"/v1/billing/unfreeze",
|
|
179
|
+
params
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
var CustomersResource = class {
|
|
184
|
+
constructor(http) {
|
|
185
|
+
this.http = http;
|
|
186
|
+
}
|
|
187
|
+
async deposit(params) {
|
|
188
|
+
const headers = {};
|
|
189
|
+
if (params.idempotencyKey) {
|
|
190
|
+
headers["Idempotency-Key"] = params.idempotencyKey;
|
|
191
|
+
}
|
|
192
|
+
return this.http.request(
|
|
193
|
+
"POST",
|
|
194
|
+
"/v1/customers/deposit",
|
|
195
|
+
params,
|
|
196
|
+
headers
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
async get(customerId) {
|
|
200
|
+
return this.http.request(
|
|
201
|
+
"GET",
|
|
202
|
+
`/v1/customers/${encodeURIComponent(customerId)}`
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
var Velobase = class {
|
|
207
|
+
billing;
|
|
208
|
+
customers;
|
|
209
|
+
constructor(opts) {
|
|
210
|
+
if (!opts.apiKey) {
|
|
211
|
+
throw new Error(
|
|
212
|
+
"apiKey is required. Get your API key at https://velobase.io"
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
const http = new HttpClient({
|
|
216
|
+
baseUrl: opts.baseUrl ?? DEFAULT_BASE_URL,
|
|
217
|
+
apiKey: opts.apiKey,
|
|
218
|
+
timeout: opts.timeout ?? DEFAULT_TIMEOUT,
|
|
219
|
+
maxRetries: opts.maxRetries ?? DEFAULT_MAX_RETRIES
|
|
220
|
+
});
|
|
221
|
+
this.billing = new BillingResource(http);
|
|
222
|
+
this.customers = new CustomersResource(http);
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
export {
|
|
226
|
+
Velobase,
|
|
227
|
+
VelobaseAuthenticationError,
|
|
228
|
+
VelobaseConflictError,
|
|
229
|
+
VelobaseError,
|
|
230
|
+
VelobaseInternalError,
|
|
231
|
+
VelobaseNotFoundError,
|
|
232
|
+
VelobaseValidationError,
|
|
233
|
+
Velobase as default
|
|
234
|
+
};
|
|
235
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/http.ts","../src/client.ts"],"sourcesContent":["export class VelobaseError extends Error {\n status: number;\n type: string;\n\n constructor(message: string, status: number, type: string) {\n super(message);\n this.name = \"VelobaseError\";\n this.status = status;\n this.type = type;\n }\n}\n\nexport class VelobaseAuthenticationError extends VelobaseError {\n constructor(message: string) {\n super(message, 401, \"auth_error\");\n this.name = \"VelobaseAuthenticationError\";\n }\n}\n\nexport class VelobaseValidationError extends VelobaseError {\n constructor(message: string) {\n super(message, 400, \"validation_error\");\n this.name = \"VelobaseValidationError\";\n }\n}\n\nexport class VelobaseNotFoundError extends VelobaseError {\n constructor(message: string) {\n super(message, 404, \"not_found\");\n this.name = \"VelobaseNotFoundError\";\n }\n}\n\nexport class VelobaseConflictError extends VelobaseError {\n constructor(message: string) {\n super(message, 409, \"conflict\");\n this.name = \"VelobaseConflictError\";\n }\n}\n\nexport class VelobaseInternalError extends VelobaseError {\n constructor(message: string) {\n super(message, 500, \"server_error\");\n this.name = \"VelobaseInternalError\";\n }\n}\n","import {\n VelobaseAuthenticationError,\n VelobaseConflictError,\n VelobaseError,\n VelobaseInternalError,\n VelobaseNotFoundError,\n VelobaseValidationError,\n} from \"./errors\";\n\nexport interface HttpClientOptions {\n baseUrl: string;\n apiKey: string;\n timeout: number;\n maxRetries: number;\n}\n\nfunction toSnakeCase(str: string): string {\n return str.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`);\n}\n\nfunction toCamelCase(str: string): string {\n return str.replace(/_([a-z])/g, (_, c: string) => c.toUpperCase());\n}\n\nfunction convertKeys(\n obj: unknown,\n converter: (key: string) => string,\n): unknown {\n if (Array.isArray(obj)) {\n return obj.map((item) => convertKeys(item, converter));\n }\n if (obj !== null && typeof obj === \"object\") {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj as Record<string, unknown>)) {\n result[converter(key)] = convertKeys(value, converter);\n }\n return result;\n }\n return obj;\n}\n\nexport function toSnakeCaseKeys<T>(obj: T): unknown {\n return convertKeys(obj, toSnakeCase);\n}\n\nexport function toCamelCaseKeys<T>(obj: T): unknown {\n return convertKeys(obj, toCamelCase);\n}\n\nfunction isRetryable(status: number): boolean {\n return status >= 500 || status === 429;\n}\n\nfunction throwForStatus(\n status: number,\n message: string,\n type: string,\n): never {\n switch (status) {\n case 400:\n throw new VelobaseValidationError(message);\n case 401:\n throw new VelobaseAuthenticationError(message);\n case 404:\n throw new VelobaseNotFoundError(message);\n case 409:\n throw new VelobaseConflictError(message);\n case 500:\n throw new VelobaseInternalError(message);\n default:\n throw new VelobaseError(message, status, type);\n }\n}\n\nexport class HttpClient {\n private baseUrl: string;\n private apiKey: string;\n private timeout: number;\n private maxRetries: number;\n\n constructor(opts: HttpClientOptions) {\n this.baseUrl = opts.baseUrl.replace(/\\/+$/, \"\");\n this.apiKey = opts.apiKey;\n this.timeout = opts.timeout;\n this.maxRetries = opts.maxRetries;\n }\n\n async request<T>(\n method: string,\n path: string,\n body?: unknown,\n headers?: Record<string, string>,\n ): Promise<T> {\n const url = `${this.baseUrl}${path}`;\n let lastError: Error | undefined;\n\n for (let attempt = 0; attempt <= this.maxRetries; attempt++) {\n if (attempt > 0) {\n const delay = Math.min(500 * 2 ** (attempt - 1), 5000);\n await new Promise((r) => setTimeout(r, delay));\n }\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const res = await fetch(url, {\n method,\n headers: {\n \"Authorization\": `Bearer ${this.apiKey}`,\n \"Content-Type\": \"application/json\",\n ...headers,\n },\n body: body ? JSON.stringify(toSnakeCaseKeys(body)) : undefined,\n signal: controller.signal,\n });\n\n clearTimeout(timer);\n\n if (!res.ok) {\n const json = (await res.json().catch(() => ({}))) as {\n error?: { message?: string; type?: string };\n };\n const msg =\n json.error?.message ?? `HTTP ${res.status}`;\n const type = json.error?.type ?? \"unknown_error\";\n\n if (isRetryable(res.status) && attempt < this.maxRetries) {\n lastError = new VelobaseError(msg, res.status, type);\n continue;\n }\n\n throwForStatus(res.status, msg, type);\n }\n\n const json = await res.json();\n return toCamelCaseKeys(json) as T;\n } catch (err) {\n clearTimeout(timer);\n\n if (err instanceof VelobaseError) {\n throw err;\n }\n\n lastError = err as Error;\n\n if (attempt < this.maxRetries) {\n continue;\n }\n\n throw new VelobaseError(\n `Request failed: ${lastError.message}`,\n 0,\n \"network_error\",\n );\n }\n }\n\n throw lastError;\n }\n}\n","import { HttpClient } from \"./http\";\nimport type {\n ConsumeParams,\n ConsumeResponse,\n CustomerResponse,\n DepositParams,\n DepositResponse,\n FreezeParams,\n FreezeResponse,\n UnfreezeParams,\n UnfreezeResponse,\n VelobaseOptions,\n} from \"./types\";\n\nconst DEFAULT_BASE_URL = \"https://api.velobase.io\";\nconst DEFAULT_TIMEOUT = 30_000;\nconst DEFAULT_MAX_RETRIES = 2;\n\nclass BillingResource {\n constructor(private http: HttpClient) {}\n\n async freeze(params: FreezeParams): Promise<FreezeResponse> {\n return this.http.request<FreezeResponse>(\n \"POST\",\n \"/v1/billing/freeze\",\n params,\n );\n }\n\n async consume(params: ConsumeParams): Promise<ConsumeResponse> {\n return this.http.request<ConsumeResponse>(\n \"POST\",\n \"/v1/billing/consume\",\n params,\n );\n }\n\n async unfreeze(params: UnfreezeParams): Promise<UnfreezeResponse> {\n return this.http.request<UnfreezeResponse>(\n \"POST\",\n \"/v1/billing/unfreeze\",\n params,\n );\n }\n}\n\nclass CustomersResource {\n constructor(private http: HttpClient) {}\n\n async deposit(params: DepositParams): Promise<DepositResponse> {\n const headers: Record<string, string> = {};\n if (params.idempotencyKey) {\n headers[\"Idempotency-Key\"] = params.idempotencyKey;\n }\n return this.http.request<DepositResponse>(\n \"POST\",\n \"/v1/customers/deposit\",\n params,\n headers,\n );\n }\n\n async get(customerId: string): Promise<CustomerResponse> {\n return this.http.request<CustomerResponse>(\n \"GET\",\n `/v1/customers/${encodeURIComponent(customerId)}`,\n );\n }\n}\n\nexport class Velobase {\n readonly billing: BillingResource;\n readonly customers: CustomersResource;\n\n constructor(opts: VelobaseOptions) {\n if (!opts.apiKey) {\n throw new Error(\n \"apiKey is required. Get your API key at https://velobase.io\",\n );\n }\n\n const http = new HttpClient({\n baseUrl: opts.baseUrl ?? DEFAULT_BASE_URL,\n apiKey: opts.apiKey,\n timeout: opts.timeout ?? DEFAULT_TIMEOUT,\n maxRetries: opts.maxRetries ?? DEFAULT_MAX_RETRIES,\n });\n\n this.billing = new BillingResource(http);\n this.customers = new CustomersResource(http);\n }\n}\n"],"mappings":"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","names":["json"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@velobaseai/billing",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Velobase Billing SDK for JavaScript/TypeScript",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": {
|
|
11
|
+
"types": "./dist/index.d.mts",
|
|
12
|
+
"default": "./dist/index.mjs"
|
|
13
|
+
},
|
|
14
|
+
"require": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"default": "./dist/index.js"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsup",
|
|
25
|
+
"typecheck": "tsc --noEmit",
|
|
26
|
+
"clean": "rm -rf dist"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"velobase",
|
|
30
|
+
"billing",
|
|
31
|
+
"credits",
|
|
32
|
+
"api",
|
|
33
|
+
"sdk"
|
|
34
|
+
],
|
|
35
|
+
"author": "Velobase",
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "https://github.com/velobase/billing-sdk-js.git"
|
|
40
|
+
},
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=18"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"tsup": "^8.0.0",
|
|
46
|
+
"typescript": "^5.5.0"
|
|
47
|
+
}
|
|
48
|
+
}
|