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 # 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) ## 2025-04-30 - 6.4.1 - fix(ci)
Update CI workflows, repository URL, and apply minor code formatting fixes 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", "homepage": "https://gitlab.com/mojoio/cloudflare#readme",
"dependencies": { "dependencies": {
"@push.rocks/smartdelay": "^3.0.1", "@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/smartpromise": "^4.2.3",
"@push.rocks/smartrequest": "^2.1.0", "@push.rocks/smartrequest": "^5.0.1",
"@push.rocks/smartstring": "^4.0.5", "@push.rocks/smartstring": "^4.1.0",
"@tsclass/tsclass": "^9.1.0", "@tsclass/tsclass": "^9.3.0",
"cloudflare": "^4.2.0" "cloudflare": "^5.2.0"
}, },
"devDependencies": { "devDependencies": {
"@git.zone/tsbuild": "^2.3.2", "@git.zone/tsbuild": "^3.1.0",
"@git.zone/tsrun": "^1.3.3", "@git.zone/tsrun": "^2.0.0",
"@git.zone/tstest": "^1.0.96", "@git.zone/tstest": "^2.8.2",
"@push.rocks/qenv": "^6.1.0", "@push.rocks/qenv": "^6.1.3",
"@push.rocks/tapbundle": "^6.0.0",
"@types/node": "^22.15.3", "@types/node": "^22.15.3",
"openapi-typescript": "^7.6.1" "openapi-typescript": "^7.10.1"
}, },
"files": [ "files": [
"ts/**/*", "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 // 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 // tslint:disable-next-line: no-implicit-dependencies
import { Qenv } from '@push.rocks/qenv'; import { Qenv } from '@push.rocks/qenv';

View File

@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@apiclient.xyz/cloudflare', 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.' 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>, customHeaders?: Record<string, string>,
): Promise<T> { ): Promise<T> {
try { try {
const options: plugins.smartrequest.ISmartRequestOptions = { logger.log('debug', `Making ${method} request to ${endpoint}`);
method,
headers: {
Authorization: `Bearer ${this.authToken}`,
'Content-Type': 'application/json',
...customHeaders,
},
};
// 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 (data) {
if (customHeaders && customHeaders['Content-Type']?.includes('multipart/form-data')) { if (customHeaders && customHeaders['Content-Type']?.includes('multipart/form-data')) {
// For multipart form data, use the data directly as the request body // For multipart form data, use formData method
options.requestBody = data; requestBuilder = requestBuilder.formData(data);
} else { } else {
// For JSON requests, stringify the data // For JSON requests, use json method
options.requestBody = JSON.stringify(data); requestBuilder = requestBuilder.json(data);
} }
} }
logger.log('debug', `Making ${method} request to ${endpoint}`); // Execute the request with the appropriate method
const response = await plugins.smartrequest.request( let response: plugins.CoreResponse;
`https://api.cloudflare.com/client/v4${endpoint}`, switch (method) {
options, case 'GET':
); response = await requestBuilder.get();
break;
// Check if response is already an object (might happen with newer smartrequest versions) case 'POST':
if (typeof response.body === 'object' && response.body !== null) { response = await requestBuilder.post();
return response.body; 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 { try {
if (typeof response.body === 'string' && response.body.trim()) { return await response.json<T>();
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;
}
} catch (parseError) { } catch (parseError) {
logger.log('warn', `Failed to parse response as JSON: ${parseError.message}`); 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 { return {
result: [], result: [],
success: true, success: true,
errors: [], errors: [],
messages: [ messages: [`Failed to parse: ${textBody.substring(0, 50)}...`],
`Failed to parse: ${typeof response.body === 'string' ? response.body?.substring(0, 50) : typeof response.body}...`,
],
} as T; } as T;
} }
} catch (error) { } catch (error) {

View File

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