@startsimpli/api 0.1.0 → 0.2.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.
- package/dist/index.d.mts +2235 -0
- package/dist/index.d.ts +2235 -0
- package/dist/index.js +2092 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2010 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +34 -14
- package/src/__tests__/drf-transforms.test.ts +99 -0
- package/src/__tests__/entity-query-builder.test.ts +197 -0
- package/src/__tests__/funnels-api.test.ts +237 -0
- package/src/__tests__/jwt-refresh.test.ts +11 -14
- package/src/__tests__/url-builder.test.ts +27 -1
- package/src/__tests__/validate-response.test.ts +68 -0
- package/src/constants/endpoints.ts +13 -0
- package/src/constants/options.ts +83 -0
- package/src/hooks/index.ts +4 -0
- package/src/hooks/use-server-detail.ts +71 -0
- package/src/hooks/use-server-list.ts +169 -0
- package/src/index.ts +36 -2
- package/src/lib/api-client.ts +10 -19
- package/src/lib/cache-manager.ts +103 -0
- package/src/lib/cache-store.ts +113 -0
- package/src/lib/error-handler.ts +1 -1
- package/src/lib/fetch-wrapper.ts +38 -26
- package/src/lib/funnels-api.ts +221 -0
- package/src/middleware/index.ts +3 -0
- package/src/middleware/with-rate-limit.ts +54 -0
- package/src/utils/drf-transforms.ts +64 -0
- package/src/utils/entity-query-builder.ts +174 -0
- package/src/utils/index.ts +11 -1
- package/src/utils/url-builder.ts +27 -0
- package/src/utils/validate-response.ts +39 -0
- package/src/lib/messages-api.ts.backup +0 -273
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime API response validation
|
|
3
|
+
*
|
|
4
|
+
* Validates responses against Zod schemas but NEVER throws.
|
|
5
|
+
* On mismatch: logs a warning, returns original data cast to expected type.
|
|
6
|
+
* Schema drift should not crash the app.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { z } from 'zod';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Validate an API response against a Zod schema.
|
|
13
|
+
*
|
|
14
|
+
* - On success: returns parsed (cleaned) data
|
|
15
|
+
* - On failure: logs a warning with `context` label, returns the raw data
|
|
16
|
+
* cast as T. **The return value is NOT guaranteed to satisfy T on
|
|
17
|
+
* validation failure** — callers must tolerate schema drift at runtime.
|
|
18
|
+
*
|
|
19
|
+
* This is intentional — schema drift should degrade gracefully,
|
|
20
|
+
* not crash the app. Pass a unique `context` string so mismatches
|
|
21
|
+
* can be traced in logs (e.g. `"contacts.list"`, `"campaigns.get"`).
|
|
22
|
+
*/
|
|
23
|
+
export function validateApiResponse<T>(
|
|
24
|
+
data: unknown,
|
|
25
|
+
schema: z.ZodSchema<T>,
|
|
26
|
+
context?: string
|
|
27
|
+
): T {
|
|
28
|
+
const result = schema.safeParse(data);
|
|
29
|
+
if (!result.success) {
|
|
30
|
+
const label = context ? `[${context}]` : '[API validation]';
|
|
31
|
+
// BEAD: fund-your-startup-kadw - returns unvalidated data cast to T on failure; callers get wrong type silently
|
|
32
|
+
console.warn(
|
|
33
|
+
`${label} Schema mismatch — returning raw data.`,
|
|
34
|
+
result.error.flatten().fieldErrors
|
|
35
|
+
);
|
|
36
|
+
return data as T;
|
|
37
|
+
}
|
|
38
|
+
return result.data;
|
|
39
|
+
}
|
|
@@ -1,273 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Messages API wrapper
|
|
3
|
-
*
|
|
4
|
-
* Provides type-safe access to Django Messages API
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { ApiClient } from './api-client';
|
|
8
|
-
|
|
9
|
-
export type MessageStatus = 'draft' | 'scheduled' | 'sending' | 'sent' | 'failed';
|
|
10
|
-
export type MessageContentType = 'text/plain' | 'text/markdown' | 'text/html';
|
|
11
|
-
export type RecipientStatus = 'pending' | 'sent' | 'delivered' | 'bounced' | 'opened' | 'clicked';
|
|
12
|
-
|
|
13
|
-
export interface Message {
|
|
14
|
-
id: string;
|
|
15
|
-
team: string;
|
|
16
|
-
channel: string;
|
|
17
|
-
channel_details?: {
|
|
18
|
-
key: string;
|
|
19
|
-
name: string;
|
|
20
|
-
description: string;
|
|
21
|
-
icon: string;
|
|
22
|
-
};
|
|
23
|
-
subject: string;
|
|
24
|
-
body: string;
|
|
25
|
-
content_type: MessageContentType;
|
|
26
|
-
from_name: string | null;
|
|
27
|
-
from_email: string | null;
|
|
28
|
-
reply_to: string | null;
|
|
29
|
-
status: MessageStatus;
|
|
30
|
-
scheduled_at: string | null;
|
|
31
|
-
sent_at: string | null;
|
|
32
|
-
metadata: Record<string, any>;
|
|
33
|
-
total_recipients: number;
|
|
34
|
-
recipients_sent: number;
|
|
35
|
-
recipients_delivered: number;
|
|
36
|
-
recipients_opened: number;
|
|
37
|
-
recipients_clicked: number;
|
|
38
|
-
recipients_bounced: number;
|
|
39
|
-
recipients_unsubscribed: number;
|
|
40
|
-
open_rate: number;
|
|
41
|
-
click_rate: number;
|
|
42
|
-
bounce_rate: number;
|
|
43
|
-
error_message: string | null;
|
|
44
|
-
created_at: string;
|
|
45
|
-
updated_at: string;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export interface MessageRecipient {
|
|
49
|
-
id: string;
|
|
50
|
-
recipient_type: string;
|
|
51
|
-
recipient_id: string | null;
|
|
52
|
-
recipient_email: string;
|
|
53
|
-
recipient_name: string | null;
|
|
54
|
-
channel_identifier: string | null;
|
|
55
|
-
channel_profiles: Record<string, any>;
|
|
56
|
-
status: RecipientStatus;
|
|
57
|
-
sent_at: string | null;
|
|
58
|
-
delivered_at: string | null;
|
|
59
|
-
bounced_at: string | null;
|
|
60
|
-
first_opened_at: string | null;
|
|
61
|
-
last_opened_at: string | null;
|
|
62
|
-
first_clicked_at: string | null;
|
|
63
|
-
last_clicked_at: string | null;
|
|
64
|
-
open_count: number;
|
|
65
|
-
click_count: number;
|
|
66
|
-
is_unsubscribed: boolean;
|
|
67
|
-
unsubscribed_at: string | null;
|
|
68
|
-
error_message: string | null;
|
|
69
|
-
created_at: string;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export interface MessagingChannel {
|
|
73
|
-
id: string;
|
|
74
|
-
key: string;
|
|
75
|
-
name: string;
|
|
76
|
-
description: string;
|
|
77
|
-
icon: string;
|
|
78
|
-
capabilities: {
|
|
79
|
-
max_content_length: number;
|
|
80
|
-
supports_html: boolean;
|
|
81
|
-
supports_markdown: boolean;
|
|
82
|
-
supports_attachments: boolean;
|
|
83
|
-
max_attachments: number;
|
|
84
|
-
max_attachment_size: number;
|
|
85
|
-
};
|
|
86
|
-
requirements: {
|
|
87
|
-
requires_connection: boolean;
|
|
88
|
-
requires_opt_in: boolean;
|
|
89
|
-
auth_requirements: Record<string, any>;
|
|
90
|
-
};
|
|
91
|
-
rate_limit: {
|
|
92
|
-
messages: number;
|
|
93
|
-
seconds: number;
|
|
94
|
-
scope: string;
|
|
95
|
-
description: string;
|
|
96
|
-
};
|
|
97
|
-
metadata: Record<string, any>;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
export interface MessageFilters {
|
|
101
|
-
status?: MessageStatus;
|
|
102
|
-
content_type?: MessageContentType;
|
|
103
|
-
scheduled_after?: string;
|
|
104
|
-
scheduled_before?: string;
|
|
105
|
-
sent_after?: string;
|
|
106
|
-
sent_before?: string;
|
|
107
|
-
search?: string;
|
|
108
|
-
page?: number;
|
|
109
|
-
page_size?: number;
|
|
110
|
-
ordering?: string;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export interface CreateMessageInput {
|
|
114
|
-
channel: string;
|
|
115
|
-
subject: string;
|
|
116
|
-
body: string;
|
|
117
|
-
content_type?: MessageContentType;
|
|
118
|
-
from_name?: string;
|
|
119
|
-
from_email?: string;
|
|
120
|
-
reply_to?: string;
|
|
121
|
-
scheduled_at?: string;
|
|
122
|
-
metadata?: Record<string, any>;
|
|
123
|
-
recipients?: Array<{
|
|
124
|
-
recipient_type: string;
|
|
125
|
-
recipient_email: string;
|
|
126
|
-
recipient_name?: string;
|
|
127
|
-
channel_identifier?: string;
|
|
128
|
-
}>;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
export interface ScheduleMessageInput {
|
|
132
|
-
scheduled_at: string;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
export interface SendTestInput {
|
|
136
|
-
test_email?: string;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
export class MessagesApi {
|
|
140
|
-
constructor(private client: ApiClient) {}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* List messages with optional filters
|
|
144
|
-
*/
|
|
145
|
-
async list(filters?: MessageFilters) {
|
|
146
|
-
const params = new URLSearchParams();
|
|
147
|
-
|
|
148
|
-
if (filters?.status) params.append('status', filters.status);
|
|
149
|
-
if (filters?.content_type) params.append('content_type', filters.content_type);
|
|
150
|
-
if (filters?.scheduled_after) params.append('scheduled_after', filters.scheduled_after);
|
|
151
|
-
if (filters?.scheduled_before) params.append('scheduled_before', filters.scheduled_before);
|
|
152
|
-
if (filters?.sent_after) params.append('sent_after', filters.sent_after);
|
|
153
|
-
if (filters?.sent_before) params.append('sent_before', filters.sent_before);
|
|
154
|
-
if (filters?.search) params.append('search', filters.search);
|
|
155
|
-
if (filters?.page) params.append('page', String(filters.page));
|
|
156
|
-
if (filters?.page_size) params.append('page_size', String(filters.page_size));
|
|
157
|
-
if (filters?.ordering) params.append('ordering', filters.ordering);
|
|
158
|
-
|
|
159
|
-
return this.client.get<{ results: Message[]; count: number; next: string | null; previous: string | null }>(
|
|
160
|
-
`/api/v1/messages/?${params.toString()}`
|
|
161
|
-
);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Get message by ID
|
|
166
|
-
*/
|
|
167
|
-
async get(id: string) {
|
|
168
|
-
return this.client.get<Message>(`/api/v1/messages/${id}/`);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Create a new message
|
|
173
|
-
*/
|
|
174
|
-
async create(data: CreateMessageInput) {
|
|
175
|
-
return this.client.post<Message>('/api/v1/messages/', data);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Update message (draft only)
|
|
180
|
-
*/
|
|
181
|
-
async update(id: string, data: Partial<CreateMessageInput>) {
|
|
182
|
-
return this.client.patch<Message>(`/api/v1/messages/${id}/`, data);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Delete message (draft only)
|
|
187
|
-
*/
|
|
188
|
-
async delete(id: string) {
|
|
189
|
-
return this.client.delete(`/api/v1/messages/${id}/`);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Schedule message for future sending
|
|
194
|
-
*/
|
|
195
|
-
async schedule(id: string, input: ScheduleMessageInput) {
|
|
196
|
-
return this.client.post<{ id: string; status: MessageStatus; scheduled_at: string; message: string }>(
|
|
197
|
-
`/api/v1/messages/${id}/schedule/`,
|
|
198
|
-
input
|
|
199
|
-
);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* Send message immediately
|
|
204
|
-
*/
|
|
205
|
-
async sendNow(id: string) {
|
|
206
|
-
return this.client.post<{ id: string; status: MessageStatus; message: string }>(
|
|
207
|
-
`/api/v1/messages/${id}/send_now/`,
|
|
208
|
-
{}
|
|
209
|
-
);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Send test message
|
|
214
|
-
*/
|
|
215
|
-
async sendTest(id: string, input?: SendTestInput) {
|
|
216
|
-
return this.client.post<{ id: string; test_email: string; message: string }>(
|
|
217
|
-
`/api/v1/messages/${id}/send_test/`,
|
|
218
|
-
input || {}
|
|
219
|
-
);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
/**
|
|
223
|
-
* Preview message rendering
|
|
224
|
-
*/
|
|
225
|
-
async preview(id: string) {
|
|
226
|
-
return this.client.get<{
|
|
227
|
-
subject: string;
|
|
228
|
-
body: string;
|
|
229
|
-
preview_html: string;
|
|
230
|
-
from_name: string;
|
|
231
|
-
from_email: string;
|
|
232
|
-
}>(`/api/v1/messages/${id}/preview/`);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* List recipients for a message
|
|
237
|
-
*/
|
|
238
|
-
async getRecipients(id: string, page?: number, pageSize?: number) {
|
|
239
|
-
const params = new URLSearchParams();
|
|
240
|
-
if (page) params.append('page', String(page));
|
|
241
|
-
if (pageSize) params.append('page_size', String(pageSize));
|
|
242
|
-
|
|
243
|
-
return this.client.get<{ results: MessageRecipient[]; count: number }>(
|
|
244
|
-
`/api/v1/messages/${id}/recipients/?${params.toString()}`
|
|
245
|
-
);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
* Add recipients to a message
|
|
250
|
-
*/
|
|
251
|
-
async addRecipients(
|
|
252
|
-
id: string,
|
|
253
|
-
recipients: Array<{
|
|
254
|
-
recipient_type: string;
|
|
255
|
-
recipient_email: string;
|
|
256
|
-
recipient_name?: string;
|
|
257
|
-
channel_identifier?: string;
|
|
258
|
-
}>
|
|
259
|
-
) {
|
|
260
|
-
return this.client.post<{
|
|
261
|
-
message: string;
|
|
262
|
-
total_recipients: number;
|
|
263
|
-
recipients: MessageRecipient[];
|
|
264
|
-
}>(`/api/v1/messages/${id}/add_recipients/`, { recipients });
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
/**
|
|
268
|
-
* Get available messaging channels
|
|
269
|
-
*/
|
|
270
|
-
async getChannels() {
|
|
271
|
-
return this.client.get<{ channels: MessagingChannel[]; count: number }>('/api/v1/messages/channels/');
|
|
272
|
-
}
|
|
273
|
-
}
|