From 416e38b891b03e3093efec95f641a2927d954bbe Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 18:28:20 +0000 Subject: [PATCH 1/4] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index b78433dccc..0a70b93fc6 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 1749 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cloudflare%2Fcloudflare-b15b44e0efd207de48e7e74e742b0b4b190c74f12a941a1a0ef59a51656a5224.yml openapi_spec_hash: 83243c9ee06f88d0fa91e9b185d8a42e -config_hash: d0ab46f06dbe6f6e33d86a3ede15ac44 +config_hash: f3028048c6dc3559115fdd749755dee2 From f3996568d1d92a7f92956aa52c1e7d167e7afa59 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 21:11:21 +0000 Subject: [PATCH 2/4] feat(client): add support for endpoint-specific base URLs --- src/core.ts | 15 +++++++++++---- src/index.ts | 8 ++++++++ tests/index.test.ts | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 4 deletions(-) diff --git a/src/core.ts b/src/core.ts index c0dc76418c..6d59eacdf0 100644 --- a/src/core.ts +++ b/src/core.ts @@ -171,6 +171,7 @@ export class APIPromise extends Promise { export abstract class APIClient { baseURL: string; apiVersion: string; + #baseURLOverridden: boolean; maxRetries: number; timeout: number; httpAgent: Agent | undefined; @@ -181,6 +182,7 @@ export abstract class APIClient { constructor({ baseURL, apiVersion, + baseURLOverridden, maxRetries = 2, timeout = 60000, // 1 minute httpAgent, @@ -188,6 +190,7 @@ export abstract class APIClient { }: { baseURL: string; apiVersion: string; + baseURLOverridden: boolean; maxRetries?: number | undefined; timeout: number | undefined; httpAgent: Agent | undefined; @@ -195,6 +198,7 @@ export abstract class APIClient { }) { this.baseURL = baseURL; this.apiVersion = apiVersion; + this.#baseURLOverridden = baseURLOverridden; this.maxRetries = validatePositiveInteger('maxRetries', maxRetries); this.timeout = validatePositiveInteger('timeout', timeout); this.httpAgent = httpAgent; @@ -305,7 +309,7 @@ export abstract class APIClient { { retryCount = 0 }: { retryCount?: number } = {}, ): { req: RequestInit; url: string; timeout: number } { const options = { ...inputOptions }; - const { method, path, query, headers: headers = {} } = options; + const { method, path, query, defaultBaseURL, headers: headers = {} } = options; const body = ArrayBuffer.isView(options.body) || (options.__binaryRequest && typeof options.body === 'string') ? @@ -315,7 +319,7 @@ export abstract class APIClient { : null; const contentLength = this.calculateContentLength(body); - const url = this.buildURL(path!, query); + const url = this.buildURL(path!, query, defaultBaseURL); if ('timeout' in options) validatePositiveInteger('timeout', options.timeout); options.timeout = options.timeout ?? this.timeout; const httpAgent = options.httpAgent ?? this.httpAgent ?? getDefaultAgent(url); @@ -508,11 +512,12 @@ export abstract class APIClient { return new PagePromise(this, request, Page); } - buildURL(path: string, query: Req | null | undefined): string { + buildURL(path: string, query: Req | null | undefined, defaultBaseURL?: string | undefined): string { + const baseURL = (!this.#baseURLOverridden && defaultBaseURL) || this.baseURL; const url = isAbsoluteURL(path) ? new URL(path) - : new URL(this.baseURL + (this.baseURL.endsWith('/') && path.startsWith('/') ? path.slice(1) : path)); + : new URL(baseURL + (baseURL.endsWith('/') && path.startsWith('/') ? path.slice(1) : path)); const defaultQuery = this.defaultQuery(); if (!isEmptyObj(defaultQuery)) { @@ -801,6 +806,7 @@ export type RequestOptions< query?: Req | undefined; body?: Req | null | undefined; headers?: Headers | undefined; + defaultBaseURL?: string | undefined; maxRetries?: number; stream?: boolean | undefined; @@ -824,6 +830,7 @@ const requestOptionsKeys: KeysEnum = { query: true, body: true, headers: true, + defaultBaseURL: true, maxRetries: true, stream: true, diff --git a/src/index.ts b/src/index.ts index 015e92a036..b438ddbb2b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -246,6 +246,7 @@ export class Cloudflare extends Core.APIClient { super({ baseURL: options.baseURL!, apiVersion: options.apiVersion!, + baseURLOverridden: baseURL ? baseURL !== 'https://api.cloudflare.com/client/v4' : false, timeout: options.timeout ?? 60000 /* 1 minute */, httpAgent: options.httpAgent, maxRetries: options.maxRetries, @@ -356,6 +357,13 @@ export class Cloudflare extends Core.APIClient { pipelines: API.Pipelines = new API.Pipelines(this); schemaValidation: API.SchemaValidation = new API.SchemaValidation(this); + /** + * Check whether the base URL is set to its default. + */ + #baseURLOverridden(): boolean { + return this.baseURL !== 'https://api.cloudflare.com/client/v4'; + } + protected override defaultQuery(): Core.DefaultQuery | undefined { return this._options.defaultQuery; } diff --git a/tests/index.test.ts b/tests/index.test.ts index a7b041721a..00507b9e00 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -214,6 +214,38 @@ describe('instantiate client', () => { }); expect(client.baseURL).toEqual('https://api.cloudflare.com/client/v4'); }); + + test('in request options', () => { + const client = new Cloudflare({ + apiKey: '144c9defac04969c7bfad8efaa8ea194', + apiEmail: 'user@example.com', + }); + expect(client.buildURL('/foo', null, 'http://localhost:5000/option')).toEqual( + 'http://localhost:5000/option/foo', + ); + }); + + test('in request options overridden by client options', () => { + const client = new Cloudflare({ + apiKey: '144c9defac04969c7bfad8efaa8ea194', + apiEmail: 'user@example.com', + baseURL: 'http://localhost:5000/client', + }); + expect(client.buildURL('/foo', null, 'http://localhost:5000/option')).toEqual( + 'http://localhost:5000/client/foo', + ); + }); + + test('in request options overridden by env variable', () => { + process.env['CLOUDFLARE_BASE_URL'] = 'http://localhost:5000/env'; + const client = new Cloudflare({ + apiKey: '144c9defac04969c7bfad8efaa8ea194', + apiEmail: 'user@example.com', + }); + expect(client.buildURL('/foo', null, 'http://localhost:5000/option')).toEqual( + 'http://localhost:5000/env/foo', + ); + }); }); test('maxRetries option is correctly set', () => { From f0ff0b7456757c5c176a4c51b792e44ddb823285 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 20:03:37 +0000 Subject: [PATCH 3/4] fix(api): Update zone subscription paths --- .stats.yml | 4 +- api.md | 6 + src/resources/zones/index.ts | 7 +- src/resources/zones/subscriptions.ts | 118 +++++++++++++++++- src/resources/zones/zones.ts | 14 ++- .../api-resources/zones/subscriptions.test.ts | 85 +++++++++++++ 6 files changed, 228 insertions(+), 6 deletions(-) create mode 100644 tests/api-resources/zones/subscriptions.test.ts diff --git a/.stats.yml b/.stats.yml index 0a70b93fc6..590d127c2c 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 1749 +configured_endpoints: 1752 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cloudflare%2Fcloudflare-b15b44e0efd207de48e7e74e742b0b4b190c74f12a941a1a0ef59a51656a5224.yml openapi_spec_hash: 83243c9ee06f88d0fa91e9b185d8a42e -config_hash: f3028048c6dc3559115fdd749755dee2 +config_hash: 8601d43fd5ccaf9e3d08f26748a5a63a diff --git a/api.md b/api.md index 0cc019c7e9..521e1037c9 100644 --- a/api.md +++ b/api.md @@ -392,6 +392,12 @@ Methods: ## Subscriptions +Methods: + +- client.zones.subscriptions.create({ ...params }) -> Subscription +- client.zones.subscriptions.update({ ...params }) -> Subscription +- client.zones.subscriptions.get({ ...params }) -> Subscription + ## Plans Types: diff --git a/src/resources/zones/index.ts b/src/resources/zones/index.ts index 0f3224682b..dde3861963 100644 --- a/src/resources/zones/index.ts +++ b/src/resources/zones/index.ts @@ -91,5 +91,10 @@ export { type SettingEditParams, type SettingGetParams, } from './settings'; -export { Subscriptions } from './subscriptions'; +export { + Subscriptions, + type SubscriptionCreateParams, + type SubscriptionUpdateParams, + type SubscriptionGetParams, +} from './subscriptions'; export { Zones } from './zones'; diff --git a/src/resources/zones/subscriptions.ts b/src/resources/zones/subscriptions.ts index 42d6ea9ea6..893d08111d 100644 --- a/src/resources/zones/subscriptions.ts +++ b/src/resources/zones/subscriptions.ts @@ -1,5 +1,121 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { APIResource } from '../../resource'; +import * as Core from '../../core'; +import * as Shared from '../shared'; -export class Subscriptions extends APIResource {} +export class Subscriptions extends APIResource { + /** + * Create a zone subscription, either plan or add-ons. + * + * @example + * ```ts + * const subscription = + * await client.zones.subscriptions.create({ + * zone_id: '506e3185e9c882d175a2d0cb0093d9f2', + * }); + * ``` + */ + create( + params: SubscriptionCreateParams, + options?: Core.RequestOptions, + ): Core.APIPromise { + const { zone_id, ...body } = params; + return ( + this._client.post(`/zones/${zone_id}/subscription`, { body, ...options }) as Core.APIPromise<{ + result: Shared.Subscription; + }> + )._thenUnwrap((obj) => obj.result); + } + + /** + * Updates zone subscriptions, either plan or add-ons. + * + * @example + * ```ts + * const subscription = + * await client.zones.subscriptions.update({ + * zone_id: '506e3185e9c882d175a2d0cb0093d9f2', + * }); + * ``` + */ + update( + params: SubscriptionUpdateParams, + options?: Core.RequestOptions, + ): Core.APIPromise { + const { zone_id, ...body } = params; + return ( + this._client.put(`/zones/${zone_id}/subscription`, { body, ...options }) as Core.APIPromise<{ + result: Shared.Subscription; + }> + )._thenUnwrap((obj) => obj.result); + } + + /** + * Lists zone subscription details. + * + * @example + * ```ts + * const subscription = await client.zones.subscriptions.get({ + * zone_id: '506e3185e9c882d175a2d0cb0093d9f2', + * }); + * ``` + */ + get(params: SubscriptionGetParams, options?: Core.RequestOptions): Core.APIPromise { + const { zone_id } = params; + return ( + this._client.get(`/zones/${zone_id}/subscription`, options) as Core.APIPromise<{ + result: Shared.Subscription; + }> + )._thenUnwrap((obj) => obj.result); + } +} + +export interface SubscriptionCreateParams { + /** + * Path param: Subscription identifier tag. + */ + zone_id: string; + + /** + * Body param: How often the subscription is renewed automatically. + */ + frequency?: 'weekly' | 'monthly' | 'quarterly' | 'yearly'; + + /** + * Body param: The rate plan applied to the subscription. + */ + rate_plan?: Shared.RatePlanParam; +} + +export interface SubscriptionUpdateParams { + /** + * Path param: Subscription identifier tag. + */ + zone_id: string; + + /** + * Body param: How often the subscription is renewed automatically. + */ + frequency?: 'weekly' | 'monthly' | 'quarterly' | 'yearly'; + + /** + * Body param: The rate plan applied to the subscription. + */ + rate_plan?: Shared.RatePlanParam; +} + +export interface SubscriptionGetParams { + /** + * Subscription identifier tag. + */ + zone_id: string; +} + +export declare namespace Subscriptions { + export { + type SubscriptionCreateParams as SubscriptionCreateParams, + type SubscriptionUpdateParams as SubscriptionUpdateParams, + type SubscriptionGetParams as SubscriptionGetParams, + }; +} diff --git a/src/resources/zones/zones.ts b/src/resources/zones/zones.ts index 2a84e7f2a6..38ee5e8a16 100644 --- a/src/resources/zones/zones.ts +++ b/src/resources/zones/zones.ts @@ -94,7 +94,12 @@ import { ZeroRTT, } from './settings'; import * as SubscriptionsAPI from './subscriptions'; -import { Subscriptions } from './subscriptions'; +import { + SubscriptionCreateParams, + SubscriptionGetParams, + SubscriptionUpdateParams, + Subscriptions, +} from './subscriptions'; import { V4PagePaginationArray, type V4PagePaginationArrayParams } from '../../pagination'; export class Zones extends APIResource { @@ -745,7 +750,12 @@ export declare namespace Zones { type HoldGetParams as HoldGetParams, }; - export { Subscriptions as Subscriptions }; + export { + Subscriptions as Subscriptions, + type SubscriptionCreateParams as SubscriptionCreateParams, + type SubscriptionUpdateParams as SubscriptionUpdateParams, + type SubscriptionGetParams as SubscriptionGetParams, + }; export { Plans as Plans, diff --git a/tests/api-resources/zones/subscriptions.test.ts b/tests/api-resources/zones/subscriptions.test.ts new file mode 100644 index 0000000000..9b955dc7c9 --- /dev/null +++ b/tests/api-resources/zones/subscriptions.test.ts @@ -0,0 +1,85 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import Cloudflare from 'cloudflare'; +import { Response } from 'node-fetch'; + +const client = new Cloudflare({ + apiKey: '144c9defac04969c7bfad8efaa8ea194', + apiEmail: 'user@example.com', + baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', +}); + +describe('resource subscriptions', () => { + test('create: only required params', async () => { + const responsePromise = client.zones.subscriptions.create({ + zone_id: '506e3185e9c882d175a2d0cb0093d9f2', + }); + 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('create: required and optional params', async () => { + const response = await client.zones.subscriptions.create({ + zone_id: '506e3185e9c882d175a2d0cb0093d9f2', + frequency: 'monthly', + rate_plan: { + id: 'free', + currency: 'USD', + externally_managed: false, + is_contract: false, + public_name: 'Business Plan', + scope: 'zone', + sets: ['string'], + }, + }); + }); + + test('update: only required params', async () => { + const responsePromise = client.zones.subscriptions.update({ + zone_id: '506e3185e9c882d175a2d0cb0093d9f2', + }); + 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('update: required and optional params', async () => { + const response = await client.zones.subscriptions.update({ + zone_id: '506e3185e9c882d175a2d0cb0093d9f2', + frequency: 'monthly', + rate_plan: { + id: 'free', + currency: 'USD', + externally_managed: false, + is_contract: false, + public_name: 'Business Plan', + scope: 'zone', + sets: ['string'], + }, + }); + }); + + test('get: only required params', async () => { + const responsePromise = client.zones.subscriptions.get({ zone_id: '506e3185e9c882d175a2d0cb0093d9f2' }); + 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('get: required and optional params', async () => { + const response = await client.zones.subscriptions.get({ zone_id: '506e3185e9c882d175a2d0cb0093d9f2' }); + }); +}); From 7994b935d56454dc14649f8dd65cad1bed1eabeb Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 21:43:26 +0000 Subject: [PATCH 4/4] release: 4.4.1 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 13 +++++++++++++ package.json | 2 +- src/version.ts | 2 +- 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index fb1f343c6a..f4b3cc5134 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "4.4.0" + ".": "4.4.1" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 7dc3098276..b8dd3802fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## 4.4.1 (2025-06-16) + +Full Changelog: [v4.4.0...v4.4.1](https://github.com/cloudflare/cloudflare-typescript/compare/v4.4.0...v4.4.1) + +### Features + +* **client:** add support for endpoint-specific base URLs ([f399656](https://github.com/cloudflare/cloudflare-typescript/commit/f3996568d1d92a7f92956aa52c1e7d167e7afa59)) + + +### Bug Fixes + +* **api:** Update zone subscription paths ([f0ff0b7](https://github.com/cloudflare/cloudflare-typescript/commit/f0ff0b7456757c5c176a4c51b792e44ddb823285)) + ## 4.4.0 (2025-06-16) Full Changelog: [v4.3.0...v4.4.0](https://github.com/cloudflare/cloudflare-typescript/compare/v4.3.0...v4.4.0) diff --git a/package.json b/package.json index d77ba54c61..5eda6809c8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cloudflare", - "version": "4.4.0", + "version": "4.4.1", "description": "The official TypeScript library for the Cloudflare API", "author": "Cloudflare ", "types": "dist/index.d.ts", diff --git a/src/version.ts b/src/version.ts index 8ab25148f0..5956b8d7dd 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const VERSION = '4.4.0'; // x-release-please-version +export const VERSION = '4.4.1'; // x-release-please-version