meterflow 0.4.2 → 0.4.4
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 +14 -1
- package/dist/errors.d.ts +14 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/index.cjs +46 -9
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +46 -9
- package/dist/index.mjs +44 -9
- package/dist/resources/usage.d.ts +9 -0
- package/dist/resources/usage.d.ts.map +1 -1
- package/dist/utils/http.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -212,6 +212,17 @@ await client.usage.recordBatch([
|
|
|
212
212
|
]);
|
|
213
213
|
```
|
|
214
214
|
|
|
215
|
+
A batch holds at most **500 events** (exported as `MAX_BATCH_EVENTS`). A larger array is rejected locally with `PayloadTooLargeError` before anything is sent — the SDK deliberately does **not** split it for you, because one call is one request with one idempotency key, and turning it into several would make a partial failure indistinguishable from success. Chunk at the call site and give each chunk its own key:
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
import { MAX_BATCH_EVENTS } from "meterflow";
|
|
219
|
+
|
|
220
|
+
for (let i = 0; i < events.length; i += MAX_BATCH_EVENTS) {
|
|
221
|
+
const chunk = events.slice(i, i + MAX_BATCH_EVENTS);
|
|
222
|
+
await client.usage.recordBatch(chunk, { idempotencyKey: `job-42:chunk-${i / MAX_BATCH_EVENTS}` });
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
215
226
|
**Summary** — a customer's usage, broken down by meter:
|
|
216
227
|
|
|
217
228
|
```typescript
|
|
@@ -324,7 +335,8 @@ Every non-2xx response is thrown as a typed error. All of them extend `MeterFlow
|
|
|
324
335
|
| `InsufficientCreditsError` | 402 | `insufficient_credits` | no |
|
|
325
336
|
| `NotFoundError` | 404 | `not_found` | no |
|
|
326
337
|
| `ConflictError` | 409 | `conflict` | no |
|
|
327
|
-
| `
|
|
338
|
+
| `PayloadTooLargeError` | 413 | `payload_too_large` | no — split the batch (see `MAX_BATCH_EVENTS`) |
|
|
339
|
+
| `ValidationError` | 422 | `validation_error` | no — per-field detail in `.fields` |
|
|
328
340
|
| `RateLimitError` | 429 | `rate_limit` | yes (honours `Retry-After`, exposed as `.retryAfter`) |
|
|
329
341
|
| `ServerError` | 5xx | `server_error` | yes |
|
|
330
342
|
|
|
@@ -332,6 +344,7 @@ Every error carries:
|
|
|
332
344
|
|
|
333
345
|
- **`requestId`** — the API's `X-Request-ID` for that call. Include it when contacting support; it pinpoints the exact request in our logs.
|
|
334
346
|
- **`statusCode`**, **`errorType`**, and **`retryable`** — for programmatic handling and structured logging.
|
|
347
|
+
- **`message`** — the API's own sentence (e.g. `Plan limit reached: the Drip plan includes 5,000 usage events per month…`). A `ValidationError` appends its field detail (`Validation failed: amount: must be greater than 0`) and also exposes it structured as **`.fields`** (`{ field, message }[]`).
|
|
335
348
|
|
|
336
349
|
```typescript
|
|
337
350
|
import { MeterFlowError, InsufficientCreditsError, RateLimitError } from "meterflow";
|
package/dist/errors.d.ts
CHANGED
|
@@ -17,7 +17,21 @@ export declare class InsufficientCreditsError extends MeterFlowError {
|
|
|
17
17
|
export declare class ConflictError extends MeterFlowError {
|
|
18
18
|
constructor(message: string, requestId?: string);
|
|
19
19
|
}
|
|
20
|
+
/** One field the API rejected, as returned in a 422's `error.fields`. */
|
|
21
|
+
export interface ValidationFieldError {
|
|
22
|
+
field: string;
|
|
23
|
+
message: string;
|
|
24
|
+
}
|
|
20
25
|
export declare class ValidationError extends MeterFlowError {
|
|
26
|
+
/** Per-field detail from the API. Empty when the 422 carried none. */
|
|
27
|
+
readonly fields: ValidationFieldError[];
|
|
28
|
+
constructor(message: string, requestId?: string, fields?: ValidationFieldError[]);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The request was well-formed but too large — today only `usage.recordBatch` with more than
|
|
32
|
+
* `MAX_BATCH_EVENTS` events. Never retried: the same payload would be refused again. Split it.
|
|
33
|
+
*/
|
|
34
|
+
export declare class PayloadTooLargeError extends MeterFlowError {
|
|
21
35
|
constructor(message: string, requestId?: string);
|
|
22
36
|
}
|
|
23
37
|
export declare class RateLimitError extends MeterFlowError {
|
package/dist/errors.d.ts.map
CHANGED
|
@@ -1 +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;
|
|
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,yEAAyE;AACzE,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,eAAgB,SAAQ,cAAc;IACjD,sEAAsE;IACtE,QAAQ,CAAC,MAAM,EAAE,oBAAoB,EAAE,CAAC;gBAE5B,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,MAAM,GAAE,oBAAoB,EAAO;CAKrF;AAED;;;GAGG;AACH,qBAAa,oBAAqB,SAAQ,cAAc;gBAC1C,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
CHANGED
|
@@ -23,15 +23,20 @@ __export(index_exports, {
|
|
|
23
23
|
AuthError: () => AuthError,
|
|
24
24
|
ConflictError: () => ConflictError,
|
|
25
25
|
InsufficientCreditsError: () => InsufficientCreditsError,
|
|
26
|
+
MAX_BATCH_EVENTS: () => MAX_BATCH_EVENTS,
|
|
26
27
|
MeterFlow: () => MeterFlow,
|
|
27
28
|
MeterFlowError: () => MeterFlowError,
|
|
28
29
|
NotFoundError: () => NotFoundError,
|
|
30
|
+
PayloadTooLargeError: () => PayloadTooLargeError,
|
|
29
31
|
RateLimitError: () => RateLimitError,
|
|
30
32
|
ServerError: () => ServerError,
|
|
31
33
|
ValidationError: () => ValidationError
|
|
32
34
|
});
|
|
33
35
|
module.exports = __toCommonJS(index_exports);
|
|
34
36
|
|
|
37
|
+
// package.json
|
|
38
|
+
var version = "0.4.4";
|
|
39
|
+
|
|
35
40
|
// src/errors.ts
|
|
36
41
|
var MeterFlowError = class extends Error {
|
|
37
42
|
errorType;
|
|
@@ -72,9 +77,18 @@ var ConflictError = class extends MeterFlowError {
|
|
|
72
77
|
}
|
|
73
78
|
};
|
|
74
79
|
var ValidationError = class extends MeterFlowError {
|
|
75
|
-
|
|
80
|
+
/** Per-field detail from the API. Empty when the 422 carried none. */
|
|
81
|
+
fields;
|
|
82
|
+
constructor(message, requestId, fields = []) {
|
|
76
83
|
super(message, "validation_error", false, requestId, 422);
|
|
77
84
|
this.name = "ValidationError";
|
|
85
|
+
this.fields = fields;
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
var PayloadTooLargeError = class extends MeterFlowError {
|
|
89
|
+
constructor(message, requestId) {
|
|
90
|
+
super(message, "payload_too_large", false, requestId, 413);
|
|
91
|
+
this.name = "PayloadTooLargeError";
|
|
78
92
|
}
|
|
79
93
|
};
|
|
80
94
|
var RateLimitError = class extends MeterFlowError {
|
|
@@ -121,7 +135,7 @@ async function retryable(fn, opts, shouldRetry = () => true) {
|
|
|
121
135
|
}
|
|
122
136
|
|
|
123
137
|
// src/utils/http.ts
|
|
124
|
-
var SDK_VERSION =
|
|
138
|
+
var SDK_VERSION = version;
|
|
125
139
|
function buildUrl(baseUrl, path, query) {
|
|
126
140
|
const url = new URL(path, baseUrl.endsWith("/") ? baseUrl : baseUrl + "/");
|
|
127
141
|
if (query) {
|
|
@@ -145,21 +159,35 @@ function buildHeaders(options, opts) {
|
|
|
145
159
|
}
|
|
146
160
|
return headers;
|
|
147
161
|
}
|
|
162
|
+
function isFieldError(value) {
|
|
163
|
+
return typeof value === "object" && value !== null && typeof value.field === "string" && typeof value.message === "string";
|
|
164
|
+
}
|
|
148
165
|
async function parseErrorBody(response) {
|
|
149
166
|
try {
|
|
150
167
|
const json = await response.json();
|
|
151
|
-
|
|
152
|
-
|
|
168
|
+
const error = json["error"];
|
|
169
|
+
if (typeof error === "object" && error !== null) {
|
|
170
|
+
const body = error;
|
|
171
|
+
const fields = Array.isArray(body["fields"]) ? body["fields"].filter(isFieldError) : [];
|
|
172
|
+
if (typeof body["message"] === "string") {
|
|
173
|
+
const detail = fields.map((f) => `${f.field}: ${f.message}`).join("; ");
|
|
174
|
+
return { message: detail ? `${body["message"]}: ${detail}` : body["message"], fields };
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (typeof json["detail"] === "string") return { message: json["detail"], fields: [] };
|
|
178
|
+
return { message: JSON.stringify(json), fields: [] };
|
|
153
179
|
} catch {
|
|
154
|
-
return response.statusText || `HTTP ${response.status}
|
|
180
|
+
return { message: response.statusText || `HTTP ${response.status}`, fields: [] };
|
|
155
181
|
}
|
|
156
182
|
}
|
|
157
|
-
function mapResponseError(status,
|
|
183
|
+
function mapResponseError(status, parsed, requestId, response) {
|
|
184
|
+
const { message, fields } = parsed;
|
|
158
185
|
if (status === 401 || status === 403) return new AuthError(message, requestId, status);
|
|
159
186
|
if (status === 402) return new InsufficientCreditsError(message, requestId);
|
|
160
187
|
if (status === 404) return new NotFoundError(message, requestId);
|
|
161
188
|
if (status === 409) return new ConflictError(message, requestId);
|
|
162
|
-
if (status ===
|
|
189
|
+
if (status === 413) return new PayloadTooLargeError(message, requestId);
|
|
190
|
+
if (status === 422) return new ValidationError(message, requestId, fields);
|
|
163
191
|
if (status === 429) {
|
|
164
192
|
const retryAfterRaw = response.headers.get("Retry-After");
|
|
165
193
|
const retryAfter = retryAfterRaw != null ? parseInt(retryAfterRaw, 10) : void 0;
|
|
@@ -181,8 +209,8 @@ async function httpRequest(options, method, path, opts) {
|
|
|
181
209
|
const response = await options.fetch(url, fetchInit);
|
|
182
210
|
const requestId = response.headers.get("x-request-id") ?? void 0;
|
|
183
211
|
if (!response.ok) {
|
|
184
|
-
const
|
|
185
|
-
throw mapResponseError(response.status,
|
|
212
|
+
const parsed = await parseErrorBody(response);
|
|
213
|
+
throw mapResponseError(response.status, parsed, requestId, response);
|
|
186
214
|
}
|
|
187
215
|
if (response.status === 204 || response.headers.get("content-length") === "0") {
|
|
188
216
|
return void 0;
|
|
@@ -225,6 +253,7 @@ var CreditsResource = class {
|
|
|
225
253
|
};
|
|
226
254
|
|
|
227
255
|
// src/resources/usage.ts
|
|
256
|
+
var MAX_BATCH_EVENTS = 500;
|
|
228
257
|
var UsageResource = class {
|
|
229
258
|
constructor(client) {
|
|
230
259
|
this.client = client;
|
|
@@ -235,7 +264,13 @@ var UsageResource = class {
|
|
|
235
264
|
...opts?.idempotencyKey !== void 0 && { idempotencyKey: opts.idempotencyKey }
|
|
236
265
|
});
|
|
237
266
|
}
|
|
267
|
+
/** Record up to `MAX_BATCH_EVENTS` events in one request. Larger arrays are rejected locally, never sent. */
|
|
238
268
|
recordBatch(events, opts) {
|
|
269
|
+
if (events.length > MAX_BATCH_EVENTS) {
|
|
270
|
+
return Promise.reject(
|
|
271
|
+
new PayloadTooLargeError(`A batch may contain at most ${MAX_BATCH_EVENTS} events; this one has ${events.length}. Split it.`)
|
|
272
|
+
);
|
|
273
|
+
}
|
|
239
274
|
return this.client.request("POST", "usage/events/batch", {
|
|
240
275
|
body: { events },
|
|
241
276
|
...opts?.idempotencyKey !== void 0 && { idempotencyKey: opts.idempotencyKey }
|
|
@@ -338,9 +373,11 @@ var MeterFlow = class {
|
|
|
338
373
|
AuthError,
|
|
339
374
|
ConflictError,
|
|
340
375
|
InsufficientCreditsError,
|
|
376
|
+
MAX_BATCH_EVENTS,
|
|
341
377
|
MeterFlow,
|
|
342
378
|
MeterFlowError,
|
|
343
379
|
NotFoundError,
|
|
380
|
+
PayloadTooLargeError,
|
|
344
381
|
RateLimitError,
|
|
345
382
|
ServerError,
|
|
346
383
|
ValidationError
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { MeterFlow } from "./client";
|
|
2
|
-
export { MeterFlowError, AuthError, NotFoundError, InsufficientCreditsError, ConflictError, ValidationError, RateLimitError, ServerError, } from "./errors";
|
|
2
|
+
export { MeterFlowError, AuthError, NotFoundError, InsufficientCreditsError, ConflictError, PayloadTooLargeError, ValidationError, RateLimitError, ServerError, type ValidationFieldError, } from "./errors";
|
|
3
|
+
export { MAX_BATCH_EVENTS } from "./resources/usage";
|
|
3
4
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAIrC,OAAO,EACL,cAAc,EACd,SAAS,EACT,aAAa,EACb,wBAAwB,EACxB,aAAa,EACb,eAAe,EACf,cAAc,EACd,WAAW,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAIrC,OAAO,EACL,cAAc,EACd,SAAS,EACT,aAAa,EACb,wBAAwB,EACxB,aAAa,EACb,oBAAoB,EACpB,eAAe,EACf,cAAc,EACd,WAAW,EACX,KAAK,oBAAoB,GAC1B,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -23,15 +23,20 @@ __export(index_exports, {
|
|
|
23
23
|
AuthError: () => AuthError,
|
|
24
24
|
ConflictError: () => ConflictError,
|
|
25
25
|
InsufficientCreditsError: () => InsufficientCreditsError,
|
|
26
|
+
MAX_BATCH_EVENTS: () => MAX_BATCH_EVENTS,
|
|
26
27
|
MeterFlow: () => MeterFlow,
|
|
27
28
|
MeterFlowError: () => MeterFlowError,
|
|
28
29
|
NotFoundError: () => NotFoundError,
|
|
30
|
+
PayloadTooLargeError: () => PayloadTooLargeError,
|
|
29
31
|
RateLimitError: () => RateLimitError,
|
|
30
32
|
ServerError: () => ServerError,
|
|
31
33
|
ValidationError: () => ValidationError
|
|
32
34
|
});
|
|
33
35
|
module.exports = __toCommonJS(index_exports);
|
|
34
36
|
|
|
37
|
+
// package.json
|
|
38
|
+
var version = "0.4.4";
|
|
39
|
+
|
|
35
40
|
// src/errors.ts
|
|
36
41
|
var MeterFlowError = class extends Error {
|
|
37
42
|
errorType;
|
|
@@ -72,9 +77,18 @@ var ConflictError = class extends MeterFlowError {
|
|
|
72
77
|
}
|
|
73
78
|
};
|
|
74
79
|
var ValidationError = class extends MeterFlowError {
|
|
75
|
-
|
|
80
|
+
/** Per-field detail from the API. Empty when the 422 carried none. */
|
|
81
|
+
fields;
|
|
82
|
+
constructor(message, requestId, fields = []) {
|
|
76
83
|
super(message, "validation_error", false, requestId, 422);
|
|
77
84
|
this.name = "ValidationError";
|
|
85
|
+
this.fields = fields;
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
var PayloadTooLargeError = class extends MeterFlowError {
|
|
89
|
+
constructor(message, requestId) {
|
|
90
|
+
super(message, "payload_too_large", false, requestId, 413);
|
|
91
|
+
this.name = "PayloadTooLargeError";
|
|
78
92
|
}
|
|
79
93
|
};
|
|
80
94
|
var RateLimitError = class extends MeterFlowError {
|
|
@@ -121,7 +135,7 @@ async function retryable(fn, opts, shouldRetry = () => true) {
|
|
|
121
135
|
}
|
|
122
136
|
|
|
123
137
|
// src/utils/http.ts
|
|
124
|
-
var SDK_VERSION =
|
|
138
|
+
var SDK_VERSION = version;
|
|
125
139
|
function buildUrl(baseUrl, path, query) {
|
|
126
140
|
const url = new URL(path, baseUrl.endsWith("/") ? baseUrl : baseUrl + "/");
|
|
127
141
|
if (query) {
|
|
@@ -145,21 +159,35 @@ function buildHeaders(options, opts) {
|
|
|
145
159
|
}
|
|
146
160
|
return headers;
|
|
147
161
|
}
|
|
162
|
+
function isFieldError(value) {
|
|
163
|
+
return typeof value === "object" && value !== null && typeof value.field === "string" && typeof value.message === "string";
|
|
164
|
+
}
|
|
148
165
|
async function parseErrorBody(response) {
|
|
149
166
|
try {
|
|
150
167
|
const json = await response.json();
|
|
151
|
-
|
|
152
|
-
|
|
168
|
+
const error = json["error"];
|
|
169
|
+
if (typeof error === "object" && error !== null) {
|
|
170
|
+
const body = error;
|
|
171
|
+
const fields = Array.isArray(body["fields"]) ? body["fields"].filter(isFieldError) : [];
|
|
172
|
+
if (typeof body["message"] === "string") {
|
|
173
|
+
const detail = fields.map((f) => `${f.field}: ${f.message}`).join("; ");
|
|
174
|
+
return { message: detail ? `${body["message"]}: ${detail}` : body["message"], fields };
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (typeof json["detail"] === "string") return { message: json["detail"], fields: [] };
|
|
178
|
+
return { message: JSON.stringify(json), fields: [] };
|
|
153
179
|
} catch {
|
|
154
|
-
return response.statusText || `HTTP ${response.status}
|
|
180
|
+
return { message: response.statusText || `HTTP ${response.status}`, fields: [] };
|
|
155
181
|
}
|
|
156
182
|
}
|
|
157
|
-
function mapResponseError(status,
|
|
183
|
+
function mapResponseError(status, parsed, requestId, response) {
|
|
184
|
+
const { message, fields } = parsed;
|
|
158
185
|
if (status === 401 || status === 403) return new AuthError(message, requestId, status);
|
|
159
186
|
if (status === 402) return new InsufficientCreditsError(message, requestId);
|
|
160
187
|
if (status === 404) return new NotFoundError(message, requestId);
|
|
161
188
|
if (status === 409) return new ConflictError(message, requestId);
|
|
162
|
-
if (status ===
|
|
189
|
+
if (status === 413) return new PayloadTooLargeError(message, requestId);
|
|
190
|
+
if (status === 422) return new ValidationError(message, requestId, fields);
|
|
163
191
|
if (status === 429) {
|
|
164
192
|
const retryAfterRaw = response.headers.get("Retry-After");
|
|
165
193
|
const retryAfter = retryAfterRaw != null ? parseInt(retryAfterRaw, 10) : void 0;
|
|
@@ -181,8 +209,8 @@ async function httpRequest(options, method, path, opts) {
|
|
|
181
209
|
const response = await options.fetch(url, fetchInit);
|
|
182
210
|
const requestId = response.headers.get("x-request-id") ?? void 0;
|
|
183
211
|
if (!response.ok) {
|
|
184
|
-
const
|
|
185
|
-
throw mapResponseError(response.status,
|
|
212
|
+
const parsed = await parseErrorBody(response);
|
|
213
|
+
throw mapResponseError(response.status, parsed, requestId, response);
|
|
186
214
|
}
|
|
187
215
|
if (response.status === 204 || response.headers.get("content-length") === "0") {
|
|
188
216
|
return void 0;
|
|
@@ -225,6 +253,7 @@ var CreditsResource = class {
|
|
|
225
253
|
};
|
|
226
254
|
|
|
227
255
|
// src/resources/usage.ts
|
|
256
|
+
var MAX_BATCH_EVENTS = 500;
|
|
228
257
|
var UsageResource = class {
|
|
229
258
|
constructor(client) {
|
|
230
259
|
this.client = client;
|
|
@@ -235,7 +264,13 @@ var UsageResource = class {
|
|
|
235
264
|
...opts?.idempotencyKey !== void 0 && { idempotencyKey: opts.idempotencyKey }
|
|
236
265
|
});
|
|
237
266
|
}
|
|
267
|
+
/** Record up to `MAX_BATCH_EVENTS` events in one request. Larger arrays are rejected locally, never sent. */
|
|
238
268
|
recordBatch(events, opts) {
|
|
269
|
+
if (events.length > MAX_BATCH_EVENTS) {
|
|
270
|
+
return Promise.reject(
|
|
271
|
+
new PayloadTooLargeError(`A batch may contain at most ${MAX_BATCH_EVENTS} events; this one has ${events.length}. Split it.`)
|
|
272
|
+
);
|
|
273
|
+
}
|
|
239
274
|
return this.client.request("POST", "usage/events/batch", {
|
|
240
275
|
body: { events },
|
|
241
276
|
...opts?.idempotencyKey !== void 0 && { idempotencyKey: opts.idempotencyKey }
|
|
@@ -338,9 +373,11 @@ var MeterFlow = class {
|
|
|
338
373
|
AuthError,
|
|
339
374
|
ConflictError,
|
|
340
375
|
InsufficientCreditsError,
|
|
376
|
+
MAX_BATCH_EVENTS,
|
|
341
377
|
MeterFlow,
|
|
342
378
|
MeterFlowError,
|
|
343
379
|
NotFoundError,
|
|
380
|
+
PayloadTooLargeError,
|
|
344
381
|
RateLimitError,
|
|
345
382
|
ServerError,
|
|
346
383
|
ValidationError
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// package.json
|
|
2
|
+
var version = "0.4.4";
|
|
3
|
+
|
|
1
4
|
// src/errors.ts
|
|
2
5
|
var MeterFlowError = class extends Error {
|
|
3
6
|
errorType;
|
|
@@ -38,9 +41,18 @@ var ConflictError = class extends MeterFlowError {
|
|
|
38
41
|
}
|
|
39
42
|
};
|
|
40
43
|
var ValidationError = class extends MeterFlowError {
|
|
41
|
-
|
|
44
|
+
/** Per-field detail from the API. Empty when the 422 carried none. */
|
|
45
|
+
fields;
|
|
46
|
+
constructor(message, requestId, fields = []) {
|
|
42
47
|
super(message, "validation_error", false, requestId, 422);
|
|
43
48
|
this.name = "ValidationError";
|
|
49
|
+
this.fields = fields;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
var PayloadTooLargeError = class extends MeterFlowError {
|
|
53
|
+
constructor(message, requestId) {
|
|
54
|
+
super(message, "payload_too_large", false, requestId, 413);
|
|
55
|
+
this.name = "PayloadTooLargeError";
|
|
44
56
|
}
|
|
45
57
|
};
|
|
46
58
|
var RateLimitError = class extends MeterFlowError {
|
|
@@ -87,7 +99,7 @@ async function retryable(fn, opts, shouldRetry = () => true) {
|
|
|
87
99
|
}
|
|
88
100
|
|
|
89
101
|
// src/utils/http.ts
|
|
90
|
-
var SDK_VERSION =
|
|
102
|
+
var SDK_VERSION = version;
|
|
91
103
|
function buildUrl(baseUrl, path, query) {
|
|
92
104
|
const url = new URL(path, baseUrl.endsWith("/") ? baseUrl : baseUrl + "/");
|
|
93
105
|
if (query) {
|
|
@@ -111,21 +123,35 @@ function buildHeaders(options, opts) {
|
|
|
111
123
|
}
|
|
112
124
|
return headers;
|
|
113
125
|
}
|
|
126
|
+
function isFieldError(value) {
|
|
127
|
+
return typeof value === "object" && value !== null && typeof value.field === "string" && typeof value.message === "string";
|
|
128
|
+
}
|
|
114
129
|
async function parseErrorBody(response) {
|
|
115
130
|
try {
|
|
116
131
|
const json = await response.json();
|
|
117
|
-
|
|
118
|
-
|
|
132
|
+
const error = json["error"];
|
|
133
|
+
if (typeof error === "object" && error !== null) {
|
|
134
|
+
const body = error;
|
|
135
|
+
const fields = Array.isArray(body["fields"]) ? body["fields"].filter(isFieldError) : [];
|
|
136
|
+
if (typeof body["message"] === "string") {
|
|
137
|
+
const detail = fields.map((f) => `${f.field}: ${f.message}`).join("; ");
|
|
138
|
+
return { message: detail ? `${body["message"]}: ${detail}` : body["message"], fields };
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (typeof json["detail"] === "string") return { message: json["detail"], fields: [] };
|
|
142
|
+
return { message: JSON.stringify(json), fields: [] };
|
|
119
143
|
} catch {
|
|
120
|
-
return response.statusText || `HTTP ${response.status}
|
|
144
|
+
return { message: response.statusText || `HTTP ${response.status}`, fields: [] };
|
|
121
145
|
}
|
|
122
146
|
}
|
|
123
|
-
function mapResponseError(status,
|
|
147
|
+
function mapResponseError(status, parsed, requestId, response) {
|
|
148
|
+
const { message, fields } = parsed;
|
|
124
149
|
if (status === 401 || status === 403) return new AuthError(message, requestId, status);
|
|
125
150
|
if (status === 402) return new InsufficientCreditsError(message, requestId);
|
|
126
151
|
if (status === 404) return new NotFoundError(message, requestId);
|
|
127
152
|
if (status === 409) return new ConflictError(message, requestId);
|
|
128
|
-
if (status ===
|
|
153
|
+
if (status === 413) return new PayloadTooLargeError(message, requestId);
|
|
154
|
+
if (status === 422) return new ValidationError(message, requestId, fields);
|
|
129
155
|
if (status === 429) {
|
|
130
156
|
const retryAfterRaw = response.headers.get("Retry-After");
|
|
131
157
|
const retryAfter = retryAfterRaw != null ? parseInt(retryAfterRaw, 10) : void 0;
|
|
@@ -147,8 +173,8 @@ async function httpRequest(options, method, path, opts) {
|
|
|
147
173
|
const response = await options.fetch(url, fetchInit);
|
|
148
174
|
const requestId = response.headers.get("x-request-id") ?? void 0;
|
|
149
175
|
if (!response.ok) {
|
|
150
|
-
const
|
|
151
|
-
throw mapResponseError(response.status,
|
|
176
|
+
const parsed = await parseErrorBody(response);
|
|
177
|
+
throw mapResponseError(response.status, parsed, requestId, response);
|
|
152
178
|
}
|
|
153
179
|
if (response.status === 204 || response.headers.get("content-length") === "0") {
|
|
154
180
|
return void 0;
|
|
@@ -191,6 +217,7 @@ var CreditsResource = class {
|
|
|
191
217
|
};
|
|
192
218
|
|
|
193
219
|
// src/resources/usage.ts
|
|
220
|
+
var MAX_BATCH_EVENTS = 500;
|
|
194
221
|
var UsageResource = class {
|
|
195
222
|
constructor(client) {
|
|
196
223
|
this.client = client;
|
|
@@ -201,7 +228,13 @@ var UsageResource = class {
|
|
|
201
228
|
...opts?.idempotencyKey !== void 0 && { idempotencyKey: opts.idempotencyKey }
|
|
202
229
|
});
|
|
203
230
|
}
|
|
231
|
+
/** Record up to `MAX_BATCH_EVENTS` events in one request. Larger arrays are rejected locally, never sent. */
|
|
204
232
|
recordBatch(events, opts) {
|
|
233
|
+
if (events.length > MAX_BATCH_EVENTS) {
|
|
234
|
+
return Promise.reject(
|
|
235
|
+
new PayloadTooLargeError(`A batch may contain at most ${MAX_BATCH_EVENTS} events; this one has ${events.length}. Split it.`)
|
|
236
|
+
);
|
|
237
|
+
}
|
|
205
238
|
return this.client.request("POST", "usage/events/batch", {
|
|
206
239
|
body: { events },
|
|
207
240
|
...opts?.idempotencyKey !== void 0 && { idempotencyKey: opts.idempotencyKey }
|
|
@@ -303,9 +336,11 @@ export {
|
|
|
303
336
|
AuthError,
|
|
304
337
|
ConflictError,
|
|
305
338
|
InsufficientCreditsError,
|
|
339
|
+
MAX_BATCH_EVENTS,
|
|
306
340
|
MeterFlow,
|
|
307
341
|
MeterFlowError,
|
|
308
342
|
NotFoundError,
|
|
343
|
+
PayloadTooLargeError,
|
|
309
344
|
RateLimitError,
|
|
310
345
|
ServerError,
|
|
311
346
|
ValidationError
|
|
@@ -11,10 +11,19 @@ export interface SummaryQuery {
|
|
|
11
11
|
from_?: string;
|
|
12
12
|
to?: string;
|
|
13
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* The API refuses a batch above this with 413. The SDK checks it before sending so an oversized
|
|
16
|
+
* batch fails instantly, offline, with the same `PayloadTooLargeError` — and it does NOT split
|
|
17
|
+
* the batch for you: one call is one request with one idempotency key, and silently turning it
|
|
18
|
+
* into N requests would leave partial success indistinguishable from failure. Chunk at the call
|
|
19
|
+
* site, where you can pick a key per chunk.
|
|
20
|
+
*/
|
|
21
|
+
export declare const MAX_BATCH_EVENTS = 500;
|
|
14
22
|
export declare class UsageResource {
|
|
15
23
|
private readonly client;
|
|
16
24
|
constructor(client: MeterFlow);
|
|
17
25
|
record(body: UsageEventRequest, opts?: RecordOptions): Promise<UsageEventResponse>;
|
|
26
|
+
/** Record up to `MAX_BATCH_EVENTS` events in one request. Larger arrays are rejected locally, never sent. */
|
|
18
27
|
recordBatch(events: UsageEventRequest[], opts?: RecordOptions): Promise<UsageEventResponse[]>;
|
|
19
28
|
summary(customerId: string, query?: SummaryQuery): Promise<UsageSummaryResponse>;
|
|
20
29
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usage.d.ts","sourceRoot":"","sources":["../../../src/resources/usage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"usage.d.ts","sourceRoot":"","sources":["../../../src/resources/usage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAE3C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,KAAK,iBAAiB,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,mBAAmB,CAAC,CAAC;AACpE,KAAK,kBAAkB,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,oBAAoB,CAAC,CAAC;AACtE,KAAK,oBAAoB,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,sBAAsB,CAAC,CAAC;AAE1E,MAAM,WAAW,aAAa;IAC5B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,MAAM,CAAC;AAEpC,qBAAa,aAAa;IACZ,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,SAAS;IAE9C,MAAM,CAAC,IAAI,EAAE,iBAAiB,EAAE,IAAI,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAOlF,6GAA6G;IAC7G,WAAW,CAAC,MAAM,EAAE,iBAAiB,EAAE,EAAE,IAAI,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC;IAY7F,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,oBAAoB,CAAC;CAKjF"}
|
package/dist/utils/http.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../../src/utils/http.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../../src/utils/http.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAiGlE,wBAAsB,WAAW,CAAC,CAAC,EACjC,OAAO,EAAE,QAAQ,CAAC,gBAAgB,CAAC,EACnC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,cAAc,GACpB,OAAO,CAAC,CAAC,CAAC,CAgCZ"}
|