meterflow 0.4.2 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -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
- | `ValidationError` | 422 | `validation_error` | no |
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 {
@@ -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;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"}
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.3";
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
- constructor(message, requestId) {
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 = "0.2.0";
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
- if (typeof json["detail"] === "string") return json["detail"];
152
- return JSON.stringify(json);
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, message, requestId, response) {
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 === 422) return new ValidationError(message, requestId);
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 message = await parseErrorBody(response);
185
- throw mapResponseError(response.status, message, requestId, response);
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
@@ -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,GACZ,MAAM,UAAU,CAAC"}
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.3";
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
- constructor(message, requestId) {
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 = "0.2.0";
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
- if (typeof json["detail"] === "string") return json["detail"];
152
- return JSON.stringify(json);
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, message, requestId, response) {
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 === 422) return new ValidationError(message, requestId);
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 message = await parseErrorBody(response);
185
- throw mapResponseError(response.status, message, requestId, response);
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.3";
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
- constructor(message, requestId) {
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 = "0.2.0";
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
- if (typeof json["detail"] === "string") return json["detail"];
118
- return JSON.stringify(json);
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, message, requestId, response) {
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 === 422) return new ValidationError(message, requestId);
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 message = await parseErrorBody(response);
151
- throw mapResponseError(response.status, message, requestId, response);
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;AAC3C,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,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,WAAW,CAAC,MAAM,EAAE,iBAAiB,EAAE,EAAE,IAAI,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC;IAO7F,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,oBAAoB,CAAC;CAKjF"}
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"}
@@ -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;AAiElE,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"}
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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "meterflow",
3
- "version": "0.4.2",
3
+ "version": "0.4.3",
4
4
  "description": "Official Node.js SDK for MeterFlow — usage-based billing, credit management, and metering.",
5
5
  "license": "MIT",
6
6
  "author": "MeterFlow",