fix(core): Switch to SmartRequest fluent API and improve Cloudflare API request handling

This commit is contained in:
2025-11-17 23:10:24 +00:00
parent 1c0a20ac99
commit 038f56b0ce
8 changed files with 10467 additions and 4727 deletions

View File

@@ -1,5 +1,15 @@
# Changelog
## 2025-11-17 - 6.4.2 - fix(core)
Switch to SmartRequest fluent API and improve Cloudflare API request handling
- Upgrade runtime dependencies: @push.rocks/smartlog -> ^3.1.10, @push.rocks/smartrequest -> ^5.0.1, @push.rocks/smartstring -> ^4.1.0, @tsclass/tsclass -> ^9.3.0, cloudflare -> ^5.2.0
- Upgrade devDependencies: @git.zone/tsbuild -> ^3.1.0, @git.zone/tsrun -> ^2.0.0, @git.zone/tstest -> ^2.8.2, @push.rocks/qenv -> ^6.1.3, openapi-typescript -> ^7.10.1
- Export SmartRequest and CoreResponse from cloudflare.plugins to align with smartrequest v5 API
- Refactor CloudflareAccount.request to use SmartRequest fluent builder, add detailed logging, default JSON Content-Type, support multipart/form-data via formData(), and use appropriate HTTP method helpers
- Improve response parsing: return a safe fallback when JSON parsing fails by reading response.text() and include a concise message; better HTTP error logging including response body text
- Update usages to rely on the new request behavior (zones/workers managers use account.request for endpoints not covered by the official client)
## 2025-04-30 - 6.4.1 - fix(ci)
Update CI workflows, repository URL, and apply minor code formatting fixes

7414
deno.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -36,21 +36,20 @@
"homepage": "https://gitlab.com/mojoio/cloudflare#readme",
"dependencies": {
"@push.rocks/smartdelay": "^3.0.1",
"@push.rocks/smartlog": "^3.0.2",
"@push.rocks/smartlog": "^3.1.10",
"@push.rocks/smartpromise": "^4.2.3",
"@push.rocks/smartrequest": "^2.1.0",
"@push.rocks/smartstring": "^4.0.5",
"@tsclass/tsclass": "^9.1.0",
"cloudflare": "^4.2.0"
"@push.rocks/smartrequest": "^5.0.1",
"@push.rocks/smartstring": "^4.1.0",
"@tsclass/tsclass": "^9.3.0",
"cloudflare": "^5.2.0"
},
"devDependencies": {
"@git.zone/tsbuild": "^2.3.2",
"@git.zone/tsrun": "^1.3.3",
"@git.zone/tstest": "^1.0.96",
"@push.rocks/qenv": "^6.1.0",
"@push.rocks/tapbundle": "^6.0.0",
"@git.zone/tsbuild": "^3.1.0",
"@git.zone/tsrun": "^2.0.0",
"@git.zone/tstest": "^2.8.2",
"@push.rocks/qenv": "^6.1.3",
"@types/node": "^22.15.3",
"openapi-typescript": "^7.6.1"
"openapi-typescript": "^7.10.1"
},
"files": [
"ts/**/*",

7654
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
// tslint:disable-next-line: no-implicit-dependencies
import { expect, tap } from '@push.rocks/tapbundle';
import { expect, tap } from '@git.zone/tstest/tapbundle';
// tslint:disable-next-line: no-implicit-dependencies
import { Qenv } from '@push.rocks/qenv';

View File

@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@apiclient.xyz/cloudflare',
version: '6.4.1',
version: '6.4.2',
description: 'A TypeScript client for managing Cloudflare accounts, zones, DNS records, and workers with ease.'
}

View File

@@ -42,56 +42,77 @@ export class CloudflareAccount implements plugins.tsclass.network.IConvenientDns
customHeaders?: Record<string, string>,
): Promise<T> {
try {
const options: plugins.smartrequest.ISmartRequestOptions = {
method,
headers: {
Authorization: `Bearer ${this.authToken}`,
'Content-Type': 'application/json',
...customHeaders,
},
};
logger.log('debug', `Making ${method} request to ${endpoint}`);
// Build the request using fluent API
let requestBuilder = plugins.SmartRequest.create()
.url(`https://api.cloudflare.com/client/v4${endpoint}`)
.header('Authorization', `Bearer ${this.authToken}`);
// Add custom headers
if (customHeaders) {
for (const [key, value] of Object.entries(customHeaders)) {
requestBuilder = requestBuilder.header(key, value);
}
} else {
// Default to JSON content type if no custom headers
requestBuilder = requestBuilder.header('Content-Type', 'application/json');
}
// Add request body if provided
if (data) {
if (customHeaders && customHeaders['Content-Type']?.includes('multipart/form-data')) {
// For multipart form data, use the data directly as the request body
options.requestBody = data;
// For multipart form data, use formData method
requestBuilder = requestBuilder.formData(data);
} else {
// For JSON requests, stringify the data
options.requestBody = JSON.stringify(data);
// For JSON requests, use json method
requestBuilder = requestBuilder.json(data);
}
}
logger.log('debug', `Making ${method} request to ${endpoint}`);
const response = await plugins.smartrequest.request(
`https://api.cloudflare.com/client/v4${endpoint}`,
options,
);
// Check if response is already an object (might happen with newer smartrequest versions)
if (typeof response.body === 'object' && response.body !== null) {
return response.body;
// Execute the request with the appropriate method
let response: plugins.CoreResponse;
switch (method) {
case 'GET':
response = await requestBuilder.get();
break;
case 'POST':
response = await requestBuilder.post();
break;
case 'PUT':
response = await requestBuilder.put();
break;
case 'DELETE':
response = await requestBuilder.delete();
break;
case 'PATCH':
response = await requestBuilder.patch();
break;
default:
throw new Error(`Unsupported HTTP method: ${method}`);
}
// Otherwise try to parse as JSON
// Check response status
if (!response.ok) {
const errorBody = await response.text();
logger.log('error', `HTTP ${response.status}: ${errorBody}`);
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
// Parse the response body
try {
if (typeof response.body === 'string' && response.body.trim()) {
return JSON.parse(response.body);
} else {
// If body is empty or not a string, return an empty result
logger.log('warn', `Empty or invalid response body: ${typeof response.body}`);
return { result: [] } as T;
}
return await response.json<T>();
} catch (parseError) {
logger.log('warn', `Failed to parse response as JSON: ${parseError.message}`);
// Create a fake response object to maintain expected structure
// Try to get as text and create a fallback response
const textBody = await response.text().catch(() => '');
return {
result: [],
success: true,
errors: [],
messages: [
`Failed to parse: ${typeof response.body === 'string' ? response.body?.substring(0, 50) : typeof response.body}...`,
],
messages: [`Failed to parse: ${textBody.substring(0, 50)}...`],
} as T;
}
} catch (error) {

View File

@@ -1,11 +1,11 @@
import * as smartlog from '@push.rocks/smartlog';
import * as smartpromise from '@push.rocks/smartpromise';
import * as smartdelay from '@push.rocks/smartdelay';
import * as smartrequest from '@push.rocks/smartrequest';
import { SmartRequest, CoreResponse } from '@push.rocks/smartrequest';
import * as smartstring from '@push.rocks/smartstring';
import * as tsclass from '@tsclass/tsclass';
export { smartlog, smartpromise, smartdelay, smartrequest, smartstring, tsclass };
export { smartlog, smartpromise, smartdelay, SmartRequest, CoreResponse, smartstring, tsclass };
// third party
import * as cloudflare from 'cloudflare';