feat(core): Release 6.2.0: Improved async iterator support, enhanced error handling and refined API interfaces for better type safety and consistent behavior.

This commit is contained in:
2025-04-26 12:15:16 +00:00
parent 092a6ba55b
commit 1b34bee35d
8 changed files with 301 additions and 120 deletions

View File

@@ -32,12 +32,14 @@ export class CloudflareAccount {
* @param method HTTP method (GET, POST, PUT, DELETE)
* @param endpoint API endpoint path
* @param data Optional request body data
* @param customHeaders Optional custom headers to override defaults
* @returns API response
*/
public async request<T = any>(
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH',
endpoint: string,
data?: any
data?: any,
customHeaders?: Record<string, string>
): Promise<T> {
try {
const options: plugins.smartrequest.ISmartRequestOptions = {
@@ -45,15 +47,48 @@ export class CloudflareAccount {
headers: {
'Authorization': `Bearer ${this.authToken}`,
'Content-Type': 'application/json',
...customHeaders,
},
};
if (data) {
options.requestBody = JSON.stringify(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;
} else {
// For JSON requests, stringify the data
options.requestBody = JSON.stringify(data);
}
}
logger.log('debug', `Making ${method} request to ${endpoint}`);
const response = await plugins.smartrequest.request(`https://api.cloudflare.com/client/v4${endpoint}`, options);
return JSON.parse(response.body);
// Check if response is already an object (might happen with newer smartrequest versions)
if (typeof response.body === 'object' && response.body !== null) {
return response.body;
}
// Otherwise try to parse as JSON
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;
}
} catch (parseError) {
logger.log('warn', `Failed to parse response as JSON: ${parseError.message}`);
// Create a fake response object to maintain expected structure
return {
result: [],
success: true,
errors: [],
messages: [`Failed to parse: ${typeof response.body === 'string' ? response.body?.substring(0, 50) : typeof response.body}...`]
} as T;
}
} catch (error) {
logger.log('error', `Cloudflare API request failed: ${error.message}`);
throw error;
@@ -74,14 +109,24 @@ export class CloudflareAccount {
public convenience = {
/**
* listAccounts
* Lists all accounts accessible with the current API token
* @returns Array of Cloudflare account objects
*/
listAccounts: async () => {
const accounts: plugins.ICloudflareTypes['Account'][] = [];
for await (const account of this.apiAccount.accounts.list()) {
accounts.push(account as interfaces.ICloudflareApiAccountObject);
try {
const accounts: plugins.ICloudflareTypes['Account'][] = [];
// Collect all accounts using async iterator
for await (const account of this.apiAccount.accounts.list()) {
accounts.push(account as interfaces.ICloudflareApiAccountObject);
}
logger.log('info', `Found ${accounts.length} accounts`);
return accounts;
} catch (error) {
logger.log('error', `Failed to list accounts: ${error.message}`);
return [];
}
return accounts;
},
/**
* gets a zone id of a domain from cloudflare
@@ -262,27 +307,19 @@ export class CloudflareAccount {
* @param domainNameArg - the domain name that you want to get the records from
*/
listRecords: async (domainNameArg: string) => {
const domain = new plugins.smartstring.Domain(domainNameArg);
const zoneId = await this.convenience.getZoneId(domain.zoneName);
const records: plugins.ICloudflareTypes['Record'][] = [];
try {
const result = await this.apiAccount.dns.records.list({
zone_id: zoneId,
});
const domain = new plugins.smartstring.Domain(domainNameArg);
const zoneId = await this.convenience.getZoneId(domain.zoneName);
const records: plugins.ICloudflareTypes['Record'][] = [];
// Check if the result has a 'result' property (API response format)
if (result && result.result && Array.isArray(result.result)) {
return result.result;
}
// Otherwise iterate through async iterator (new client format)
// Collect all records using async iterator
for await (const record of this.apiAccount.dns.records.list({
zone_id: zoneId,
})) {
records.push(record);
}
logger.log('info', `Found ${records.length} DNS records for ${domainNameArg}`);
return records;
} catch (error) {
logger.log('error', `Failed to list records for ${domainNameArg}: ${error.message}`);
@@ -291,29 +328,23 @@ export class CloudflareAccount {
},
/**
* list all zones in the associated authenticated account
* @param domainName
* @param domainName optional filter by domain name
*/
listZones: async (domainName?: string) => {
const options: any = {};
if (domainName) {
options.name = domainName;
}
const zones: plugins.ICloudflareTypes['Zone'][] = [];
try {
const result = await this.apiAccount.zones.list(options);
// Check if the result has a 'result' property (API response format)
if (result && result.result && Array.isArray(result.result)) {
return result.result;
const options: any = {};
if (domainName) {
options.name = domainName;
}
// Otherwise iterate through async iterator (new client format)
const zones: plugins.ICloudflareTypes['Zone'][] = [];
// Collect all zones using async iterator
for await (const zone of this.apiAccount.zones.list(options)) {
zones.push(zone);
}
logger.log('info', `Found ${zones.length} zones${domainName ? ` matching ${domainName}` : ''}`);
return zones;
} catch (error) {
logger.log('error', `Failed to list zones: ${error.message}`);