diff --git a/.release-please-manifest.json b/.release-please-manifest.json index a748fa3..4f5d6fd 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.0-beta.4" + ".": "0.1.0-beta.5" } diff --git a/.stats.yml b/.stats.yml index 169fb48..a0c4d47 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,2 +1,2 @@ -configured_endpoints: 90 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/metronome%2Fmetronome-c2288c6a60ac4804016ea90592c280d11c7d5b52b63752bacf3519441595bcfb.yml +configured_endpoints: 91 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/metronome%2Fmetronome-c4ec65355a30c07306ddff2c4a97411c2eb631a878583ce8bdd876a4fe2a5c96.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index 71c317c..96dfb7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## 0.1.0-beta.5 (2024-09-20) + +Full Changelog: [v0.1.0-beta.4...v0.1.0-beta.5](https://github.com/Metronome-Industries/metronome-node/compare/v0.1.0-beta.4...v0.1.0-beta.5) + +### Features + +* **api:** OpenAPI spec update via Stainless API ([#109](https://github.com/Metronome-Industries/metronome-node/issues/109)) ([015b008](https://github.com/Metronome-Industries/metronome-node/commit/015b008fc62450e29de68671b932246d80328101)) +* **client:** send retry count header ([#107](https://github.com/Metronome-Industries/metronome-node/issues/107)) ([6992cf7](https://github.com/Metronome-Industries/metronome-node/commit/6992cf75364fb700caa3b9148dbb905aa109671d)) + ## 0.1.0-beta.4 (2024-09-19) Full Changelog: [v0.1.0-beta.3...v0.1.0-beta.4](https://github.com/Metronome-Industries/metronome-node/compare/v0.1.0-beta.3...v0.1.0-beta.4) diff --git a/api.md b/api.md index 1dc0dfb..bf0bbd3 100644 --- a/api.md +++ b/api.md @@ -133,12 +133,14 @@ Types: - Invoice - InvoiceRetrieveResponse - InvoiceAddChargeResponse +- InvoiceListBreakdownsResponse Methods: - client.customers.invoices.retrieve(customerId, invoiceId, { ...params }) -> InvoiceRetrieveResponse - client.customers.invoices.list(customerId, { ...params }) -> InvoicesCursorPage - client.customers.invoices.addCharge(customerId, { ...params }) -> InvoiceAddChargeResponse +- client.customers.invoices.listBreakdowns(customerId, { ...params }) -> InvoiceListBreakdownsResponsesCursorPage ## BillingConfig diff --git a/package.json b/package.json index a960c10..c461ff0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@metronome/sdk", - "version": "0.1.0-beta.4", + "version": "0.1.0-beta.5", "description": "The official TypeScript library for the Metronome API", "author": "Metronome ", "types": "dist/index.d.ts", diff --git a/src/core.ts b/src/core.ts index f416565..0123c60 100644 --- a/src/core.ts +++ b/src/core.ts @@ -274,7 +274,10 @@ export abstract class APIClient { return null; } - buildRequest(options: FinalRequestOptions): { req: RequestInit; url: string; timeout: number } { + buildRequest( + options: FinalRequestOptions, + { retryCount = 0 }: { retryCount?: number } = {}, + ): { req: RequestInit; url: string; timeout: number } { const { method, path, query, headers: headers = {} } = options; const body = @@ -306,7 +309,7 @@ export abstract class APIClient { headers[this.idempotencyHeader] = options.idempotencyKey; } - const reqHeaders = this.buildHeaders({ options, headers, contentLength }); + const reqHeaders = this.buildHeaders({ options, headers, contentLength, retryCount }); const req: RequestInit = { method, @@ -325,10 +328,12 @@ export abstract class APIClient { options, headers, contentLength, + retryCount, }: { options: FinalRequestOptions; headers: Record; contentLength: string | null | undefined; + retryCount: number; }): Record { const reqHeaders: Record = {}; if (contentLength) { @@ -344,6 +349,8 @@ export abstract class APIClient { delete reqHeaders['content-type']; } + reqHeaders['x-stainless-retry-count'] = String(retryCount); + this.validateHeaders(reqHeaders, headers); return reqHeaders; @@ -395,13 +402,14 @@ export abstract class APIClient { retriesRemaining: number | null, ): Promise { const options = await optionsInput; + const maxRetries = options.maxRetries ?? this.maxRetries; if (retriesRemaining == null) { - retriesRemaining = options.maxRetries ?? this.maxRetries; + retriesRemaining = maxRetries; } await this.prepareOptions(options); - const { req, url, timeout } = this.buildRequest(options); + const { req, url, timeout } = this.buildRequest(options, { retryCount: maxRetries - retriesRemaining }); await this.prepareRequest(req, { url, options }); diff --git a/src/resources/customers/customers.ts b/src/resources/customers/customers.ts index 585aa04..d803706 100644 --- a/src/resources/customers/customers.ts +++ b/src/resources/customers/customers.ts @@ -530,10 +530,13 @@ export namespace Customers { export import Invoice = InvoicesAPI.Invoice; export import InvoiceRetrieveResponse = InvoicesAPI.InvoiceRetrieveResponse; export import InvoiceAddChargeResponse = InvoicesAPI.InvoiceAddChargeResponse; + export import InvoiceListBreakdownsResponse = InvoicesAPI.InvoiceListBreakdownsResponse; export import InvoicesCursorPage = InvoicesAPI.InvoicesCursorPage; + export import InvoiceListBreakdownsResponsesCursorPage = InvoicesAPI.InvoiceListBreakdownsResponsesCursorPage; export import InvoiceRetrieveParams = InvoicesAPI.InvoiceRetrieveParams; export import InvoiceListParams = InvoicesAPI.InvoiceListParams; export import InvoiceAddChargeParams = InvoicesAPI.InvoiceAddChargeParams; + export import InvoiceListBreakdownsParams = InvoicesAPI.InvoiceListBreakdownsParams; export import BillingConfig = BillingConfigAPI.BillingConfig; export import BillingConfigRetrieveResponse = BillingConfigAPI.BillingConfigRetrieveResponse; export import BillingConfigCreateParams = BillingConfigAPI.BillingConfigCreateParams; diff --git a/src/resources/customers/index.ts b/src/resources/customers/index.ts index 376c2e2..e914e8b 100644 --- a/src/resources/customers/index.ts +++ b/src/resources/customers/index.ts @@ -54,10 +54,13 @@ export { Invoice, InvoiceRetrieveResponse, InvoiceAddChargeResponse, + InvoiceListBreakdownsResponse, InvoiceRetrieveParams, InvoiceListParams, InvoiceAddChargeParams, + InvoiceListBreakdownsParams, InvoicesCursorPage, + InvoiceListBreakdownsResponsesCursorPage, Invoices, } from './invoices'; export { diff --git a/src/resources/customers/invoices.ts b/src/resources/customers/invoices.ts index 7c65c85..379f35d 100644 --- a/src/resources/customers/invoices.ts +++ b/src/resources/customers/invoices.ts @@ -68,10 +68,28 @@ export class Invoices extends APIResource { ): Core.APIPromise { return this._client.post(`/customers/${customerId}/addCharge`, { body, ...options }); } + + /** + * List daily or hourly breakdown invoices for a given customer, optionally + * filtered by status, date range, and/or credit type. + */ + listBreakdowns( + customerId: string, + query: InvoiceListBreakdownsParams, + options?: Core.RequestOptions, + ): Core.PagePromise { + return this._client.getAPIList( + `/customers/${customerId}/invoices/breakdowns`, + InvoiceListBreakdownsResponsesCursorPage, + { query, ...options }, + ); + } } export class InvoicesCursorPage extends CursorPage {} +export class InvoiceListBreakdownsResponsesCursorPage extends CursorPage {} + export interface Invoice { id: string; @@ -477,6 +495,12 @@ export interface InvoiceRetrieveResponse { export interface InvoiceAddChargeResponse {} +export interface InvoiceListBreakdownsResponse extends Invoice { + breakdown_end_timestamp: string; + + breakdown_start_timestamp: string; +} + export interface InvoiceRetrieveParams { /** * If set, all zero quantity line items will be filtered out of the response @@ -548,12 +572,55 @@ export interface InvoiceAddChargeParams { quantity: number; } +export interface InvoiceListBreakdownsParams extends CursorPageParams { + /** + * RFC 3339 timestamp. Breakdowns will only be returned for time windows that end + * on or before this time. + */ + ending_before: string; + + /** + * RFC 3339 timestamp. Breakdowns will only be returned for time windows that start + * on or after this time. + */ + starting_on: string; + + /** + * Only return invoices for the specified credit type + */ + credit_type_id?: string; + + /** + * If set, all zero quantity line items will be filtered out of the response + */ + skip_zero_qty_line_items?: boolean; + + /** + * Invoice sort order by issued_at, e.g. date_asc or date_desc. Defaults to + * date_asc. + */ + sort?: 'date_asc' | 'date_desc'; + + /** + * Invoice status, e.g. DRAFT or FINALIZED + */ + status?: string; + + /** + * The granularity of the breakdowns to return. Defaults to day. + */ + window_size?: 'HOUR' | 'DAY'; +} + export namespace Invoices { export import Invoice = InvoicesAPI.Invoice; export import InvoiceRetrieveResponse = InvoicesAPI.InvoiceRetrieveResponse; export import InvoiceAddChargeResponse = InvoicesAPI.InvoiceAddChargeResponse; + export import InvoiceListBreakdownsResponse = InvoicesAPI.InvoiceListBreakdownsResponse; export import InvoicesCursorPage = InvoicesAPI.InvoicesCursorPage; + export import InvoiceListBreakdownsResponsesCursorPage = InvoicesAPI.InvoiceListBreakdownsResponsesCursorPage; export import InvoiceRetrieveParams = InvoicesAPI.InvoiceRetrieveParams; export import InvoiceListParams = InvoicesAPI.InvoiceListParams; export import InvoiceAddChargeParams = InvoicesAPI.InvoiceAddChargeParams; + export import InvoiceListBreakdownsParams = InvoicesAPI.InvoiceListBreakdownsParams; } diff --git a/src/version.ts b/src/version.ts index 3bbfee3..509a910 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const VERSION = '0.1.0-beta.4'; // x-release-please-version +export const VERSION = '0.1.0-beta.5'; // x-release-please-version diff --git a/tests/api-resources/customers/invoices.test.ts b/tests/api-resources/customers/invoices.test.ts index 3128902..abed639 100644 --- a/tests/api-resources/customers/invoices.test.ts +++ b/tests/api-resources/customers/invoices.test.ts @@ -114,4 +114,32 @@ describe('resource invoices', () => { quantity: 1, }); }); + + test('listBreakdowns: only required params', async () => { + const responsePromise = client.customers.invoices.listBreakdowns('d7abd0cd-4ae9-4db7-8676-e986a4ebd8dc', { + ending_before: '2019-12-27T18:11:19.117Z', + starting_on: '2019-12-27T18:11:19.117Z', + }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + test('listBreakdowns: required and optional params', async () => { + const response = await client.customers.invoices.listBreakdowns('d7abd0cd-4ae9-4db7-8676-e986a4ebd8dc', { + ending_before: '2019-12-27T18:11:19.117Z', + starting_on: '2019-12-27T18:11:19.117Z', + credit_type_id: 'credit_type_id', + limit: 1, + next_page: 'next_page', + skip_zero_qty_line_items: true, + sort: 'date_asc', + status: 'status', + window_size: 'HOUR', + }); + }); }); diff --git a/tests/index.test.ts b/tests/index.test.ts index 327a9f5..3fec531 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -247,6 +247,31 @@ describe('retries', () => { expect(count).toEqual(3); }); + test('retry count header', async () => { + let count = 0; + let capturedRequest: RequestInit | undefined; + const testFetch = async (url: RequestInfo, init: RequestInit = {}): Promise => { + count++; + if (count <= 2) { + return new Response(undefined, { + status: 429, + headers: { + 'Retry-After': '0.1', + }, + }); + } + capturedRequest = init; + return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); + }; + + const client = new Metronome({ bearerToken: 'My Bearer Token', fetch: testFetch, maxRetries: 4 }); + + expect(await client.request({ path: '/foo', method: 'get' })).toEqual({ a: 1 }); + + expect((capturedRequest!.headers as Headers)['x-stainless-retry-count']).toEqual('2'); + expect(count).toEqual(3); + }); + test('retry on 429 with retry-after', async () => { let count = 0; const testFetch = async (url: RequestInfo, { signal }: RequestInit = {}): Promise => {