@withaevum/sdk 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,39 @@
1
+ import { AevumClient } from './client';
2
+ import type { GetSlotsParams, GetSlotsResponse, CheckAvailabilityParams, CheckAvailabilityResponse, GetAvailabilitySchedulesParams, CreateAvailabilityScheduleParams, UpdateAvailabilityScheduleParams, AvailabilitySchedule, GetAvailabilitySlotsParams } from './types';
3
+ /**
4
+ * Availability API methods
5
+ */
6
+ export declare class AvailabilityAPI {
7
+ private client;
8
+ constructor(client: AevumClient);
9
+ /**
10
+ * Get available time slots for a provider within a date range
11
+ * Enhanced version that supports offeringId and limit
12
+ */
13
+ getSlots(params: GetSlotsParams | GetAvailabilitySlotsParams): Promise<GetSlotsResponse>;
14
+ /**
15
+ * Get availability schedules
16
+ */
17
+ getSchedules(params?: GetAvailabilitySchedulesParams): Promise<AvailabilitySchedule[]>;
18
+ /**
19
+ * Create an availability schedule for a provider
20
+ */
21
+ createSchedule(params: CreateAvailabilityScheduleParams): Promise<AvailabilitySchedule>;
22
+ /**
23
+ * Get a single availability schedule by ID
24
+ */
25
+ getSchedule(scheduleId: string): Promise<AvailabilitySchedule>;
26
+ /**
27
+ * Update an availability schedule (partial update)
28
+ */
29
+ updateSchedule(scheduleId: string, params: UpdateAvailabilityScheduleParams): Promise<AvailabilitySchedule>;
30
+ /**
31
+ * Delete an availability schedule
32
+ */
33
+ deleteSchedule(scheduleId: string): Promise<void>;
34
+ /**
35
+ * Check if a specific time slot is available
36
+ * This is a client-side implementation that uses getSlots()
37
+ */
38
+ check(params: CheckAvailabilityParams): Promise<CheckAvailabilityResponse>;
39
+ }
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Availability API methods
3
+ */
4
+ export class AvailabilityAPI {
5
+ constructor(client) {
6
+ this.client = client;
7
+ }
8
+ /**
9
+ * Get available time slots for a provider within a date range
10
+ * Enhanced version that supports offeringId and limit
11
+ */
12
+ async getSlots(params) {
13
+ const query = {};
14
+ // Support both old and new parameter formats
15
+ if ('providerId' in params && params.providerId) {
16
+ query.providerId = params.providerId;
17
+ }
18
+ if ('offeringId' in params && params.offeringId) {
19
+ query.offeringId = params.offeringId;
20
+ }
21
+ if ('start' in params && params.start) {
22
+ query.start = params.start;
23
+ }
24
+ else if ('startDate' in params && params.startDate) {
25
+ query.start = params.startDate;
26
+ }
27
+ if ('end' in params && params.end) {
28
+ query.end = params.end;
29
+ }
30
+ else if ('endDate' in params && params.endDate) {
31
+ query.end = params.endDate;
32
+ }
33
+ if ('limit' in params && params.limit !== undefined) {
34
+ query.limit = params.limit;
35
+ }
36
+ // For backward compatibility with GetSlotsParams
37
+ if ('startDate' in params && 'endDate' in params) {
38
+ if (!params.startDate || !params.endDate) {
39
+ throw new Error('startDate and endDate are required');
40
+ }
41
+ }
42
+ else if (!query.start || !query.end) {
43
+ throw new Error('start/startDate and end/endDate are required');
44
+ }
45
+ try {
46
+ return await this.client.request('GET', '/api/v1/orgs/{orgId}/availability/slots', { query });
47
+ }
48
+ catch (error) {
49
+ // Handle 404 gracefully - endpoint might not exist yet
50
+ if (error?.status === 404 || error?.message?.includes('404')) {
51
+ console.warn('Availability slots endpoint not found, returning empty slots');
52
+ return { slots: [] };
53
+ }
54
+ throw error;
55
+ }
56
+ }
57
+ /**
58
+ * Get availability schedules
59
+ */
60
+ async getSchedules(params) {
61
+ const query = {};
62
+ if (params?.providerId) {
63
+ query.providerId = params.providerId;
64
+ }
65
+ try {
66
+ const response = await this.client.request('GET', '/api/v1/orgs/{orgId}/availability/schedules', { query });
67
+ return response || [];
68
+ }
69
+ catch (error) {
70
+ // Handle 404 gracefully - endpoint might not exist yet
71
+ if (error?.status === 404 || error?.message?.includes('404')) {
72
+ console.warn('Availability schedules endpoint not found, returning empty array');
73
+ return [];
74
+ }
75
+ throw error;
76
+ }
77
+ }
78
+ /**
79
+ * Create an availability schedule for a provider
80
+ */
81
+ async createSchedule(params) {
82
+ return this.client.request('POST', '/api/v1/orgs/{orgId}/availability/schedules', {
83
+ body: {
84
+ providerId: params.providerId,
85
+ name: params.name,
86
+ timezone: params.timezone,
87
+ weekly_hours: params.weekly_hours,
88
+ recurrence_preset: params.recurrence_preset,
89
+ recurrence_end_mode: params.recurrence_end_mode,
90
+ recurrence_end_date: params.recurrence_end_date,
91
+ recurrence_end_count: params.recurrence_end_count,
92
+ },
93
+ });
94
+ }
95
+ /**
96
+ * Get a single availability schedule by ID
97
+ */
98
+ async getSchedule(scheduleId) {
99
+ return this.client.request('GET', `/api/v1/orgs/{orgId}/availability/schedules/${scheduleId}`);
100
+ }
101
+ /**
102
+ * Update an availability schedule (partial update)
103
+ */
104
+ async updateSchedule(scheduleId, params) {
105
+ const body = {};
106
+ if (params.name !== undefined)
107
+ body.name = params.name;
108
+ if (params.timezone !== undefined)
109
+ body.timezone = params.timezone;
110
+ if (params.weekly_hours !== undefined)
111
+ body.weekly_hours = params.weekly_hours;
112
+ if (params.recurrence_preset !== undefined)
113
+ body.recurrence_preset = params.recurrence_preset;
114
+ if (params.recurrence_end_mode !== undefined)
115
+ body.recurrence_end_mode = params.recurrence_end_mode;
116
+ if (params.recurrence_end_date !== undefined)
117
+ body.recurrence_end_date = params.recurrence_end_date;
118
+ if (params.recurrence_end_count !== undefined)
119
+ body.recurrence_end_count = params.recurrence_end_count;
120
+ return this.client.request('PATCH', `/api/v1/orgs/{orgId}/availability/schedules/${scheduleId}`, { body });
121
+ }
122
+ /**
123
+ * Delete an availability schedule
124
+ */
125
+ async deleteSchedule(scheduleId) {
126
+ await this.client.request('DELETE', `/api/v1/orgs/{orgId}/availability/schedules/${scheduleId}`);
127
+ }
128
+ /**
129
+ * Check if a specific time slot is available
130
+ * This is a client-side implementation that uses getSlots()
131
+ */
132
+ async check(params) {
133
+ const { providerId, startTime, duration } = params;
134
+ // Parse start time
135
+ const start = new Date(startTime);
136
+ if (isNaN(start.getTime())) {
137
+ throw new Error('Invalid startTime format. Expected ISO 8601 datetime with timezone');
138
+ }
139
+ // Calculate end time
140
+ const end = new Date(start.getTime() + duration * 60 * 1000);
141
+ // Get slots for a window around the requested time (1 day before and after)
142
+ const windowStart = new Date(start.getTime() - 24 * 60 * 60 * 1000);
143
+ const windowEnd = new Date(end.getTime() + 24 * 60 * 60 * 1000);
144
+ // Format as ISO strings with timezone
145
+ const startDate = windowStart.toISOString();
146
+ const endDate = windowEnd.toISOString();
147
+ // Get available slots
148
+ const { slots } = await this.getSlots({
149
+ providerId,
150
+ startDate,
151
+ endDate,
152
+ });
153
+ // Check if requested time falls within any available slot
154
+ const requestedStart = start.getTime();
155
+ const requestedEnd = end.getTime();
156
+ const isAvailable = slots.some((slot) => {
157
+ const slotStart = new Date(slot.start_time_utc).getTime();
158
+ const slotEnd = new Date(slot.end_time_utc).getTime();
159
+ // Check if requested time is completely within the slot
160
+ return requestedStart >= slotStart && requestedEnd <= slotEnd;
161
+ });
162
+ return { available: isAvailable };
163
+ }
164
+ }
@@ -0,0 +1,42 @@
1
+ import { AevumClient } from './client';
2
+ import type { Booking, CreateBookingParams, ListBookingsParams, ListBookingsResponse, RescheduleBookingParams } from './types';
3
+ /**
4
+ * Bookings API methods
5
+ */
6
+ export declare class BookingsAPI {
7
+ private client;
8
+ constructor(client: AevumClient);
9
+ /**
10
+ * Create a new booking
11
+ */
12
+ create(params: CreateBookingParams): Promise<Booking>;
13
+ /**
14
+ * Get a booking by ID
15
+ */
16
+ get(bookingId: string): Promise<Booking>;
17
+ /**
18
+ * List bookings with optional filters
19
+ */
20
+ list(params?: ListBookingsParams): Promise<ListBookingsResponse>;
21
+ /**
22
+ * Cancel a booking
23
+ */
24
+ cancel(bookingId: string, reason?: string): Promise<Booking>;
25
+ /**
26
+ * Reschedule a booking
27
+ */
28
+ reschedule(bookingId: string, params: RescheduleBookingParams): Promise<Booking>;
29
+ /**
30
+ * Confirm a booking
31
+ */
32
+ confirm(bookingId: string, params?: {
33
+ sendNotification?: boolean;
34
+ }): Promise<Booking>;
35
+ /**
36
+ * Update a booking (status and/or notes)
37
+ */
38
+ update(bookingId: string, params: {
39
+ status?: string;
40
+ notes?: string;
41
+ }): Promise<Booking>;
42
+ }
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Bookings API methods
3
+ */
4
+ export class BookingsAPI {
5
+ constructor(client) {
6
+ this.client = client;
7
+ }
8
+ /**
9
+ * Create a new booking
10
+ */
11
+ async create(params) {
12
+ // Map SDK params to API format
13
+ const providerIds = params.providerIds || (params.providerId ? [params.providerId] : []);
14
+ const offeringIds = params.offeringIds || (params.offeringId ? [params.offeringId] : []);
15
+ if (providerIds.length === 0) {
16
+ throw new Error('At least one providerId or providerIds is required');
17
+ }
18
+ if (offeringIds.length === 0) {
19
+ throw new Error('At least one offeringId or offeringIds is required');
20
+ }
21
+ // Calculate endTime if not provided (assuming we can infer from offering duration)
22
+ // For now, require endTime to be provided
23
+ if (!params.endTime) {
24
+ throw new Error('endTime is required');
25
+ }
26
+ // Build request body
27
+ const body = {
28
+ providerIds,
29
+ offeringIds,
30
+ start_time: params.startTime,
31
+ end_time: params.endTime,
32
+ kind: params.kind || 'standard',
33
+ status: params.status || 'pending',
34
+ };
35
+ // Handle customer
36
+ if (params.customerId) {
37
+ body.customerId = params.customerId;
38
+ }
39
+ else if (params.customerEmail || params.customer) {
40
+ body.customer = {
41
+ email: params.customerEmail || params.customer?.email,
42
+ name: params.customer?.name,
43
+ phone: params.customer?.phone,
44
+ };
45
+ }
46
+ else {
47
+ throw new Error('Either customerId, customerEmail, or customer object is required');
48
+ }
49
+ // Add optional fields
50
+ if (params.price_cents !== undefined) {
51
+ body.price_cents = params.price_cents;
52
+ }
53
+ if (params.notes) {
54
+ body.notes = params.notes;
55
+ }
56
+ const response = await this.client.request('POST', '/api/v1/orgs/{orgId}/bookings', { body });
57
+ // The API returns a partial booking, so we need to fetch the full booking
58
+ if (response.id) {
59
+ return this.get(response.id);
60
+ }
61
+ return response;
62
+ }
63
+ /**
64
+ * Get a booking by ID
65
+ */
66
+ async get(bookingId) {
67
+ return this.client.request('GET', `/api/v1/orgs/{orgId}/bookings/${bookingId}`);
68
+ }
69
+ /**
70
+ * List bookings with optional filters
71
+ */
72
+ async list(params) {
73
+ const query = {};
74
+ if (params?.status) {
75
+ query.status = params.status;
76
+ }
77
+ if (params?.providerId) {
78
+ query.providerId = params.providerId;
79
+ }
80
+ if (params?.customerId) {
81
+ query.customerId = params.customerId;
82
+ }
83
+ if (params?.startDate) {
84
+ query.start = params.startDate;
85
+ }
86
+ if (params?.endDate) {
87
+ query.end = params.endDate;
88
+ }
89
+ if (params?.page !== undefined) {
90
+ query.page = params.page;
91
+ }
92
+ if (params?.pageSize !== undefined) {
93
+ query.pageSize = params.pageSize;
94
+ }
95
+ return this.client.request('GET', '/api/v1/orgs/{orgId}/bookings', { query });
96
+ }
97
+ /**
98
+ * Cancel a booking
99
+ */
100
+ async cancel(bookingId, reason) {
101
+ const body = {};
102
+ if (reason) {
103
+ body.reason = reason;
104
+ }
105
+ await this.client.request('PATCH', `/api/v1/orgs/{orgId}/bookings/${bookingId}/cancel`, { body });
106
+ // Return updated booking
107
+ return this.get(bookingId);
108
+ }
109
+ /**
110
+ * Reschedule a booking
111
+ */
112
+ async reschedule(bookingId, params) {
113
+ const body = {
114
+ start_time: params.start_time,
115
+ end_time: params.end_time,
116
+ sendNotification: params.sendNotification ?? false,
117
+ };
118
+ await this.client.request('PATCH', `/api/v1/orgs/{orgId}/bookings/${bookingId}/reschedule`, { body });
119
+ // Return updated booking
120
+ return this.get(bookingId);
121
+ }
122
+ /**
123
+ * Confirm a booking
124
+ */
125
+ async confirm(bookingId, params) {
126
+ const body = {
127
+ sendNotification: params?.sendNotification ?? false,
128
+ };
129
+ await this.client.request('PATCH', `/api/v1/orgs/{orgId}/bookings/${bookingId}/confirm`, { body });
130
+ // Return updated booking
131
+ return this.get(bookingId);
132
+ }
133
+ /**
134
+ * Update a booking (status and/or notes)
135
+ */
136
+ async update(bookingId, params) {
137
+ const body = {};
138
+ if (params.status !== undefined) {
139
+ body.status = params.status;
140
+ }
141
+ if (params.notes !== undefined) {
142
+ body.notes = params.notes;
143
+ }
144
+ await this.client.request('PATCH', `/api/v1/orgs/{orgId}/bookings/${bookingId}`, { body });
145
+ // Return updated booking
146
+ return this.get(bookingId);
147
+ }
148
+ }
@@ -0,0 +1,13 @@
1
+ import { AevumClient } from './client';
2
+ import type { GetCalendarEventsParams, GetCalendarEventsResponse } from './types';
3
+ /**
4
+ * Calendar API methods
5
+ */
6
+ export declare class CalendarAPI {
7
+ private client;
8
+ constructor(client: AevumClient);
9
+ /**
10
+ * Get calendar events (bookings and events) for a date range
11
+ */
12
+ getEvents(params?: GetCalendarEventsParams): Promise<GetCalendarEventsResponse>;
13
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Calendar API methods
3
+ */
4
+ export class CalendarAPI {
5
+ constructor(client) {
6
+ this.client = client;
7
+ }
8
+ /**
9
+ * Get calendar events (bookings and events) for a date range
10
+ */
11
+ async getEvents(params) {
12
+ const orgId = await this.client.resolveOrgId();
13
+ // Get bookings for calendar
14
+ const bookingsQuery = {};
15
+ if (params?.providerId) {
16
+ bookingsQuery.providerId = params.providerId;
17
+ }
18
+ if (params?.start) {
19
+ bookingsQuery.start = params.start;
20
+ }
21
+ if (params?.end) {
22
+ bookingsQuery.end = params.end;
23
+ }
24
+ const bookings = await this.client.request('GET', '/api/v1/orgs/{orgId}/bookings', {
25
+ query: bookingsQuery,
26
+ });
27
+ // Get events for calendar
28
+ const eventsQuery = {};
29
+ if (params?.providerId) {
30
+ eventsQuery.providerId = params.providerId;
31
+ }
32
+ if (params?.start) {
33
+ eventsQuery.start = params.start;
34
+ }
35
+ if (params?.end) {
36
+ eventsQuery.end = params.end;
37
+ }
38
+ let events = [];
39
+ try {
40
+ const eventsResponse = await this.client.request('GET', `/api/v1/orgs/${orgId}/events`, { query: eventsQuery });
41
+ // Handle paginated response - extract events/offerings array
42
+ if (Array.isArray(eventsResponse)) {
43
+ events = eventsResponse;
44
+ }
45
+ else if (eventsResponse?.events) {
46
+ events = eventsResponse.events;
47
+ }
48
+ else if (eventsResponse?.offerings) {
49
+ // Events endpoint returns 'offerings' property
50
+ events = eventsResponse.offerings;
51
+ }
52
+ else {
53
+ events = [];
54
+ }
55
+ }
56
+ catch (error) {
57
+ // Events endpoint might not be available, continue without it
58
+ console.warn('Failed to fetch events:', error);
59
+ }
60
+ return {
61
+ bookings: bookings.bookings || [],
62
+ events: events || [],
63
+ };
64
+ }
65
+ }
@@ -0,0 +1,44 @@
1
+ import { AvailabilityAPI } from './availability';
2
+ import { BookingsAPI } from './bookings';
3
+ import { OfferingsAPI } from './offerings';
4
+ import { CustomersAPI } from './customers';
5
+ import { ProvidersAPI } from './providers';
6
+ import { OrgsAPI } from './orgs';
7
+ import { CalendarAPI } from './calendar';
8
+ import { AnalyticsAPI } from './analytics';
9
+ import { PaymentsAPI } from './payments';
10
+ import type { AevumClientConfig } from './types';
11
+ /**
12
+ * Main client class for interacting with the Aevum API
13
+ */
14
+ export declare class AevumClient {
15
+ private readonly apiKey;
16
+ private readonly baseUrl;
17
+ private orgId;
18
+ private orgIdPromise;
19
+ readonly bookings: BookingsAPI;
20
+ readonly availability: AvailabilityAPI;
21
+ readonly offerings: OfferingsAPI;
22
+ readonly customers: CustomersAPI;
23
+ readonly providers: ProvidersAPI;
24
+ readonly orgs: OrgsAPI;
25
+ readonly calendar: CalendarAPI;
26
+ readonly analytics: AnalyticsAPI;
27
+ readonly payments: PaymentsAPI;
28
+ constructor(config: AevumClientConfig);
29
+ /**
30
+ * Resolve organization ID from API key (cached after first call)
31
+ */
32
+ resolveOrgId(): Promise<string>;
33
+ /**
34
+ * Internal method to fetch orgId from API
35
+ */
36
+ private _fetchOrgId;
37
+ /**
38
+ * Make an authenticated HTTP request to the API
39
+ */
40
+ request<T>(method: string, path: string, options?: {
41
+ body?: unknown;
42
+ query?: Record<string, string | number | undefined>;
43
+ }): Promise<T>;
44
+ }
package/dist/client.js ADDED
@@ -0,0 +1,162 @@
1
+ // packages/sdk/src/client.ts
2
+ import { AvailabilityAPI } from './availability';
3
+ import { BookingsAPI } from './bookings';
4
+ import { OfferingsAPI } from './offerings';
5
+ import { CustomersAPI } from './customers';
6
+ import { ProvidersAPI } from './providers';
7
+ import { OrgsAPI } from './orgs';
8
+ import { CalendarAPI } from './calendar';
9
+ import { AnalyticsAPI } from './analytics';
10
+ import { PaymentsAPI } from './payments';
11
+ import { AevumAPIError, AevumAuthenticationError, AevumNotFoundError, AevumValidationError, } from './errors';
12
+ /**
13
+ * Main client class for interacting with the Aevum API
14
+ */
15
+ export class AevumClient {
16
+ constructor(config) {
17
+ this.orgId = null;
18
+ this.orgIdPromise = null;
19
+ if (!config.apiKey) {
20
+ throw new Error('API key is required');
21
+ }
22
+ this.apiKey = config.apiKey;
23
+ // Default baseUrl: use provided, or check env var (Node.js only), or use production
24
+ if (config.baseUrl) {
25
+ this.baseUrl = config.baseUrl;
26
+ }
27
+ else if (typeof process !== 'undefined' && process.env?.AEVUM_API_BASE_URL) {
28
+ // Check environment variable if in Node.js
29
+ this.baseUrl = process.env.AEVUM_API_BASE_URL;
30
+ }
31
+ else if (typeof process !== 'undefined' && process.env?.NODE_ENV === 'development') {
32
+ // Default to localhost in development
33
+ this.baseUrl = 'http://localhost:3000';
34
+ }
35
+ else {
36
+ // Production default
37
+ this.baseUrl = 'https://withaevum.com';
38
+ }
39
+ // Set orgId if provided in config (avoids extra API call)
40
+ if (config.orgId) {
41
+ this.orgId = config.orgId;
42
+ }
43
+ // Initialize API namespaces
44
+ this.bookings = new BookingsAPI(this);
45
+ this.availability = new AvailabilityAPI(this);
46
+ this.offerings = new OfferingsAPI(this);
47
+ this.customers = new CustomersAPI(this);
48
+ this.providers = new ProvidersAPI(this);
49
+ this.orgs = new OrgsAPI(this);
50
+ this.calendar = new CalendarAPI(this);
51
+ this.analytics = new AnalyticsAPI(this);
52
+ this.payments = new PaymentsAPI(this);
53
+ }
54
+ /**
55
+ * Resolve organization ID from API key (cached after first call)
56
+ */
57
+ async resolveOrgId() {
58
+ // Return cached orgId if available
59
+ if (this.orgId) {
60
+ return this.orgId;
61
+ }
62
+ // Return existing promise if resolution is in progress
63
+ if (this.orgIdPromise) {
64
+ return this.orgIdPromise;
65
+ }
66
+ // Start resolution
67
+ this.orgIdPromise = this._fetchOrgId();
68
+ this.orgId = await this.orgIdPromise;
69
+ return this.orgId;
70
+ }
71
+ /**
72
+ * Internal method to fetch orgId from API
73
+ */
74
+ async _fetchOrgId() {
75
+ const response = await fetch(`${this.baseUrl}/api/v1/session/org-from-key`, {
76
+ method: 'GET',
77
+ headers: {
78
+ Authorization: `Bearer ${this.apiKey}`,
79
+ 'Content-Type': 'application/json',
80
+ },
81
+ });
82
+ if (!response.ok) {
83
+ if (response.status === 401 || response.status === 403) {
84
+ throw new AevumAuthenticationError('Invalid or missing API key', response.status);
85
+ }
86
+ const errorText = await response.text().catch(() => '');
87
+ throw new AevumAPIError(`Failed to resolve organization ID: ${errorText}`, response.status);
88
+ }
89
+ const data = await response.json();
90
+ if (!data.orgId) {
91
+ throw new AevumAPIError('Invalid response: missing orgId', response.status);
92
+ }
93
+ return data.orgId;
94
+ }
95
+ /**
96
+ * Make an authenticated HTTP request to the API
97
+ */
98
+ async request(method, path, options = {}) {
99
+ // Ensure orgId is resolved (will use cache if already resolved)
100
+ const orgId = await this.resolveOrgId();
101
+ // Replace {orgId} placeholder in path if present
102
+ const resolvedPath = path.replace('{orgId}', orgId);
103
+ // Build URL with query parameters
104
+ const url = new URL(resolvedPath, this.baseUrl);
105
+ if (options.query) {
106
+ Object.entries(options.query).forEach(([key, value]) => {
107
+ if (value !== undefined && value !== null) {
108
+ url.searchParams.append(key, String(value));
109
+ }
110
+ });
111
+ }
112
+ // Prepare headers
113
+ const headers = {
114
+ Authorization: `Bearer ${this.apiKey}`,
115
+ 'Content-Type': 'application/json',
116
+ };
117
+ // Prepare request options
118
+ const requestOptions = {
119
+ method,
120
+ headers,
121
+ };
122
+ // Add body if present
123
+ if (options.body !== undefined) {
124
+ requestOptions.body = JSON.stringify(options.body);
125
+ }
126
+ // Make the request
127
+ const response = await fetch(url.toString(), requestOptions);
128
+ // Handle empty responses (204 No Content)
129
+ if (response.status === 204 ||
130
+ response.headers.get('content-length') === '0') {
131
+ return null;
132
+ }
133
+ // Parse response body
134
+ const text = await response.text();
135
+ let data;
136
+ try {
137
+ data = text ? JSON.parse(text) : null;
138
+ }
139
+ catch (error) {
140
+ throw new AevumAPIError(`Invalid JSON response: ${text}`, response.status);
141
+ }
142
+ // Handle errors
143
+ if (!response.ok) {
144
+ // Safely extract error data, handling null/undefined cases
145
+ const errorData = (data && typeof data === 'object') ? data : null;
146
+ const errorMessage = errorData?.error || `API request failed with status ${response.status}`;
147
+ const errorCode = errorData?.code;
148
+ const errorIssues = errorData?.issues;
149
+ if (response.status === 401 || response.status === 403) {
150
+ throw new AevumAuthenticationError(errorMessage, response.status);
151
+ }
152
+ if (response.status === 404) {
153
+ throw new AevumNotFoundError(errorMessage);
154
+ }
155
+ if (response.status === 400) {
156
+ throw new AevumValidationError(errorMessage, errorIssues);
157
+ }
158
+ throw new AevumAPIError(errorMessage, response.status, errorCode, errorIssues);
159
+ }
160
+ return data;
161
+ }
162
+ }