meterflow 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 +89 -0
- package/dist/client.d.ts +32 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/errors.d.ts +30 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/index.cjs +358 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +358 -0
- package/dist/index.mjs +322 -0
- package/dist/resources/credits.d.ts +26 -0
- package/dist/resources/credits.d.ts.map +1 -0
- package/dist/resources/plans.d.ts +14 -0
- package/dist/resources/plans.d.ts.map +1 -0
- package/dist/resources/subscriptions.d.ts +24 -0
- package/dist/resources/subscriptions.d.ts.map +1 -0
- package/dist/resources/usage.d.ts +22 -0
- package/dist/resources/usage.d.ts.map +1 -0
- package/dist/utils/http.d.ts +3 -0
- package/dist/utils/http.d.ts.map +1 -0
- package/dist/utils/retry.d.ts +8 -0
- package/dist/utils/retry.d.ts.map +1 -0
- package/dist/utils/webhook-verify.d.ts +2 -0
- package/dist/utils/webhook-verify.d.ts.map +1 -0
- package/dist/vitest.config.d.ts +3 -0
- package/dist/vitest.config.d.ts.map +1 -0
- package/package.json +60 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 MeterFlow
|
|
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,89 @@
|
|
|
1
|
+
# meterflow
|
|
2
|
+
|
|
3
|
+
Official Node.js SDK for [MeterFlow](https://meterflow.com) — usage-based billing, credit management, and metering.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- Node.js ≥ 18
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install meterflow
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick start
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { MeterFlow } from "meterflow";
|
|
19
|
+
|
|
20
|
+
const client = new MeterFlow({ apiKey: "mf_live_your_api_key" });
|
|
21
|
+
|
|
22
|
+
// Grant credits to a customer
|
|
23
|
+
await client.credits.grant({
|
|
24
|
+
customer_external_id: "customer_123",
|
|
25
|
+
amount: 100,
|
|
26
|
+
description: "Welcome credits",
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Record a usage event
|
|
30
|
+
await client.usage.record({
|
|
31
|
+
customer_external_id: "customer_123",
|
|
32
|
+
meter_name: "api_calls",
|
|
33
|
+
quantity: 1,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Check credit balance
|
|
37
|
+
const balance = await client.credits.balance("customer_123");
|
|
38
|
+
console.log(balance.balance);
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Configuration
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
const client = new MeterFlow({
|
|
45
|
+
apiKey: "mf_live_your_api_key", // required; mf_live_* or mf_test_*
|
|
46
|
+
baseUrl: "https://api.meterflow.com/api/v1", // optional override
|
|
47
|
+
timeout: 30_000, // ms; default 30 s
|
|
48
|
+
retries: 3, // default 3; 0 to disable
|
|
49
|
+
});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Resources
|
|
53
|
+
|
|
54
|
+
| Resource | Methods |
|
|
55
|
+
|---|---|
|
|
56
|
+
| `client.credits` | `grant`, `deduct`, `balance`, `transactions` |
|
|
57
|
+
| `client.usage` | `record`, `recordBatch`, `summary` |
|
|
58
|
+
| `client.subscriptions` | `create`, `list`, `get`, `update`, `delete` |
|
|
59
|
+
| `client.plans` | `list`, `get` |
|
|
60
|
+
|
|
61
|
+
## Webhook verification
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
import { verifyWebhook } from "meterflow";
|
|
65
|
+
|
|
66
|
+
const isValid = verifyWebhook(rawBody, req.headers["x-meterflow-signature"], webhookSecret);
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Error handling
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
import { MeterFlowError, InsufficientCreditsError, RateLimitError } from "meterflow";
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
await client.credits.deduct({ customer_external_id: "cust_1", amount: 999 });
|
|
76
|
+
} catch (err) {
|
|
77
|
+
if (err instanceof InsufficientCreditsError) {
|
|
78
|
+
console.log("Not enough credits");
|
|
79
|
+
} else if (err instanceof RateLimitError) {
|
|
80
|
+
console.log(`Rate limited, retry after ${err.retryAfter}s`);
|
|
81
|
+
} else if (err instanceof MeterFlowError) {
|
|
82
|
+
console.log(`Request ID for support: ${err.requestId}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## License
|
|
88
|
+
|
|
89
|
+
MIT
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { CreditsResource } from "./resources/credits";
|
|
2
|
+
import { UsageResource } from "./resources/usage";
|
|
3
|
+
import { SubscriptionsResource } from "./resources/subscriptions";
|
|
4
|
+
import { PlansResource } from "./resources/plans";
|
|
5
|
+
export interface MeterFlowOptions {
|
|
6
|
+
apiKey: string;
|
|
7
|
+
baseUrl?: string;
|
|
8
|
+
timeout?: number;
|
|
9
|
+
retries?: number;
|
|
10
|
+
fetch?: typeof globalThis.fetch;
|
|
11
|
+
}
|
|
12
|
+
export interface RequestOptions {
|
|
13
|
+
body?: unknown;
|
|
14
|
+
query?: Record<string, string | number | boolean>;
|
|
15
|
+
idempotencyKey?: string;
|
|
16
|
+
timeout?: number;
|
|
17
|
+
}
|
|
18
|
+
export declare class MeterFlow {
|
|
19
|
+
readonly options: Required<MeterFlowOptions>;
|
|
20
|
+
private _credits?;
|
|
21
|
+
private _usage?;
|
|
22
|
+
private _subscriptions?;
|
|
23
|
+
private _plans?;
|
|
24
|
+
constructor(options: MeterFlowOptions);
|
|
25
|
+
get credits(): CreditsResource;
|
|
26
|
+
get usage(): UsageResource;
|
|
27
|
+
get subscriptions(): SubscriptionsResource;
|
|
28
|
+
get plans(): PlansResource;
|
|
29
|
+
/** @internal — used by resource classes only; not part of the public API */
|
|
30
|
+
request<T>(method: string, path: string, opts?: RequestOptions): Promise<T>;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAClE,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAElD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;CACjC;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;IAClD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,SAAS;IACpB,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,gBAAgB,CAAC,CAAC;IAE7C,OAAO,CAAC,QAAQ,CAAC,CAAkB;IACnC,OAAO,CAAC,MAAM,CAAC,CAAgB;IAC/B,OAAO,CAAC,cAAc,CAAC,CAAwB;IAC/C,OAAO,CAAC,MAAM,CAAC,CAAgB;gBAEnB,OAAO,EAAE,gBAAgB;IAarC,IAAI,OAAO,IAAI,eAAe,CAE7B;IAED,IAAI,KAAK,IAAI,aAAa,CAEzB;IAED,IAAI,aAAa,IAAI,qBAAqB,CAEzC;IAED,IAAI,KAAK,IAAI,aAAa,CAEzB;IAED,4EAA4E;IAC5E,OAAO,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,CAAC,CAAC;CAG5E"}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export declare class MeterFlowError extends Error {
|
|
2
|
+
readonly errorType: string;
|
|
3
|
+
readonly requestId: string | undefined;
|
|
4
|
+
readonly statusCode: number | undefined;
|
|
5
|
+
readonly retryable: boolean;
|
|
6
|
+
constructor(message: string, errorType: string, retryable: boolean, requestId?: string, statusCode?: number);
|
|
7
|
+
}
|
|
8
|
+
export declare class AuthError extends MeterFlowError {
|
|
9
|
+
constructor(message: string, requestId?: string, statusCode?: number);
|
|
10
|
+
}
|
|
11
|
+
export declare class NotFoundError extends MeterFlowError {
|
|
12
|
+
constructor(message: string, requestId?: string);
|
|
13
|
+
}
|
|
14
|
+
export declare class InsufficientCreditsError extends MeterFlowError {
|
|
15
|
+
constructor(message: string, requestId?: string);
|
|
16
|
+
}
|
|
17
|
+
export declare class ConflictError extends MeterFlowError {
|
|
18
|
+
constructor(message: string, requestId?: string);
|
|
19
|
+
}
|
|
20
|
+
export declare class ValidationError extends MeterFlowError {
|
|
21
|
+
constructor(message: string, requestId?: string);
|
|
22
|
+
}
|
|
23
|
+
export declare class RateLimitError extends MeterFlowError {
|
|
24
|
+
readonly retryAfter: number | undefined;
|
|
25
|
+
constructor(message: string, retryAfter?: number, requestId?: string);
|
|
26
|
+
}
|
|
27
|
+
export declare class ServerError extends MeterFlowError {
|
|
28
|
+
constructor(message: string, requestId?: string, statusCode?: number);
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/errors.ts"],"names":[],"mappings":"AAAA,qBAAa,cAAe,SAAQ,KAAK;IACvC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;gBAEhB,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM;CAQ5G;AAED,qBAAa,SAAU,SAAQ,cAAc;gBAC/B,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,UAAU,SAAM;CAIlE;AAED,qBAAa,aAAc,SAAQ,cAAc;gBACnC,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM;CAIhD;AAED,qBAAa,wBAAyB,SAAQ,cAAc;gBAC9C,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM;CAIhD;AAED,qBAAa,aAAc,SAAQ,cAAc;gBACnC,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM;CAIhD;AAED,qBAAa,eAAgB,SAAQ,cAAc;gBACrC,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM;CAIhD;AAED,qBAAa,cAAe,SAAQ,cAAc;IAChD,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;gBAE5B,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM;CAKrE;AAED,qBAAa,WAAY,SAAQ,cAAc;gBACjC,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,UAAU,SAAM;CAIlE"}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,358 @@
|
|
|
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
|
+
AuthError: () => AuthError,
|
|
24
|
+
ConflictError: () => ConflictError,
|
|
25
|
+
InsufficientCreditsError: () => InsufficientCreditsError,
|
|
26
|
+
MeterFlow: () => MeterFlow,
|
|
27
|
+
MeterFlowError: () => MeterFlowError,
|
|
28
|
+
NotFoundError: () => NotFoundError,
|
|
29
|
+
RateLimitError: () => RateLimitError,
|
|
30
|
+
ServerError: () => ServerError,
|
|
31
|
+
ValidationError: () => ValidationError,
|
|
32
|
+
verifyWebhook: () => verifyWebhook
|
|
33
|
+
});
|
|
34
|
+
module.exports = __toCommonJS(index_exports);
|
|
35
|
+
|
|
36
|
+
// src/errors.ts
|
|
37
|
+
var MeterFlowError = class extends Error {
|
|
38
|
+
errorType;
|
|
39
|
+
requestId;
|
|
40
|
+
statusCode;
|
|
41
|
+
retryable;
|
|
42
|
+
constructor(message, errorType, retryable2, requestId, statusCode) {
|
|
43
|
+
super(message);
|
|
44
|
+
this.name = "MeterFlowError";
|
|
45
|
+
this.errorType = errorType;
|
|
46
|
+
this.retryable = retryable2;
|
|
47
|
+
this.requestId = requestId;
|
|
48
|
+
this.statusCode = statusCode;
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
var AuthError = class extends MeterFlowError {
|
|
52
|
+
constructor(message, requestId, statusCode = 401) {
|
|
53
|
+
super(message, "auth_error", false, requestId, statusCode);
|
|
54
|
+
this.name = "AuthError";
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
var NotFoundError = class extends MeterFlowError {
|
|
58
|
+
constructor(message, requestId) {
|
|
59
|
+
super(message, "not_found", false, requestId, 404);
|
|
60
|
+
this.name = "NotFoundError";
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
var InsufficientCreditsError = class extends MeterFlowError {
|
|
64
|
+
constructor(message, requestId) {
|
|
65
|
+
super(message, "insufficient_credits", false, requestId, 402);
|
|
66
|
+
this.name = "InsufficientCreditsError";
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
var ConflictError = class extends MeterFlowError {
|
|
70
|
+
constructor(message, requestId) {
|
|
71
|
+
super(message, "conflict", false, requestId, 409);
|
|
72
|
+
this.name = "ConflictError";
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
var ValidationError = class extends MeterFlowError {
|
|
76
|
+
constructor(message, requestId) {
|
|
77
|
+
super(message, "validation_error", false, requestId, 422);
|
|
78
|
+
this.name = "ValidationError";
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
var RateLimitError = class extends MeterFlowError {
|
|
82
|
+
retryAfter;
|
|
83
|
+
constructor(message, retryAfter, requestId) {
|
|
84
|
+
super(message, "rate_limit", true, requestId, 429);
|
|
85
|
+
this.name = "RateLimitError";
|
|
86
|
+
this.retryAfter = retryAfter;
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
var ServerError = class extends MeterFlowError {
|
|
90
|
+
constructor(message, requestId, statusCode = 500) {
|
|
91
|
+
super(message, "server_error", true, requestId, statusCode);
|
|
92
|
+
this.name = "ServerError";
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// src/utils/retry.ts
|
|
97
|
+
function sleep(ms) {
|
|
98
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
99
|
+
}
|
|
100
|
+
async function retryable(fn, opts, shouldRetry = () => true) {
|
|
101
|
+
let lastError;
|
|
102
|
+
for (let attempt = 0; attempt <= opts.retries; attempt++) {
|
|
103
|
+
try {
|
|
104
|
+
return await fn();
|
|
105
|
+
} catch (err) {
|
|
106
|
+
lastError = err;
|
|
107
|
+
const isLast = attempt === opts.retries;
|
|
108
|
+
if (isLast || !shouldRetry(err)) {
|
|
109
|
+
throw err;
|
|
110
|
+
}
|
|
111
|
+
let delayMs;
|
|
112
|
+
if (err instanceof RateLimitError && err.retryAfter != null) {
|
|
113
|
+
delayMs = err.retryAfter * 1e3;
|
|
114
|
+
} else {
|
|
115
|
+
const baseMs = Math.min(1e3 * Math.pow(opts.factor, attempt), opts.maxDelayMs);
|
|
116
|
+
delayMs = opts.jitter ? baseMs * (0.75 + Math.random() * 0.5) : baseMs;
|
|
117
|
+
}
|
|
118
|
+
await sleep(delayMs);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
throw lastError;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// src/utils/http.ts
|
|
125
|
+
var SDK_VERSION = "0.1.0";
|
|
126
|
+
function buildUrl(baseUrl, path, query) {
|
|
127
|
+
const url = new URL(path, baseUrl.endsWith("/") ? baseUrl : baseUrl + "/");
|
|
128
|
+
if (query) {
|
|
129
|
+
for (const [key, value] of Object.entries(query)) {
|
|
130
|
+
url.searchParams.set(key, String(value));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return url.toString();
|
|
134
|
+
}
|
|
135
|
+
function buildHeaders(options, opts) {
|
|
136
|
+
const headers = {
|
|
137
|
+
Authorization: `Bearer ${options.apiKey}`,
|
|
138
|
+
"User-Agent": `meterflow-node/${SDK_VERSION}`,
|
|
139
|
+
Accept: "application/json"
|
|
140
|
+
};
|
|
141
|
+
if (opts?.body !== void 0) {
|
|
142
|
+
headers["Content-Type"] = "application/json";
|
|
143
|
+
}
|
|
144
|
+
if (opts?.idempotencyKey) {
|
|
145
|
+
headers["Idempotency-Key"] = opts.idempotencyKey;
|
|
146
|
+
}
|
|
147
|
+
return headers;
|
|
148
|
+
}
|
|
149
|
+
async function parseErrorBody(response) {
|
|
150
|
+
try {
|
|
151
|
+
const json = await response.json();
|
|
152
|
+
if (typeof json["detail"] === "string") return json["detail"];
|
|
153
|
+
return JSON.stringify(json);
|
|
154
|
+
} catch {
|
|
155
|
+
return response.statusText || `HTTP ${response.status}`;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function mapResponseError(status, message, requestId, response) {
|
|
159
|
+
if (status === 401 || status === 403) return new AuthError(message, requestId, status);
|
|
160
|
+
if (status === 402) return new InsufficientCreditsError(message, requestId);
|
|
161
|
+
if (status === 404) return new NotFoundError(message, requestId);
|
|
162
|
+
if (status === 409) return new ConflictError(message, requestId);
|
|
163
|
+
if (status === 422) return new ValidationError(message, requestId);
|
|
164
|
+
if (status === 429) {
|
|
165
|
+
const retryAfterRaw = response.headers.get("Retry-After");
|
|
166
|
+
const retryAfter = retryAfterRaw != null ? parseInt(retryAfterRaw, 10) : void 0;
|
|
167
|
+
return new RateLimitError(message, Number.isFinite(retryAfter) ? retryAfter : void 0, requestId);
|
|
168
|
+
}
|
|
169
|
+
if (status >= 500) return new ServerError(message, requestId, status);
|
|
170
|
+
return new MeterFlowError(message, "request_error", false, requestId, status);
|
|
171
|
+
}
|
|
172
|
+
async function httpRequest(options, method, path, opts) {
|
|
173
|
+
const url = buildUrl(options.baseUrl, path, opts?.query);
|
|
174
|
+
const headers = buildHeaders(options, opts);
|
|
175
|
+
const body = opts?.body !== void 0 ? JSON.stringify(opts.body) : void 0;
|
|
176
|
+
const timeoutMs = opts?.timeout ?? options.timeout;
|
|
177
|
+
return retryable(
|
|
178
|
+
async () => {
|
|
179
|
+
const signal = AbortSignal.timeout(timeoutMs);
|
|
180
|
+
const fetchInit = { method, headers, signal };
|
|
181
|
+
if (body !== void 0) fetchInit.body = body;
|
|
182
|
+
const response = await options.fetch(url, fetchInit);
|
|
183
|
+
const requestId = response.headers.get("x-request-id") ?? void 0;
|
|
184
|
+
if (!response.ok) {
|
|
185
|
+
const message = await parseErrorBody(response);
|
|
186
|
+
throw mapResponseError(response.status, message, requestId, response);
|
|
187
|
+
}
|
|
188
|
+
if (response.status === 204 || response.headers.get("content-length") === "0") {
|
|
189
|
+
return void 0;
|
|
190
|
+
}
|
|
191
|
+
return await response.json();
|
|
192
|
+
},
|
|
193
|
+
{ retries: options.retries, factor: 2, jitter: true, maxDelayMs: 1e4 },
|
|
194
|
+
(err) => {
|
|
195
|
+
if (err instanceof MeterFlowError) return err.retryable;
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// src/resources/credits.ts
|
|
202
|
+
var CreditsResource = class {
|
|
203
|
+
constructor(client) {
|
|
204
|
+
this.client = client;
|
|
205
|
+
}
|
|
206
|
+
grant(body, opts) {
|
|
207
|
+
return this.client.request("POST", "credits/grant", {
|
|
208
|
+
body,
|
|
209
|
+
...opts?.idempotencyKey !== void 0 && { idempotencyKey: opts.idempotencyKey }
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
deduct(body, opts) {
|
|
213
|
+
return this.client.request("POST", "credits/deduct", {
|
|
214
|
+
body,
|
|
215
|
+
...opts?.idempotencyKey !== void 0 && { idempotencyKey: opts.idempotencyKey }
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
balance(customerId) {
|
|
219
|
+
return this.client.request("GET", `credits/${encodeURIComponent(customerId)}/balance`);
|
|
220
|
+
}
|
|
221
|
+
transactions(customerId, query) {
|
|
222
|
+
return this.client.request("GET", `credits/${encodeURIComponent(customerId)}/transactions`, {
|
|
223
|
+
...query !== void 0 && { query }
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
// src/resources/usage.ts
|
|
229
|
+
var UsageResource = class {
|
|
230
|
+
constructor(client) {
|
|
231
|
+
this.client = client;
|
|
232
|
+
}
|
|
233
|
+
record(body, opts) {
|
|
234
|
+
return this.client.request("POST", "usage/events", {
|
|
235
|
+
body,
|
|
236
|
+
...opts?.idempotencyKey !== void 0 && { idempotencyKey: opts.idempotencyKey }
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
recordBatch(events, opts) {
|
|
240
|
+
return this.client.request("POST", "usage/events/batch", {
|
|
241
|
+
body: { events },
|
|
242
|
+
...opts?.idempotencyKey !== void 0 && { idempotencyKey: opts.idempotencyKey }
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
summary(customerId, query) {
|
|
246
|
+
return this.client.request("GET", `usage/${encodeURIComponent(customerId)}`, {
|
|
247
|
+
...query !== void 0 && { query }
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
// src/resources/subscriptions.ts
|
|
253
|
+
var SubscriptionsResource = class {
|
|
254
|
+
constructor(client) {
|
|
255
|
+
this.client = client;
|
|
256
|
+
}
|
|
257
|
+
create(body, opts) {
|
|
258
|
+
return this.client.request("POST", "subscriptions", {
|
|
259
|
+
body,
|
|
260
|
+
...opts?.idempotencyKey !== void 0 && { idempotencyKey: opts.idempotencyKey }
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
list(query) {
|
|
264
|
+
return this.client.request("GET", "subscriptions", {
|
|
265
|
+
...query !== void 0 && { query }
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
get(subscriptionId) {
|
|
269
|
+
return this.client.request("GET", `subscriptions/${encodeURIComponent(subscriptionId)}`);
|
|
270
|
+
}
|
|
271
|
+
update(subscriptionId, body, opts) {
|
|
272
|
+
return this.client.request("PATCH", `subscriptions/${encodeURIComponent(subscriptionId)}`, {
|
|
273
|
+
body,
|
|
274
|
+
...opts?.idempotencyKey !== void 0 && { idempotencyKey: opts.idempotencyKey }
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
delete(subscriptionId) {
|
|
278
|
+
return this.client.request("DELETE", `subscriptions/${encodeURIComponent(subscriptionId)}`);
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
// src/resources/plans.ts
|
|
283
|
+
var PlansResource = class {
|
|
284
|
+
constructor(client) {
|
|
285
|
+
this.client = client;
|
|
286
|
+
}
|
|
287
|
+
list(query) {
|
|
288
|
+
return this.client.request("GET", "plans", {
|
|
289
|
+
...query !== void 0 && { query }
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
get(planId, query) {
|
|
293
|
+
return this.client.request("GET", `plans/${encodeURIComponent(planId)}`, {
|
|
294
|
+
...query !== void 0 && { query }
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
// src/client.ts
|
|
300
|
+
var MeterFlow = class {
|
|
301
|
+
options;
|
|
302
|
+
_credits;
|
|
303
|
+
_usage;
|
|
304
|
+
_subscriptions;
|
|
305
|
+
_plans;
|
|
306
|
+
constructor(options) {
|
|
307
|
+
if (!options.apiKey.startsWith("mf_live_") && !options.apiKey.startsWith("mf_test_")) {
|
|
308
|
+
throw new Error("apiKey must start with mf_live_ or mf_test_");
|
|
309
|
+
}
|
|
310
|
+
this.options = {
|
|
311
|
+
apiKey: options.apiKey,
|
|
312
|
+
baseUrl: options.baseUrl ?? "https://api.meterflow.com/api/v1",
|
|
313
|
+
timeout: options.timeout ?? 3e4,
|
|
314
|
+
retries: options.retries ?? 3,
|
|
315
|
+
fetch: options.fetch ?? globalThis.fetch
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
get credits() {
|
|
319
|
+
return this._credits ??= new CreditsResource(this);
|
|
320
|
+
}
|
|
321
|
+
get usage() {
|
|
322
|
+
return this._usage ??= new UsageResource(this);
|
|
323
|
+
}
|
|
324
|
+
get subscriptions() {
|
|
325
|
+
return this._subscriptions ??= new SubscriptionsResource(this);
|
|
326
|
+
}
|
|
327
|
+
get plans() {
|
|
328
|
+
return this._plans ??= new PlansResource(this);
|
|
329
|
+
}
|
|
330
|
+
/** @internal — used by resource classes only; not part of the public API */
|
|
331
|
+
request(method, path, opts) {
|
|
332
|
+
return httpRequest(this.options, method, path, opts);
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
// src/utils/webhook-verify.ts
|
|
337
|
+
var import_crypto = require("crypto");
|
|
338
|
+
function verifyWebhook(rawBody, signature, secret) {
|
|
339
|
+
const body = typeof rawBody === "string" ? Buffer.from(rawBody, "utf8") : rawBody;
|
|
340
|
+
const expected = (0, import_crypto.createHmac)("sha256", secret).update(body).digest("hex");
|
|
341
|
+
const expectedBuf = Buffer.from(expected, "utf8");
|
|
342
|
+
const actualBuf = Buffer.from(signature, "utf8");
|
|
343
|
+
if (expectedBuf.length !== actualBuf.length) return false;
|
|
344
|
+
return (0, import_crypto.timingSafeEqual)(expectedBuf, actualBuf);
|
|
345
|
+
}
|
|
346
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
347
|
+
0 && (module.exports = {
|
|
348
|
+
AuthError,
|
|
349
|
+
ConflictError,
|
|
350
|
+
InsufficientCreditsError,
|
|
351
|
+
MeterFlow,
|
|
352
|
+
MeterFlowError,
|
|
353
|
+
NotFoundError,
|
|
354
|
+
RateLimitError,
|
|
355
|
+
ServerError,
|
|
356
|
+
ValidationError,
|
|
357
|
+
verifyWebhook
|
|
358
|
+
});
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { MeterFlow } from "./client";
|
|
2
|
+
export { verifyWebhook } from "./utils/webhook-verify";
|
|
3
|
+
export { MeterFlowError, AuthError, NotFoundError, InsufficientCreditsError, ConflictError, ValidationError, RateLimitError, ServerError, } from "./errors";
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EACL,cAAc,EACd,SAAS,EACT,aAAa,EACb,wBAAwB,EACxB,aAAa,EACb,eAAe,EACf,cAAc,EACd,WAAW,GACZ,MAAM,UAAU,CAAC"}
|