243 lines
7.9 KiB
TypeScript
243 lines
7.9 KiB
TypeScript
import type { key_val } from '$lib/stores/ae_stores';
|
|
|
|
/**
|
|
* Performs a DELETE request to the Aether API.
|
|
* Refactored 2026-01-08 to use standard fetch with timeout, custom fetch injection,
|
|
* standardized kebab-case headers, and robust V3 response handling.
|
|
*/
|
|
export const delete_object = async function delete_object({
|
|
api_cfg = null,
|
|
endpoint = '',
|
|
headers = {},
|
|
params = {},
|
|
data = {},
|
|
timeout = 60000,
|
|
return_meta = false,
|
|
log_lvl = 0,
|
|
retry_count = 5
|
|
}: {
|
|
api_cfg: any;
|
|
endpoint: string;
|
|
headers?: any;
|
|
params?: any;
|
|
data?: any;
|
|
timeout?: number;
|
|
return_meta?: boolean;
|
|
log_lvl?: number;
|
|
retry_count?: number;
|
|
}) {
|
|
if (log_lvl) {
|
|
console.log(`*** delete_object() *** Endpoint: ${endpoint}`);
|
|
console.log('Params:', params);
|
|
if (log_lvl > 1) {
|
|
console.log('Data:', data);
|
|
console.log(`Base URL: ${api_cfg?.['base_url']}`);
|
|
}
|
|
}
|
|
|
|
if (!api_cfg) {
|
|
console.error('No API Config was provided. Returning false.');
|
|
return false;
|
|
}
|
|
|
|
// Construct the URL with query parameters
|
|
const url = new URL(endpoint, api_cfg['base_url']);
|
|
if (params) {
|
|
Object.keys(params).forEach((key) =>
|
|
url.searchParams.append(key, params[key])
|
|
);
|
|
}
|
|
|
|
// Clean and merge headers without mutating the original api_cfg
|
|
const headers_cleaned: key_val = {};
|
|
const merged_headers = { ...api_cfg['headers'], ...headers };
|
|
|
|
// Handle "Bootstrap Paradox" for unauthenticated requests
|
|
if (merged_headers.hasOwnProperty('x-no-account-id')) {
|
|
delete merged_headers['x-account-id'];
|
|
if (merged_headers['x-no-account-id'] === null) {
|
|
merged_headers['x-no-account-id'] = 'Nothing to See Here';
|
|
}
|
|
}
|
|
|
|
for (const prop in merged_headers) {
|
|
const prop_cleaned = prop.replaceAll('_', '-');
|
|
let value = merged_headers[prop];
|
|
if (value === null || value === undefined) continue;
|
|
|
|
if (typeof value !== 'string') {
|
|
value = JSON.stringify(value);
|
|
}
|
|
headers_cleaned[prop_cleaned] = value;
|
|
}
|
|
|
|
// Auto-inject Authorization header if JWT is present but header is missing
|
|
const jwt =
|
|
headers_cleaned['jwt'] || headers_cleaned['JWT'] || api_cfg['jwt'];
|
|
if (
|
|
jwt &&
|
|
!headers_cleaned['Authorization'] &&
|
|
!headers_cleaned['authorization']
|
|
) {
|
|
headers_cleaned['Authorization'] = `Bearer ${jwt}`;
|
|
}
|
|
|
|
headers_cleaned['Content-Type'] = 'application/json';
|
|
|
|
if (log_lvl > 1) {
|
|
console.log('Final cleaned headers:', headers_cleaned);
|
|
}
|
|
|
|
let fetch_method: any = fetch;
|
|
if (api_cfg.fetch) {
|
|
if (log_lvl > 1) {
|
|
console.log('Using custom fetch function from api_cfg!!!');
|
|
}
|
|
fetch_method = api_cfg.fetch;
|
|
}
|
|
|
|
for (let attempt = 1; attempt <= retry_count; attempt++) {
|
|
// Keep timeout handle at attempt scope so catch can always clear it.
|
|
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
try {
|
|
const controller = new AbortController();
|
|
// AbortError alone is ambiguous. Track helper-timeout aborts so
|
|
// caller/navigation aborts can still fail fast with no retry.
|
|
let did_timeout_abort = false;
|
|
timeoutId = setTimeout(() => {
|
|
did_timeout_abort = true;
|
|
console.error(
|
|
`API DELETE request timed out after ${timeout}ms.`
|
|
);
|
|
controller.abort();
|
|
}, timeout);
|
|
|
|
const fetchOptions: RequestInit = {
|
|
method: 'DELETE',
|
|
headers: headers_cleaned,
|
|
body:
|
|
Object.keys(data).length > 0
|
|
? JSON.stringify(data)
|
|
: undefined,
|
|
signal: controller.signal
|
|
};
|
|
|
|
const response = await fetch_method(
|
|
url.toString(),
|
|
fetchOptions
|
|
).catch(function (error: any) {
|
|
if (
|
|
error?.name === 'AbortError' ||
|
|
error?.name === 'TypeError' ||
|
|
error?.message?.includes('aborted')
|
|
) {
|
|
if (log_lvl > 1) {
|
|
console.log(
|
|
'API DELETE: Request aborted or browser-terminated.',
|
|
error
|
|
);
|
|
}
|
|
return error;
|
|
}
|
|
|
|
console.log(
|
|
'API DELETE Object *fetch* request was aborted or failed in an unexpected way.',
|
|
error
|
|
);
|
|
return error;
|
|
});
|
|
if (timeoutId) clearTimeout(timeoutId);
|
|
|
|
// Error object was returned from fetch catch block; decide retry class.
|
|
if (
|
|
response instanceof Error ||
|
|
(response &&
|
|
(response.name === 'AbortError' ||
|
|
response.name === 'TypeError'))
|
|
) {
|
|
if (response.name === 'AbortError') {
|
|
if (did_timeout_abort) {
|
|
throw new Error(
|
|
`Timeout abort (attempt ${attempt}/${retry_count}) after ${timeout}ms`
|
|
);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
throw new Error(
|
|
`Network error (attempt ${attempt}): ${response.message}`
|
|
);
|
|
}
|
|
|
|
if (!response) {
|
|
throw new Error(
|
|
`HTTP fetch request was aborted or failed in an unexpected way! URL = ${url.toString()}`
|
|
);
|
|
}
|
|
|
|
if (log_lvl) {
|
|
console.log(
|
|
`Response: status=${response.status} attempt=${attempt}`
|
|
);
|
|
}
|
|
|
|
if (!response.ok) {
|
|
if (response.status === 404) {
|
|
console.warn('404 Not Found. Returning null.');
|
|
return null;
|
|
}
|
|
|
|
const errorBody = await response.text();
|
|
console.error(
|
|
`HTTP error! status: ${response.status}`,
|
|
errorBody
|
|
);
|
|
|
|
// Fail fast on client/auth/validation failures.
|
|
if (
|
|
response.status === 400 ||
|
|
response.status === 401 ||
|
|
response.status === 403 ||
|
|
response.status === 422
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
throw new Error(
|
|
`HTTP error! status: ${response.status} - ${errorBody}`
|
|
);
|
|
}
|
|
|
|
const json = await response.json();
|
|
|
|
if (log_lvl > 1) {
|
|
console.log('Response JSON:', json);
|
|
}
|
|
|
|
// Return the response data or metadata
|
|
// Robustly handle V3 response envelopes
|
|
return return_meta
|
|
? json
|
|
: json.data !== undefined
|
|
? json.data
|
|
: json;
|
|
} catch (error) {
|
|
// Ensure per-attempt timeout is always cleared on failure.
|
|
if (timeoutId) clearTimeout(timeoutId);
|
|
console.error(`API DELETE error on attempt ${attempt}:`, error);
|
|
|
|
if (attempt === retry_count) {
|
|
console.error('Max retry attempts reached. Returning false.');
|
|
return false;
|
|
}
|
|
|
|
// Backoff before retrying. Caps at 8s to match GET/POST/PATCH policy.
|
|
const delay_ms = Math.min(2000 * attempt, 8000);
|
|
console.log(
|
|
`API DELETE: Retrying in ${delay_ms}ms... (attempt ${attempt}/${retry_count})`
|
|
);
|
|
await new Promise<void>((resolve) => setTimeout(resolve, delay_ms));
|
|
}
|
|
}
|
|
};
|