@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
package/dist/index.js
ADDED
|
@@ -0,0 +1,2092 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var DOMPurify = require('isomorphic-dompurify');
|
|
4
|
+
var zod = require('zod');
|
|
5
|
+
var react = require('react');
|
|
6
|
+
|
|
7
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
8
|
+
|
|
9
|
+
var DOMPurify__default = /*#__PURE__*/_interopDefault(DOMPurify);
|
|
10
|
+
|
|
11
|
+
// src/utils/url-builder.ts
|
|
12
|
+
function buildUrl({ baseUrl, endpoint, params, id }) {
|
|
13
|
+
const cleanBase = baseUrl.replace(/\/$/, "");
|
|
14
|
+
const cleanEndpoint = endpoint.replace(/^\//, "");
|
|
15
|
+
let path = `${cleanBase}/${cleanEndpoint}`;
|
|
16
|
+
if (id !== void 0) {
|
|
17
|
+
path += `/${id}`;
|
|
18
|
+
}
|
|
19
|
+
if (!path.endsWith("/")) {
|
|
20
|
+
path += "/";
|
|
21
|
+
}
|
|
22
|
+
if (params && Object.keys(params).length > 0) {
|
|
23
|
+
const queryString = buildQueryString(params);
|
|
24
|
+
if (queryString) {
|
|
25
|
+
path += `?${queryString}`;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return path;
|
|
29
|
+
}
|
|
30
|
+
function buildQueryString(params) {
|
|
31
|
+
const searchParams = new URLSearchParams();
|
|
32
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
33
|
+
if (value === void 0 || value === null) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (Array.isArray(value)) {
|
|
37
|
+
value.forEach((item) => {
|
|
38
|
+
searchParams.append(key, String(item));
|
|
39
|
+
});
|
|
40
|
+
} else if (typeof value === "object") {
|
|
41
|
+
searchParams.append(key, JSON.stringify(value));
|
|
42
|
+
} else {
|
|
43
|
+
searchParams.append(key, String(value));
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
return searchParams.toString();
|
|
47
|
+
}
|
|
48
|
+
function resolveApiUrl(path, baseUrl = "") {
|
|
49
|
+
if (/^https?:\/\//i.test(path)) return path;
|
|
50
|
+
const normalized = path.startsWith("/") ? path : `/${path}`;
|
|
51
|
+
if (!baseUrl) return normalized;
|
|
52
|
+
if (/^https?:\/\//i.test(baseUrl)) {
|
|
53
|
+
return new URL(normalized, baseUrl).toString();
|
|
54
|
+
}
|
|
55
|
+
const cleanBase = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
|
|
56
|
+
return `${cleanBase}${normalized}`;
|
|
57
|
+
}
|
|
58
|
+
function normalizeId(id) {
|
|
59
|
+
if (typeof id === "string" && id.includes("/")) {
|
|
60
|
+
const parts = id.split("/").filter(Boolean);
|
|
61
|
+
return parts[parts.length - 1];
|
|
62
|
+
}
|
|
63
|
+
return id;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// src/utils/query-params.ts
|
|
67
|
+
function buildFilterParams(filters) {
|
|
68
|
+
const params = {};
|
|
69
|
+
Object.entries(filters).forEach(([key, value]) => {
|
|
70
|
+
if (value === void 0 || value === null) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (typeof value === "boolean") {
|
|
74
|
+
params[key] = value;
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (Array.isArray(value)) {
|
|
78
|
+
if (value.length > 0) {
|
|
79
|
+
params[`${key}__in`] = value.join(",");
|
|
80
|
+
}
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (key.endsWith("_gte") || key.endsWith("_lte") || key.endsWith("_gt") || key.endsWith("_lt")) {
|
|
84
|
+
params[key] = value;
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
params[key] = value;
|
|
88
|
+
});
|
|
89
|
+
return params;
|
|
90
|
+
}
|
|
91
|
+
function buildOrderingParam(field, direction = "asc") {
|
|
92
|
+
if (!field) {
|
|
93
|
+
return void 0;
|
|
94
|
+
}
|
|
95
|
+
return direction === "desc" ? `-${field}` : field;
|
|
96
|
+
}
|
|
97
|
+
function mergeQueryParams(pagination, sorting, filters) {
|
|
98
|
+
return {
|
|
99
|
+
...filters,
|
|
100
|
+
...pagination,
|
|
101
|
+
...sorting
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// src/utils/validate-response.ts
|
|
106
|
+
function validateApiResponse(data, schema, context) {
|
|
107
|
+
const result = schema.safeParse(data);
|
|
108
|
+
if (!result.success) {
|
|
109
|
+
const label = context ? `[${context}]` : "[API validation]";
|
|
110
|
+
console.warn(
|
|
111
|
+
`${label} Schema mismatch \u2014 returning raw data.`,
|
|
112
|
+
result.error.flatten().fieldErrors
|
|
113
|
+
);
|
|
114
|
+
return data;
|
|
115
|
+
}
|
|
116
|
+
return result.data;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// src/utils/entity-query-builder.ts
|
|
120
|
+
var EntityQueryBuilder = class {
|
|
121
|
+
constructor() {
|
|
122
|
+
this.params = /* @__PURE__ */ new Map();
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Filter by entity type
|
|
126
|
+
*/
|
|
127
|
+
entityType(type) {
|
|
128
|
+
this.params.set("entity_type", type);
|
|
129
|
+
return this;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Add tag filters (compact format: "category:name")
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* .withTags('quality:tier_1', 'status:prospect')
|
|
136
|
+
* // produces: tags=quality:tier_1,status:prospect
|
|
137
|
+
*/
|
|
138
|
+
withTags(...tags) {
|
|
139
|
+
if (tags.length > 0) {
|
|
140
|
+
const existing = this.params.get("tags");
|
|
141
|
+
const combined = existing ? `${existing},${tags.join(",")}` : tags.join(",");
|
|
142
|
+
this.params.set("tags", combined);
|
|
143
|
+
}
|
|
144
|
+
return this;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Add metric filters (compact format: "type:subtype__operator:value")
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* .withMetrics('financial:aum__gte:100000000', 'check_size:min__gte:1000000')
|
|
151
|
+
*/
|
|
152
|
+
withMetrics(...metrics) {
|
|
153
|
+
if (metrics.length > 0) {
|
|
154
|
+
const existing = this.params.get("metrics");
|
|
155
|
+
const combined = existing ? `${existing},${metrics.join(",")}` : metrics.join(",");
|
|
156
|
+
this.params.set("metrics", combined);
|
|
157
|
+
}
|
|
158
|
+
return this;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Add profile filters (compact format: "type:subtype")
|
|
162
|
+
*
|
|
163
|
+
* @example
|
|
164
|
+
* .withProfiles('professional:linkedin', 'social:twitter')
|
|
165
|
+
*/
|
|
166
|
+
withProfiles(...profiles) {
|
|
167
|
+
if (profiles.length > 0) {
|
|
168
|
+
const existing = this.params.get("profiles");
|
|
169
|
+
const combined = existing ? `${existing},${profiles.join(",")}` : profiles.join(",");
|
|
170
|
+
this.params.set("profiles", combined);
|
|
171
|
+
}
|
|
172
|
+
return this;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Add attribute filters (compact format: "type:subtype:value")
|
|
176
|
+
*
|
|
177
|
+
* @example
|
|
178
|
+
* .withAttributes('demographic:location:san_francisco')
|
|
179
|
+
*/
|
|
180
|
+
withAttributes(...attrs) {
|
|
181
|
+
if (attrs.length > 0) {
|
|
182
|
+
const existing = this.params.get("attributes");
|
|
183
|
+
const combined = existing ? `${existing},${attrs.join(",")}` : attrs.join(",");
|
|
184
|
+
this.params.set("attributes", combined);
|
|
185
|
+
}
|
|
186
|
+
return this;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Set pagination parameters
|
|
190
|
+
*/
|
|
191
|
+
paginate(page, pageSize) {
|
|
192
|
+
this.params.set("page", String(page));
|
|
193
|
+
this.params.set("page_size", String(pageSize));
|
|
194
|
+
return this;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Set search query
|
|
198
|
+
*/
|
|
199
|
+
search(query) {
|
|
200
|
+
const trimmed = query.trim();
|
|
201
|
+
if (trimmed) {
|
|
202
|
+
this.params.set("search", trimmed);
|
|
203
|
+
}
|
|
204
|
+
return this;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Set sort field and direction (maps to Django's `ordering` param)
|
|
208
|
+
*/
|
|
209
|
+
sort(field, direction = "asc") {
|
|
210
|
+
const prefix = direction === "desc" ? "-" : "";
|
|
211
|
+
this.params.set("ordering", `${prefix}${field}`);
|
|
212
|
+
return this;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Add a date range filter on a given field
|
|
216
|
+
*
|
|
217
|
+
* @example
|
|
218
|
+
* .withDateRange('created', new Date('2024-01-01'), new Date('2024-12-31'))
|
|
219
|
+
* // produces: created_after=2024-01-01&created_before=2024-12-31
|
|
220
|
+
*/
|
|
221
|
+
withDateRange(field, from, to) {
|
|
222
|
+
if (from) {
|
|
223
|
+
this.params.set(`${field}_after`, from.toISOString().split("T")[0]);
|
|
224
|
+
}
|
|
225
|
+
if (to) {
|
|
226
|
+
this.params.set(`${field}_before`, to.toISOString().split("T")[0]);
|
|
227
|
+
}
|
|
228
|
+
return this;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Set an arbitrary query parameter
|
|
232
|
+
*/
|
|
233
|
+
param(key, value) {
|
|
234
|
+
this.params.set(key, value);
|
|
235
|
+
return this;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Build as a plain object (suitable for URLSearchParams or fetch helpers)
|
|
239
|
+
*/
|
|
240
|
+
build() {
|
|
241
|
+
const result = {};
|
|
242
|
+
this.params.forEach((value, key) => {
|
|
243
|
+
result[key] = value;
|
|
244
|
+
});
|
|
245
|
+
return result;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Build as a query string (includes leading `?`)
|
|
249
|
+
* Returns empty string if no params.
|
|
250
|
+
*/
|
|
251
|
+
toQueryString() {
|
|
252
|
+
if (this.params.size === 0) return "";
|
|
253
|
+
const searchParams = new URLSearchParams();
|
|
254
|
+
this.params.forEach((value, key) => {
|
|
255
|
+
searchParams.set(key, value);
|
|
256
|
+
});
|
|
257
|
+
return `?${searchParams.toString()}`;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Reset all parameters
|
|
261
|
+
*/
|
|
262
|
+
clear() {
|
|
263
|
+
this.params.clear();
|
|
264
|
+
return this;
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
// src/utils/drf-transforms.ts
|
|
269
|
+
function normalizePaginated(response, page, pageSize) {
|
|
270
|
+
const total = response.count || 0;
|
|
271
|
+
return {
|
|
272
|
+
items: response.results || [],
|
|
273
|
+
total,
|
|
274
|
+
page,
|
|
275
|
+
pageSize,
|
|
276
|
+
hasNext: response.next !== null,
|
|
277
|
+
hasPrev: response.previous !== null
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
function isDRFPaginatedResponse(response) {
|
|
281
|
+
return typeof response === "object" && response !== null && "results" in response && "count" in response;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// src/lib/error-handler.ts
|
|
285
|
+
var ApiException = class extends Error {
|
|
286
|
+
constructor(message, options) {
|
|
287
|
+
super(message);
|
|
288
|
+
this.name = "ApiException";
|
|
289
|
+
this.status = options?.status;
|
|
290
|
+
this.statusText = options?.statusText;
|
|
291
|
+
this.errors = options?.errors;
|
|
292
|
+
this.detail = options?.detail;
|
|
293
|
+
}
|
|
294
|
+
toJSON() {
|
|
295
|
+
return {
|
|
296
|
+
message: this.message,
|
|
297
|
+
detail: this.detail,
|
|
298
|
+
status: this.status,
|
|
299
|
+
statusText: this.statusText,
|
|
300
|
+
errors: this.errors
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
async function parseErrorResponse(response) {
|
|
305
|
+
const contentType = response.headers?.get("content-type");
|
|
306
|
+
if (contentType?.includes("application/json")) {
|
|
307
|
+
try {
|
|
308
|
+
const data = await response.json();
|
|
309
|
+
if (data.detail) {
|
|
310
|
+
return {
|
|
311
|
+
detail: data.detail,
|
|
312
|
+
message: data.detail,
|
|
313
|
+
status: response.status,
|
|
314
|
+
statusText: response.statusText
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
if (typeof data === "object") {
|
|
318
|
+
return {
|
|
319
|
+
errors: data,
|
|
320
|
+
message: "Validation error",
|
|
321
|
+
status: response.status,
|
|
322
|
+
statusText: response.statusText
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
return {
|
|
326
|
+
message: JSON.stringify(data),
|
|
327
|
+
status: response.status,
|
|
328
|
+
statusText: response.statusText
|
|
329
|
+
};
|
|
330
|
+
} catch {
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
const text = await response.text().catch(() => response.statusText);
|
|
334
|
+
return {
|
|
335
|
+
message: text || response.statusText || "Unknown error",
|
|
336
|
+
status: response.status,
|
|
337
|
+
statusText: response.statusText
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
function handleFetchError(error) {
|
|
341
|
+
if (error instanceof ApiException) {
|
|
342
|
+
throw error;
|
|
343
|
+
}
|
|
344
|
+
if (error instanceof TypeError && error.message.includes("fetch")) {
|
|
345
|
+
throw new ApiException("Network error - please check your connection", {
|
|
346
|
+
status: 0,
|
|
347
|
+
statusText: "Network Error"
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
if (error instanceof Error) {
|
|
351
|
+
throw new ApiException(error.message, {
|
|
352
|
+
status: 0,
|
|
353
|
+
statusText: "Client Error"
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
throw new ApiException("Unknown error occurred", {
|
|
357
|
+
status: 0,
|
|
358
|
+
statusText: "Unknown Error"
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
function isApiException(error) {
|
|
362
|
+
return error instanceof ApiException;
|
|
363
|
+
}
|
|
364
|
+
function isValidationError(error) {
|
|
365
|
+
return isApiException(error) && error.status === 400 && !!error.errors;
|
|
366
|
+
}
|
|
367
|
+
function isAuthError(error) {
|
|
368
|
+
return isApiException(error) && (error.status === 401 || error.status === 403);
|
|
369
|
+
}
|
|
370
|
+
function isNotFoundError(error) {
|
|
371
|
+
return isApiException(error) && error.status === 404;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// src/lib/fetch-wrapper.ts
|
|
375
|
+
var FetchWrapper = class _FetchWrapper {
|
|
376
|
+
constructor(config) {
|
|
377
|
+
this.isRefreshing = false;
|
|
378
|
+
this.refreshPromise = null;
|
|
379
|
+
this.config = config;
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Build headers with auth token
|
|
383
|
+
*/
|
|
384
|
+
async buildHeaders(customHeaders) {
|
|
385
|
+
const headers = new Headers(this.config.defaultHeaders);
|
|
386
|
+
if (customHeaders) {
|
|
387
|
+
const customHeadersObj = new Headers(customHeaders);
|
|
388
|
+
customHeadersObj.forEach((value, key) => {
|
|
389
|
+
headers.set(key, value);
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
if (this.config.getToken) {
|
|
393
|
+
const token = await this.config.getToken();
|
|
394
|
+
if (token) {
|
|
395
|
+
headers.set("Authorization", `Bearer ${token}`);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
if (!headers.has("Content-Type")) {
|
|
399
|
+
headers.set("Content-Type", "application/json");
|
|
400
|
+
}
|
|
401
|
+
return headers;
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Execute fetch request
|
|
405
|
+
*/
|
|
406
|
+
async execute(method, endpoint, options) {
|
|
407
|
+
try {
|
|
408
|
+
const { params, headers: customHeaders, ...fetchOptions } = options || {};
|
|
409
|
+
const url = buildUrl({
|
|
410
|
+
baseUrl: this.config.baseUrl ?? "",
|
|
411
|
+
endpoint,
|
|
412
|
+
params
|
|
413
|
+
});
|
|
414
|
+
const headers = await this.buildHeaders(customHeaders);
|
|
415
|
+
let response = await fetch(url, {
|
|
416
|
+
method,
|
|
417
|
+
headers,
|
|
418
|
+
credentials: "include",
|
|
419
|
+
...fetchOptions
|
|
420
|
+
});
|
|
421
|
+
if (response.status === 401) {
|
|
422
|
+
if (this.config.onTokenRefresh) {
|
|
423
|
+
if (!this.isRefreshing) {
|
|
424
|
+
this.isRefreshing = true;
|
|
425
|
+
this.refreshPromise = this.config.onTokenRefresh().finally(() => {
|
|
426
|
+
this.isRefreshing = false;
|
|
427
|
+
this.refreshPromise = null;
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
const newToken = await this.refreshPromise;
|
|
431
|
+
if (newToken) {
|
|
432
|
+
const retryHeaders = new Headers(headers);
|
|
433
|
+
retryHeaders.set("Authorization", `Bearer ${newToken}`);
|
|
434
|
+
response = await fetch(url, {
|
|
435
|
+
method,
|
|
436
|
+
headers: retryHeaders,
|
|
437
|
+
credentials: "include",
|
|
438
|
+
...fetchOptions
|
|
439
|
+
});
|
|
440
|
+
} else if (this.config.onUnauthorized) {
|
|
441
|
+
this.config.onUnauthorized();
|
|
442
|
+
}
|
|
443
|
+
} else if (this.config.onUnauthorized) {
|
|
444
|
+
this.config.onUnauthorized();
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
if (!response.ok) {
|
|
448
|
+
const error = await parseErrorResponse(response);
|
|
449
|
+
throw new ApiException(error.message || "Request failed", error);
|
|
450
|
+
}
|
|
451
|
+
if (response.status === 204) {
|
|
452
|
+
return void 0;
|
|
453
|
+
}
|
|
454
|
+
return await response.json();
|
|
455
|
+
} catch (error) {
|
|
456
|
+
handleFetchError(error);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Return a new FetchWrapper with selected config fields overridden.
|
|
461
|
+
*/
|
|
462
|
+
reconfigure(partial) {
|
|
463
|
+
return new _FetchWrapper({ ...this.config, ...partial });
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* GET request
|
|
467
|
+
*/
|
|
468
|
+
async get(endpoint, options) {
|
|
469
|
+
return this.execute("GET", endpoint, options);
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* POST request
|
|
473
|
+
*/
|
|
474
|
+
async post(endpoint, data, options) {
|
|
475
|
+
return this.execute("POST", endpoint, {
|
|
476
|
+
...options,
|
|
477
|
+
body: data ? JSON.stringify(data) : void 0
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* PUT request
|
|
482
|
+
*/
|
|
483
|
+
async put(endpoint, data, options) {
|
|
484
|
+
return this.execute("PUT", endpoint, {
|
|
485
|
+
...options,
|
|
486
|
+
body: data ? JSON.stringify(data) : void 0
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* PATCH request
|
|
491
|
+
*/
|
|
492
|
+
async patch(endpoint, data, options) {
|
|
493
|
+
return this.execute("PATCH", endpoint, {
|
|
494
|
+
...options,
|
|
495
|
+
body: data ? JSON.stringify(data) : void 0
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* DELETE request
|
|
500
|
+
*/
|
|
501
|
+
async delete(endpoint, options) {
|
|
502
|
+
return this.execute("DELETE", endpoint, options);
|
|
503
|
+
}
|
|
504
|
+
};
|
|
505
|
+
|
|
506
|
+
// src/lib/api-client.ts
|
|
507
|
+
var ApiClient = class {
|
|
508
|
+
constructor(config) {
|
|
509
|
+
this.baseUrl = config.baseUrl ?? "";
|
|
510
|
+
this.fetcher = new FetchWrapper({
|
|
511
|
+
baseUrl: config.baseUrl,
|
|
512
|
+
getToken: config.getToken,
|
|
513
|
+
onUnauthorized: config.onUnauthorized,
|
|
514
|
+
onTokenRefresh: config.onTokenRefresh,
|
|
515
|
+
defaultHeaders: {
|
|
516
|
+
"Content-Type": "application/json",
|
|
517
|
+
"Accept": "application/json"
|
|
518
|
+
}
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Get the fetch wrapper instance for direct access
|
|
523
|
+
*/
|
|
524
|
+
get fetch() {
|
|
525
|
+
return this.fetcher;
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Update auth token getter
|
|
529
|
+
*/
|
|
530
|
+
setTokenGetter(getToken) {
|
|
531
|
+
this.fetcher = this.fetcher.reconfigure({ getToken });
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Update unauthorized handler
|
|
535
|
+
*/
|
|
536
|
+
setUnauthorizedHandler(onUnauthorized) {
|
|
537
|
+
this.fetcher = this.fetcher.reconfigure({ onUnauthorized });
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* Convenience HTTP methods that delegate to FetchWrapper
|
|
541
|
+
*/
|
|
542
|
+
async get(endpoint, options) {
|
|
543
|
+
return this.fetcher.get(endpoint, options);
|
|
544
|
+
}
|
|
545
|
+
async post(endpoint, data, options) {
|
|
546
|
+
return this.fetcher.post(endpoint, data, options);
|
|
547
|
+
}
|
|
548
|
+
async patch(endpoint, data, options) {
|
|
549
|
+
return this.fetcher.patch(endpoint, data, options);
|
|
550
|
+
}
|
|
551
|
+
async delete(endpoint, options) {
|
|
552
|
+
return this.fetcher.delete(endpoint, options);
|
|
553
|
+
}
|
|
554
|
+
};
|
|
555
|
+
function createApiClient(config) {
|
|
556
|
+
return new ApiClient(config);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// src/constants/endpoints.ts
|
|
560
|
+
var ENDPOINTS = {
|
|
561
|
+
// Contacts
|
|
562
|
+
CONTACTS: "contacts",
|
|
563
|
+
CONTACTS_BULK: "contacts/bulk",
|
|
564
|
+
CONTACT: (id) => `contacts/${id}`,
|
|
565
|
+
// Organizations
|
|
566
|
+
ORGANIZATIONS: "organizations",
|
|
567
|
+
ORGANIZATIONS_BULK: "organizations/bulk",
|
|
568
|
+
ORGANIZATION: (id) => `organizations/${id}`,
|
|
569
|
+
// Core entities
|
|
570
|
+
TAGS: "core/tags",
|
|
571
|
+
TAG: (id) => `core/tags/${id}`,
|
|
572
|
+
ENTITY_TAGS: "core/entity-tags",
|
|
573
|
+
ENTITY_TAG: (id) => `core/entity-tags/${id}`,
|
|
574
|
+
METRICS: "core/metrics",
|
|
575
|
+
METRIC: (id) => `core/metrics/${id}`,
|
|
576
|
+
PROFILES: "core/profiles",
|
|
577
|
+
PROFILE: (id) => `core/profiles/${id}`,
|
|
578
|
+
ATTRIBUTES: "core/attributes",
|
|
579
|
+
ATTRIBUTE: (id) => `core/attributes/${id}`,
|
|
580
|
+
RELATIONSHIPS: "core/relationships",
|
|
581
|
+
RELATIONSHIP: (id) => `core/relationships/${id}`,
|
|
582
|
+
// Workflows
|
|
583
|
+
WORKFLOWS: "workflows",
|
|
584
|
+
WORKFLOW: (id) => `workflows/${id}`,
|
|
585
|
+
WORKFLOW_EXECUTE: (id) => `workflows/${id}/execute`,
|
|
586
|
+
// Messages
|
|
587
|
+
MESSAGES: "messages",
|
|
588
|
+
MESSAGE: (id) => `messages/${id}`,
|
|
589
|
+
MESSAGE_SEND: (id) => `messages/${id}/send`,
|
|
590
|
+
// Funnels
|
|
591
|
+
FUNNELS: "funnels",
|
|
592
|
+
FUNNEL: (id) => `funnels/${id}`,
|
|
593
|
+
FUNNEL_RUN: (id) => `funnels/${id}/run`,
|
|
594
|
+
FUNNEL_RUNS: (id) => `funnels/${id}/runs`,
|
|
595
|
+
FUNNEL_RUN_ITEM: (funnelId, runId) => `funnels/${funnelId}/runs/${runId}`,
|
|
596
|
+
FUNNEL_PREVIEW: (id) => `funnels/${id}/preview`,
|
|
597
|
+
FUNNEL_RESULTS: (id) => `funnels/${id}/results`,
|
|
598
|
+
FUNNEL_TEMPLATES: "funnels/templates",
|
|
599
|
+
FUNNEL_RUN_BY_ID: (runId) => `funnel-runs/${runId}`,
|
|
600
|
+
FUNNEL_RUN_CANCEL: (runId) => `funnel-runs/${runId}/cancel`,
|
|
601
|
+
FUNNEL_RUNS_GLOBAL: "funnel-runs"
|
|
602
|
+
};
|
|
603
|
+
|
|
604
|
+
// src/lib/contacts-api.ts
|
|
605
|
+
var ContactsApi = class {
|
|
606
|
+
constructor(client) {
|
|
607
|
+
this.client = client;
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* List contacts with pagination and filters
|
|
611
|
+
*/
|
|
612
|
+
async list(filters, pagination, sorting) {
|
|
613
|
+
const params = mergeQueryParams(
|
|
614
|
+
pagination,
|
|
615
|
+
sorting,
|
|
616
|
+
filters ? buildFilterParams(filters) : void 0
|
|
617
|
+
);
|
|
618
|
+
return this.client.fetch.get(ENDPOINTS.CONTACTS, { params });
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Get contact by ID
|
|
622
|
+
*/
|
|
623
|
+
async get(id) {
|
|
624
|
+
return this.client.fetch.get(ENDPOINTS.CONTACT(id));
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* Create new contact
|
|
628
|
+
*/
|
|
629
|
+
async create(data) {
|
|
630
|
+
return this.client.fetch.post(ENDPOINTS.CONTACTS, data);
|
|
631
|
+
}
|
|
632
|
+
/**
|
|
633
|
+
* Update contact
|
|
634
|
+
*/
|
|
635
|
+
async update(id, data) {
|
|
636
|
+
return this.client.fetch.patch(ENDPOINTS.CONTACT(id), data);
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* Delete contact
|
|
640
|
+
*/
|
|
641
|
+
async delete(id) {
|
|
642
|
+
return this.client.fetch.delete(ENDPOINTS.CONTACT(id));
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* Bulk create contacts
|
|
646
|
+
*/
|
|
647
|
+
async bulkCreate(data) {
|
|
648
|
+
return this.client.fetch.post(ENDPOINTS.CONTACTS_BULK, data);
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* Search contacts by name, email, or company
|
|
652
|
+
*/
|
|
653
|
+
async search(query, pagination) {
|
|
654
|
+
return this.list({ search: query }, pagination);
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Get contacts by tier
|
|
658
|
+
*/
|
|
659
|
+
async getByTier(tier, pagination) {
|
|
660
|
+
return this.list({ tier }, pagination);
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* Get contacts by firm
|
|
664
|
+
*/
|
|
665
|
+
async getByFirm(firmId, pagination) {
|
|
666
|
+
return this.list({ firmId }, pagination);
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* Get contacts with LinkedIn profiles
|
|
670
|
+
*/
|
|
671
|
+
async getWithLinkedIn(pagination) {
|
|
672
|
+
return this.list({ hasLinkedin: true }, pagination);
|
|
673
|
+
}
|
|
674
|
+
};
|
|
675
|
+
|
|
676
|
+
// src/lib/organizations-api.ts
|
|
677
|
+
var OrganizationsApi = class {
|
|
678
|
+
constructor(client) {
|
|
679
|
+
this.client = client;
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* List organizations with pagination and filters
|
|
683
|
+
*/
|
|
684
|
+
async list(filters, pagination, sorting) {
|
|
685
|
+
const params = mergeQueryParams(
|
|
686
|
+
pagination,
|
|
687
|
+
sorting,
|
|
688
|
+
filters ? buildFilterParams(filters) : void 0
|
|
689
|
+
);
|
|
690
|
+
return this.client.fetch.get(ENDPOINTS.ORGANIZATIONS, {
|
|
691
|
+
params
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Get organization by ID
|
|
696
|
+
*/
|
|
697
|
+
async get(id) {
|
|
698
|
+
return this.client.fetch.get(ENDPOINTS.ORGANIZATION(id));
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* Create new organization
|
|
702
|
+
*/
|
|
703
|
+
async create(data) {
|
|
704
|
+
return this.client.fetch.post(ENDPOINTS.ORGANIZATIONS, data);
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* Update organization
|
|
708
|
+
*/
|
|
709
|
+
async update(id, data) {
|
|
710
|
+
return this.client.fetch.patch(ENDPOINTS.ORGANIZATION(id), data);
|
|
711
|
+
}
|
|
712
|
+
/**
|
|
713
|
+
* Delete organization
|
|
714
|
+
*/
|
|
715
|
+
async delete(id) {
|
|
716
|
+
return this.client.fetch.delete(ENDPOINTS.ORGANIZATION(id));
|
|
717
|
+
}
|
|
718
|
+
/**
|
|
719
|
+
* Bulk create organizations
|
|
720
|
+
*/
|
|
721
|
+
async bulkCreate(data) {
|
|
722
|
+
return this.client.fetch.post(ENDPOINTS.ORGANIZATIONS_BULK, data);
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* Search organizations by name, domain, or location
|
|
726
|
+
*/
|
|
727
|
+
async search(query, pagination) {
|
|
728
|
+
return this.list({ search: query }, pagination);
|
|
729
|
+
}
|
|
730
|
+
/**
|
|
731
|
+
* Get organizations by tier
|
|
732
|
+
*/
|
|
733
|
+
async getByTier(tier, pagination) {
|
|
734
|
+
return this.list({ tier }, pagination);
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* Get organizations by stage
|
|
738
|
+
*/
|
|
739
|
+
async getByStage(stage, pagination) {
|
|
740
|
+
return this.list({ stage }, pagination);
|
|
741
|
+
}
|
|
742
|
+
/**
|
|
743
|
+
* Get organizations by focus area
|
|
744
|
+
*/
|
|
745
|
+
async getByFocusArea(focusArea, pagination) {
|
|
746
|
+
return this.list({ focusArea }, pagination);
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* Get organizations by check size range
|
|
750
|
+
*/
|
|
751
|
+
async getByCheckSizeRange(min, max, pagination) {
|
|
752
|
+
const filters = {};
|
|
753
|
+
if (min !== void 0) {
|
|
754
|
+
filters.checkSizeMinGte = min;
|
|
755
|
+
}
|
|
756
|
+
if (max !== void 0) {
|
|
757
|
+
filters.checkSizeMaxLte = max;
|
|
758
|
+
}
|
|
759
|
+
return this.list(filters, pagination);
|
|
760
|
+
}
|
|
761
|
+
};
|
|
762
|
+
|
|
763
|
+
// src/lib/entities-api.ts
|
|
764
|
+
var EntitiesApi = class {
|
|
765
|
+
constructor(client) {
|
|
766
|
+
this.client = client;
|
|
767
|
+
}
|
|
768
|
+
/**
|
|
769
|
+
* Tags
|
|
770
|
+
*/
|
|
771
|
+
async listTags(pagination) {
|
|
772
|
+
const params = mergeQueryParams(pagination);
|
|
773
|
+
return this.client.fetch.get(ENDPOINTS.TAGS, { params });
|
|
774
|
+
}
|
|
775
|
+
async getTag(id) {
|
|
776
|
+
return this.client.fetch.get(ENDPOINTS.TAG(String(id)));
|
|
777
|
+
}
|
|
778
|
+
async createTag(data) {
|
|
779
|
+
return this.client.fetch.post(ENDPOINTS.TAGS, data);
|
|
780
|
+
}
|
|
781
|
+
/**
|
|
782
|
+
* Entity Tags
|
|
783
|
+
*/
|
|
784
|
+
async listEntityTags(pagination, filters) {
|
|
785
|
+
const params = mergeQueryParams(pagination, void 0, filters);
|
|
786
|
+
return this.client.fetch.get(ENDPOINTS.ENTITY_TAGS, {
|
|
787
|
+
params
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
async getEntityTag(id) {
|
|
791
|
+
return this.client.fetch.get(ENDPOINTS.ENTITY_TAG(String(id)));
|
|
792
|
+
}
|
|
793
|
+
/**
|
|
794
|
+
* Metrics
|
|
795
|
+
*/
|
|
796
|
+
async listMetrics(pagination, filters) {
|
|
797
|
+
const params = mergeQueryParams(pagination, void 0, filters);
|
|
798
|
+
return this.client.fetch.get(ENDPOINTS.METRICS, { params });
|
|
799
|
+
}
|
|
800
|
+
async getMetric(id) {
|
|
801
|
+
return this.client.fetch.get(ENDPOINTS.METRIC(String(id)));
|
|
802
|
+
}
|
|
803
|
+
/**
|
|
804
|
+
* Profiles
|
|
805
|
+
*/
|
|
806
|
+
async listProfiles(pagination, filters) {
|
|
807
|
+
const params = mergeQueryParams(pagination, void 0, filters);
|
|
808
|
+
return this.client.fetch.get(ENDPOINTS.PROFILES, { params });
|
|
809
|
+
}
|
|
810
|
+
async getProfile(id) {
|
|
811
|
+
return this.client.fetch.get(ENDPOINTS.PROFILE(String(id)));
|
|
812
|
+
}
|
|
813
|
+
/**
|
|
814
|
+
* Attributes
|
|
815
|
+
*/
|
|
816
|
+
async listAttributes(pagination, filters) {
|
|
817
|
+
const params = mergeQueryParams(pagination, void 0, filters);
|
|
818
|
+
return this.client.fetch.get(ENDPOINTS.ATTRIBUTES, {
|
|
819
|
+
params
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
async getAttribute(id) {
|
|
823
|
+
return this.client.fetch.get(ENDPOINTS.ATTRIBUTE(String(id)));
|
|
824
|
+
}
|
|
825
|
+
/**
|
|
826
|
+
* Relationships
|
|
827
|
+
*/
|
|
828
|
+
async listRelationships(pagination, filters) {
|
|
829
|
+
const params = mergeQueryParams(pagination, void 0, filters);
|
|
830
|
+
return this.client.fetch.get(ENDPOINTS.RELATIONSHIPS, {
|
|
831
|
+
params
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
async getRelationship(id) {
|
|
835
|
+
return this.client.fetch.get(ENDPOINTS.RELATIONSHIP(String(id)));
|
|
836
|
+
}
|
|
837
|
+
};
|
|
838
|
+
|
|
839
|
+
// src/lib/workflows-api.ts
|
|
840
|
+
var WorkflowsApi = class {
|
|
841
|
+
constructor(client) {
|
|
842
|
+
this.client = client;
|
|
843
|
+
}
|
|
844
|
+
/**
|
|
845
|
+
* List workflows with optional filters
|
|
846
|
+
*/
|
|
847
|
+
async list(filters) {
|
|
848
|
+
const params = new URLSearchParams();
|
|
849
|
+
if (filters?.team) params.append("team", filters.team);
|
|
850
|
+
if (filters?.isActive !== void 0) params.append("isActive", String(filters.isActive));
|
|
851
|
+
if (filters?.isTemplate !== void 0) params.append("isTemplate", String(filters.isTemplate));
|
|
852
|
+
if (filters?.page) params.append("page", String(filters.page));
|
|
853
|
+
if (filters?.pageSize) params.append("pageSize", String(filters.pageSize));
|
|
854
|
+
if (filters?.ordering) params.append("ordering", filters.ordering);
|
|
855
|
+
return this.client.get(
|
|
856
|
+
`/api/v1/workflows/?${params.toString()}`
|
|
857
|
+
);
|
|
858
|
+
}
|
|
859
|
+
/**
|
|
860
|
+
* Get workflow by ID
|
|
861
|
+
*/
|
|
862
|
+
async get(id) {
|
|
863
|
+
return this.client.get(`/api/v1/workflows/${id}/`);
|
|
864
|
+
}
|
|
865
|
+
/**
|
|
866
|
+
* Create a new workflow
|
|
867
|
+
*/
|
|
868
|
+
async create(data) {
|
|
869
|
+
return this.client.post("/api/v1/workflows/", data);
|
|
870
|
+
}
|
|
871
|
+
/**
|
|
872
|
+
* Update workflow
|
|
873
|
+
*/
|
|
874
|
+
async update(id, data) {
|
|
875
|
+
return this.client.patch(`/api/v1/workflows/${id}/`, data);
|
|
876
|
+
}
|
|
877
|
+
/**
|
|
878
|
+
* Delete workflow
|
|
879
|
+
*/
|
|
880
|
+
async delete(id) {
|
|
881
|
+
return this.client.delete(`/api/v1/workflows/${id}/`);
|
|
882
|
+
}
|
|
883
|
+
/**
|
|
884
|
+
* Execute workflow
|
|
885
|
+
*/
|
|
886
|
+
async execute(id, input) {
|
|
887
|
+
return this.client.post(
|
|
888
|
+
`/api/v1/workflows/${id}/execute/`,
|
|
889
|
+
input || {}
|
|
890
|
+
);
|
|
891
|
+
}
|
|
892
|
+
/**
|
|
893
|
+
* Get workflow templates
|
|
894
|
+
*/
|
|
895
|
+
async templates() {
|
|
896
|
+
return this.client.get("/api/v1/workflows/templates/");
|
|
897
|
+
}
|
|
898
|
+
/**
|
|
899
|
+
* List workflow executions
|
|
900
|
+
*/
|
|
901
|
+
async listExecutions(filters) {
|
|
902
|
+
const params = new URLSearchParams();
|
|
903
|
+
if (filters?.workflow) params.append("workflow", filters.workflow);
|
|
904
|
+
if (filters?.status) params.append("status", filters.status);
|
|
905
|
+
if (filters?.page) params.append("page", String(filters.page));
|
|
906
|
+
if (filters?.pageSize) params.append("pageSize", String(filters.pageSize));
|
|
907
|
+
return this.client.get(
|
|
908
|
+
`/api/v1/workflow-executions/?${params.toString()}`
|
|
909
|
+
);
|
|
910
|
+
}
|
|
911
|
+
/**
|
|
912
|
+
* Get workflow execution details
|
|
913
|
+
*/
|
|
914
|
+
async getExecution(id) {
|
|
915
|
+
return this.client.get(`/api/v1/workflow-executions/${id}/`);
|
|
916
|
+
}
|
|
917
|
+
};
|
|
918
|
+
|
|
919
|
+
// src/lib/messages-api.ts
|
|
920
|
+
var MessagesApi = class {
|
|
921
|
+
constructor(client) {
|
|
922
|
+
this.client = client;
|
|
923
|
+
}
|
|
924
|
+
/**
|
|
925
|
+
* List messages with optional filters
|
|
926
|
+
*/
|
|
927
|
+
async list(filters) {
|
|
928
|
+
const params = new URLSearchParams();
|
|
929
|
+
if (filters?.status) params.append("status", filters.status);
|
|
930
|
+
if (filters?.contentType) params.append("contentType", filters.contentType);
|
|
931
|
+
if (filters?.scheduledAfter) params.append("scheduledAfter", filters.scheduledAfter);
|
|
932
|
+
if (filters?.scheduledBefore) params.append("scheduledBefore", filters.scheduledBefore);
|
|
933
|
+
if (filters?.sentAfter) params.append("sentAfter", filters.sentAfter);
|
|
934
|
+
if (filters?.sentBefore) params.append("sentBefore", filters.sentBefore);
|
|
935
|
+
if (filters?.search) params.append("search", filters.search);
|
|
936
|
+
if (filters?.page) params.append("page", String(filters.page));
|
|
937
|
+
if (filters?.pageSize) params.append("pageSize", String(filters.pageSize));
|
|
938
|
+
if (filters?.ordering) params.append("ordering", filters.ordering);
|
|
939
|
+
return this.client.get(
|
|
940
|
+
`/api/v1/messages/?${params.toString()}`
|
|
941
|
+
);
|
|
942
|
+
}
|
|
943
|
+
/**
|
|
944
|
+
* Get message by ID
|
|
945
|
+
*/
|
|
946
|
+
async get(id) {
|
|
947
|
+
return this.client.get(`/api/v1/messages/${id}/`);
|
|
948
|
+
}
|
|
949
|
+
/**
|
|
950
|
+
* Create a new message
|
|
951
|
+
*/
|
|
952
|
+
async create(data) {
|
|
953
|
+
return this.client.post("/api/v1/messages/", data);
|
|
954
|
+
}
|
|
955
|
+
/**
|
|
956
|
+
* Update message (draft only)
|
|
957
|
+
*/
|
|
958
|
+
async update(id, data) {
|
|
959
|
+
return this.client.patch(`/api/v1/messages/${id}/`, data);
|
|
960
|
+
}
|
|
961
|
+
/**
|
|
962
|
+
* Delete message (draft only)
|
|
963
|
+
*/
|
|
964
|
+
async delete(id) {
|
|
965
|
+
return this.client.delete(`/api/v1/messages/${id}/`);
|
|
966
|
+
}
|
|
967
|
+
/**
|
|
968
|
+
* Schedule message for future sending
|
|
969
|
+
*/
|
|
970
|
+
async schedule(id, input) {
|
|
971
|
+
return this.client.post(
|
|
972
|
+
`/api/v1/messages/${id}/schedule/`,
|
|
973
|
+
input
|
|
974
|
+
);
|
|
975
|
+
}
|
|
976
|
+
/**
|
|
977
|
+
* Send message immediately
|
|
978
|
+
*/
|
|
979
|
+
async sendNow(id) {
|
|
980
|
+
return this.client.post(
|
|
981
|
+
`/api/v1/messages/${id}/send_now/`,
|
|
982
|
+
{}
|
|
983
|
+
);
|
|
984
|
+
}
|
|
985
|
+
/**
|
|
986
|
+
* Send test message
|
|
987
|
+
*/
|
|
988
|
+
async sendTest(id, input) {
|
|
989
|
+
return this.client.post(
|
|
990
|
+
`/api/v1/messages/${id}/send_test/`,
|
|
991
|
+
input || {}
|
|
992
|
+
);
|
|
993
|
+
}
|
|
994
|
+
/**
|
|
995
|
+
* Preview message rendering
|
|
996
|
+
*/
|
|
997
|
+
async preview(id) {
|
|
998
|
+
return this.client.get(`/api/v1/messages/${id}/preview/`);
|
|
999
|
+
}
|
|
1000
|
+
/**
|
|
1001
|
+
* List recipients for a message
|
|
1002
|
+
*/
|
|
1003
|
+
async getRecipients(id, page, pageSize) {
|
|
1004
|
+
const params = new URLSearchParams();
|
|
1005
|
+
if (page) params.append("page", String(page));
|
|
1006
|
+
if (pageSize) params.append("pageSize", String(pageSize));
|
|
1007
|
+
return this.client.get(
|
|
1008
|
+
`/api/v1/messages/${id}/recipients/?${params.toString()}`
|
|
1009
|
+
);
|
|
1010
|
+
}
|
|
1011
|
+
/**
|
|
1012
|
+
* Add recipients to a message
|
|
1013
|
+
*/
|
|
1014
|
+
async addRecipients(id, recipients) {
|
|
1015
|
+
return this.client.post(`/api/v1/messages/${id}/add_recipients/`, { recipients });
|
|
1016
|
+
}
|
|
1017
|
+
/**
|
|
1018
|
+
* Get available messaging channels
|
|
1019
|
+
*/
|
|
1020
|
+
async getChannels() {
|
|
1021
|
+
return this.client.get("/api/v1/messages/channels/");
|
|
1022
|
+
}
|
|
1023
|
+
};
|
|
1024
|
+
|
|
1025
|
+
// src/lib/funnels-api.ts
|
|
1026
|
+
function isFunnelRunConflict(error) {
|
|
1027
|
+
return isApiException(error) && error.status === 409;
|
|
1028
|
+
}
|
|
1029
|
+
function isFunnelValidationError(error) {
|
|
1030
|
+
return isApiException(error) && error.status === 400 && !!error.errors;
|
|
1031
|
+
}
|
|
1032
|
+
var FunnelsApi = class {
|
|
1033
|
+
constructor(client) {
|
|
1034
|
+
this.client = client;
|
|
1035
|
+
}
|
|
1036
|
+
/**
|
|
1037
|
+
* List funnels with optional filters.
|
|
1038
|
+
* Tags are passed as repeated query params: ?tags=campaign:abc&tags=product:market-simpli
|
|
1039
|
+
*/
|
|
1040
|
+
async list(filters) {
|
|
1041
|
+
const params = new URLSearchParams();
|
|
1042
|
+
if (filters?.status) params.append("status", filters.status);
|
|
1043
|
+
if (filters?.entityType) params.append("entityType", filters.entityType);
|
|
1044
|
+
if (filters?.search) params.append("search", filters.search);
|
|
1045
|
+
if (filters?.createdBy) params.append("createdBy", filters.createdBy);
|
|
1046
|
+
if (filters?.page) params.append("page", String(filters.page));
|
|
1047
|
+
if (filters?.pageSize) params.append("pageSize", String(filters.pageSize));
|
|
1048
|
+
if (filters?.ordering) params.append("ordering", filters.ordering);
|
|
1049
|
+
if (filters?.tags?.length) {
|
|
1050
|
+
for (const tag of filters.tags) {
|
|
1051
|
+
params.append("tags", tag);
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
const query = params.toString();
|
|
1055
|
+
const endpoint = query ? `${ENDPOINTS.FUNNELS}?${query}` : ENDPOINTS.FUNNELS;
|
|
1056
|
+
return this.client.get(endpoint);
|
|
1057
|
+
}
|
|
1058
|
+
/**
|
|
1059
|
+
* Get funnel by ID
|
|
1060
|
+
*/
|
|
1061
|
+
async get(id) {
|
|
1062
|
+
return this.client.get(ENDPOINTS.FUNNEL(id));
|
|
1063
|
+
}
|
|
1064
|
+
/**
|
|
1065
|
+
* Create a new funnel
|
|
1066
|
+
*/
|
|
1067
|
+
async create(data) {
|
|
1068
|
+
return this.client.post(ENDPOINTS.FUNNELS, data);
|
|
1069
|
+
}
|
|
1070
|
+
/**
|
|
1071
|
+
* Update funnel (partial)
|
|
1072
|
+
*/
|
|
1073
|
+
async update(id, data) {
|
|
1074
|
+
return this.client.patch(ENDPOINTS.FUNNEL(id), data);
|
|
1075
|
+
}
|
|
1076
|
+
/**
|
|
1077
|
+
* Delete funnel
|
|
1078
|
+
*/
|
|
1079
|
+
async delete(id) {
|
|
1080
|
+
return this.client.delete(ENDPOINTS.FUNNEL(id));
|
|
1081
|
+
}
|
|
1082
|
+
/**
|
|
1083
|
+
* Execute a funnel run
|
|
1084
|
+
*/
|
|
1085
|
+
async run(id, input) {
|
|
1086
|
+
return this.client.post(ENDPOINTS.FUNNEL_RUN(id), input ?? {});
|
|
1087
|
+
}
|
|
1088
|
+
/**
|
|
1089
|
+
* List run history for a funnel
|
|
1090
|
+
*/
|
|
1091
|
+
async getRuns(funnelId, filters) {
|
|
1092
|
+
const params = new URLSearchParams();
|
|
1093
|
+
if (filters?.page) params.append("page", String(filters.page));
|
|
1094
|
+
if (filters?.pageSize) params.append("pageSize", String(filters.pageSize));
|
|
1095
|
+
if (filters?.status) params.append("status", filters.status);
|
|
1096
|
+
if (filters?.ordering) params.append("ordering", filters.ordering);
|
|
1097
|
+
const query = params.toString();
|
|
1098
|
+
const endpoint = query ? `${ENDPOINTS.FUNNEL_RUNS(funnelId)}?${query}` : ENDPOINTS.FUNNEL_RUNS(funnelId);
|
|
1099
|
+
return this.client.get(endpoint);
|
|
1100
|
+
}
|
|
1101
|
+
/**
|
|
1102
|
+
* Get a specific run by ID. funnelId param is ignored (runs are accessed globally).
|
|
1103
|
+
*/
|
|
1104
|
+
async getRun(_funnelId, runId) {
|
|
1105
|
+
return this.client.get(ENDPOINTS.FUNNEL_RUN_BY_ID(runId));
|
|
1106
|
+
}
|
|
1107
|
+
// BEAD: fund-your-startup-rgi4 - funnels/{id}/results endpoint missing from Django FunnelViewSet
|
|
1108
|
+
async getResults(_funnelId, _pagination) {
|
|
1109
|
+
throw new Error("Not implemented - BEAD: fund-your-startup-rgi4. funnels/{id}/results action does not exist in Django.");
|
|
1110
|
+
}
|
|
1111
|
+
// BEAD: fund-your-startup-rgi4 - funnels/{id}/preview endpoint missing. Django has /funnels/preview-icp/ (different signature).
|
|
1112
|
+
async preview(_funnelId, _stages) {
|
|
1113
|
+
throw new Error("Not implemented - BEAD: fund-your-startup-rgi4. funnels/{id}/preview action does not exist in Django.");
|
|
1114
|
+
}
|
|
1115
|
+
/**
|
|
1116
|
+
* List all funnel runs globally (across all funnels, scoped to current user)
|
|
1117
|
+
*/
|
|
1118
|
+
async listRunsGlobal(filters) {
|
|
1119
|
+
const params = new URLSearchParams();
|
|
1120
|
+
if (filters?.funnel) params.append("funnel", filters.funnel);
|
|
1121
|
+
if (filters?.status) params.append("status", filters.status);
|
|
1122
|
+
if (filters?.page) params.append("page", String(filters.page));
|
|
1123
|
+
if (filters?.pageSize) params.append("pageSize", String(filters.pageSize));
|
|
1124
|
+
if (filters?.startedAfter) params.append("startedAfter", filters.startedAfter);
|
|
1125
|
+
if (filters?.startedBefore) params.append("startedBefore", filters.startedBefore);
|
|
1126
|
+
const query = params.toString();
|
|
1127
|
+
const endpoint = query ? `${ENDPOINTS.FUNNEL_RUNS_GLOBAL}?${query}` : ENDPOINTS.FUNNEL_RUNS_GLOBAL;
|
|
1128
|
+
return this.client.get(endpoint);
|
|
1129
|
+
}
|
|
1130
|
+
/**
|
|
1131
|
+
* Cancel a running funnel run.
|
|
1132
|
+
*/
|
|
1133
|
+
async cancelRun(runId) {
|
|
1134
|
+
return this.client.post(ENDPOINTS.FUNNEL_RUN_CANCEL(runId), {});
|
|
1135
|
+
}
|
|
1136
|
+
/**
|
|
1137
|
+
* List available funnel templates
|
|
1138
|
+
*/
|
|
1139
|
+
async listTemplates(filters) {
|
|
1140
|
+
const params = new URLSearchParams();
|
|
1141
|
+
if (filters?.category) params.append("category", filters.category);
|
|
1142
|
+
if (filters?.page) params.append("page", String(filters.page));
|
|
1143
|
+
if (filters?.pageSize) params.append("pageSize", String(filters.pageSize));
|
|
1144
|
+
const query = params.toString();
|
|
1145
|
+
const endpoint = query ? `${ENDPOINTS.FUNNEL_TEMPLATES}?${query}` : ENDPOINTS.FUNNEL_TEMPLATES;
|
|
1146
|
+
return this.client.get(endpoint);
|
|
1147
|
+
}
|
|
1148
|
+
};
|
|
1149
|
+
|
|
1150
|
+
// src/lib/errors.ts
|
|
1151
|
+
var AppErrorCode = /* @__PURE__ */ ((AppErrorCode2) => {
|
|
1152
|
+
AppErrorCode2["VALIDATION_ERROR"] = "VALIDATION_ERROR";
|
|
1153
|
+
AppErrorCode2["AUTHENTICATION_REQUIRED"] = "AUTHENTICATION_REQUIRED";
|
|
1154
|
+
AppErrorCode2["INVALID_CREDENTIALS"] = "INVALID_CREDENTIALS";
|
|
1155
|
+
AppErrorCode2["SESSION_EXPIRED"] = "SESSION_EXPIRED";
|
|
1156
|
+
AppErrorCode2["FORBIDDEN"] = "FORBIDDEN";
|
|
1157
|
+
AppErrorCode2["INSUFFICIENT_PERMISSIONS"] = "INSUFFICIENT_PERMISSIONS";
|
|
1158
|
+
AppErrorCode2["NOT_FOUND"] = "NOT_FOUND";
|
|
1159
|
+
AppErrorCode2["RESOURCE_NOT_FOUND"] = "RESOURCE_NOT_FOUND";
|
|
1160
|
+
AppErrorCode2["CONFLICT"] = "CONFLICT";
|
|
1161
|
+
AppErrorCode2["DUPLICATE_RESOURCE"] = "DUPLICATE_RESOURCE";
|
|
1162
|
+
AppErrorCode2["RATE_LIMITED"] = "RATE_LIMITED";
|
|
1163
|
+
AppErrorCode2["BAD_REQUEST"] = "BAD_REQUEST";
|
|
1164
|
+
AppErrorCode2["INTERNAL_ERROR"] = "INTERNAL_ERROR";
|
|
1165
|
+
AppErrorCode2["DATABASE_ERROR"] = "DATABASE_ERROR";
|
|
1166
|
+
AppErrorCode2["EXTERNAL_SERVICE_ERROR"] = "EXTERNAL_SERVICE_ERROR";
|
|
1167
|
+
AppErrorCode2["SERVICE_UNAVAILABLE"] = "SERVICE_UNAVAILABLE";
|
|
1168
|
+
return AppErrorCode2;
|
|
1169
|
+
})(AppErrorCode || {});
|
|
1170
|
+
var AppError = class _AppError extends Error {
|
|
1171
|
+
constructor(message, code = "INTERNAL_ERROR" /* INTERNAL_ERROR */, statusCode = 500, details, isOperational = true) {
|
|
1172
|
+
super(message);
|
|
1173
|
+
this.name = "AppError";
|
|
1174
|
+
this.code = code;
|
|
1175
|
+
this.statusCode = statusCode;
|
|
1176
|
+
this.details = details;
|
|
1177
|
+
this.isOperational = isOperational;
|
|
1178
|
+
Error.captureStackTrace(this, this.constructor);
|
|
1179
|
+
}
|
|
1180
|
+
/**
|
|
1181
|
+
* Serialize error for API response
|
|
1182
|
+
*/
|
|
1183
|
+
toResponse() {
|
|
1184
|
+
return {
|
|
1185
|
+
error: {
|
|
1186
|
+
code: this.code,
|
|
1187
|
+
message: this.message,
|
|
1188
|
+
...this.details && { details: this.details }
|
|
1189
|
+
}
|
|
1190
|
+
};
|
|
1191
|
+
}
|
|
1192
|
+
/**
|
|
1193
|
+
* Check if an error is an operational error (expected) vs programming error
|
|
1194
|
+
*/
|
|
1195
|
+
static isOperationalError(error) {
|
|
1196
|
+
return error instanceof _AppError && error.isOperational;
|
|
1197
|
+
}
|
|
1198
|
+
};
|
|
1199
|
+
var ValidationError = class _ValidationError extends AppError {
|
|
1200
|
+
constructor(message = "Validation failed", fieldErrors = {}) {
|
|
1201
|
+
super(
|
|
1202
|
+
message,
|
|
1203
|
+
"VALIDATION_ERROR" /* VALIDATION_ERROR */,
|
|
1204
|
+
400,
|
|
1205
|
+
{ fields: fieldErrors }
|
|
1206
|
+
);
|
|
1207
|
+
this.name = "ValidationError";
|
|
1208
|
+
this.fieldErrors = fieldErrors;
|
|
1209
|
+
}
|
|
1210
|
+
/**
|
|
1211
|
+
* Create from Zod error
|
|
1212
|
+
*/
|
|
1213
|
+
static fromZodError(zodError) {
|
|
1214
|
+
const fieldErrors = {};
|
|
1215
|
+
for (const error of zodError.errors) {
|
|
1216
|
+
const path = error.path.join(".");
|
|
1217
|
+
if (!fieldErrors[path]) {
|
|
1218
|
+
fieldErrors[path] = [];
|
|
1219
|
+
}
|
|
1220
|
+
fieldErrors[path].push(error.message);
|
|
1221
|
+
}
|
|
1222
|
+
const message = Object.entries(fieldErrors).map(([field, errors]) => `${field}: ${errors.join(", ")}`).join("; ");
|
|
1223
|
+
return new _ValidationError(`Validation failed: ${message}`, fieldErrors);
|
|
1224
|
+
}
|
|
1225
|
+
};
|
|
1226
|
+
var AuthenticationError = class _AuthenticationError extends AppError {
|
|
1227
|
+
constructor(message = "Authentication required", code = "AUTHENTICATION_REQUIRED" /* AUTHENTICATION_REQUIRED */) {
|
|
1228
|
+
super(message, code, 401);
|
|
1229
|
+
this.name = "AuthenticationError";
|
|
1230
|
+
}
|
|
1231
|
+
/**
|
|
1232
|
+
* Create for invalid credentials
|
|
1233
|
+
*/
|
|
1234
|
+
static invalidCredentials() {
|
|
1235
|
+
return new _AuthenticationError("Invalid credentials", "INVALID_CREDENTIALS" /* INVALID_CREDENTIALS */);
|
|
1236
|
+
}
|
|
1237
|
+
/**
|
|
1238
|
+
* Create for expired session
|
|
1239
|
+
*/
|
|
1240
|
+
static sessionExpired() {
|
|
1241
|
+
return new _AuthenticationError("Session has expired", "SESSION_EXPIRED" /* SESSION_EXPIRED */);
|
|
1242
|
+
}
|
|
1243
|
+
};
|
|
1244
|
+
var AuthorizationError = class _AuthorizationError extends AppError {
|
|
1245
|
+
constructor(message = "Access denied", code = "FORBIDDEN" /* FORBIDDEN */) {
|
|
1246
|
+
super(message, code, 403);
|
|
1247
|
+
this.name = "AuthorizationError";
|
|
1248
|
+
}
|
|
1249
|
+
/**
|
|
1250
|
+
* Create for insufficient permissions
|
|
1251
|
+
*/
|
|
1252
|
+
static insufficientPermissions(requiredPermission) {
|
|
1253
|
+
const message = requiredPermission ? `Insufficient permissions. Required: ${requiredPermission}` : "Insufficient permissions";
|
|
1254
|
+
return new _AuthorizationError(message, "INSUFFICIENT_PERMISSIONS" /* INSUFFICIENT_PERMISSIONS */);
|
|
1255
|
+
}
|
|
1256
|
+
};
|
|
1257
|
+
var NotFoundError = class _NotFoundError extends AppError {
|
|
1258
|
+
constructor(message = "Resource not found", resourceType, resourceId) {
|
|
1259
|
+
super(
|
|
1260
|
+
message,
|
|
1261
|
+
"NOT_FOUND" /* NOT_FOUND */,
|
|
1262
|
+
404,
|
|
1263
|
+
resourceType || resourceId ? { resourceType, resourceId } : void 0
|
|
1264
|
+
);
|
|
1265
|
+
this.name = "NotFoundError";
|
|
1266
|
+
this.resourceType = resourceType;
|
|
1267
|
+
this.resourceId = resourceId;
|
|
1268
|
+
}
|
|
1269
|
+
/**
|
|
1270
|
+
* Create for a specific resource
|
|
1271
|
+
*/
|
|
1272
|
+
static forResource(resourceType, resourceId) {
|
|
1273
|
+
const message = resourceId ? `${resourceType} with ID '${resourceId}' not found` : `${resourceType} not found`;
|
|
1274
|
+
return new _NotFoundError(message, resourceType, resourceId);
|
|
1275
|
+
}
|
|
1276
|
+
};
|
|
1277
|
+
var ConflictError = class _ConflictError extends AppError {
|
|
1278
|
+
constructor(message = "Resource conflict", conflictingField) {
|
|
1279
|
+
super(
|
|
1280
|
+
message,
|
|
1281
|
+
"CONFLICT" /* CONFLICT */,
|
|
1282
|
+
409,
|
|
1283
|
+
conflictingField ? { field: conflictingField } : void 0
|
|
1284
|
+
);
|
|
1285
|
+
this.name = "ConflictError";
|
|
1286
|
+
this.conflictingField = conflictingField;
|
|
1287
|
+
}
|
|
1288
|
+
/**
|
|
1289
|
+
* Create for duplicate resource
|
|
1290
|
+
*/
|
|
1291
|
+
static duplicate(resourceType, field) {
|
|
1292
|
+
const message = field ? `A ${resourceType} with this ${field} already exists` : `A ${resourceType} with these values already exists`;
|
|
1293
|
+
return new _ConflictError(message, field);
|
|
1294
|
+
}
|
|
1295
|
+
};
|
|
1296
|
+
var RateLimitError = class extends AppError {
|
|
1297
|
+
constructor(message = "Too many requests", retryAfter) {
|
|
1298
|
+
super(
|
|
1299
|
+
message,
|
|
1300
|
+
"RATE_LIMITED" /* RATE_LIMITED */,
|
|
1301
|
+
429,
|
|
1302
|
+
retryAfter ? { retryAfter } : void 0
|
|
1303
|
+
);
|
|
1304
|
+
this.name = "RateLimitError";
|
|
1305
|
+
this.retryAfter = retryAfter;
|
|
1306
|
+
}
|
|
1307
|
+
};
|
|
1308
|
+
var DatabaseError = class _DatabaseError extends AppError {
|
|
1309
|
+
constructor(message = "Database operation failed", details) {
|
|
1310
|
+
const safeDetails = process.env.NODE_ENV === "development" ? details : void 0;
|
|
1311
|
+
super(message, "DATABASE_ERROR" /* DATABASE_ERROR */, 500, safeDetails);
|
|
1312
|
+
this.name = "DatabaseError";
|
|
1313
|
+
}
|
|
1314
|
+
/**
|
|
1315
|
+
* Create from Prisma error
|
|
1316
|
+
*/
|
|
1317
|
+
static fromPrismaError(error) {
|
|
1318
|
+
const code = error.code;
|
|
1319
|
+
switch (code) {
|
|
1320
|
+
case "P2002":
|
|
1321
|
+
throw ConflictError.duplicate("record", error.meta?.target?.[0]);
|
|
1322
|
+
case "P2025":
|
|
1323
|
+
throw new NotFoundError("Record not found");
|
|
1324
|
+
case "P2003":
|
|
1325
|
+
throw new ConflictError("Cannot perform operation due to related records");
|
|
1326
|
+
default:
|
|
1327
|
+
return new _DatabaseError("Database operation failed", { code });
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
};
|
|
1331
|
+
var ExternalServiceError = class extends AppError {
|
|
1332
|
+
constructor(serviceName, message = "External service error", details) {
|
|
1333
|
+
super(
|
|
1334
|
+
message,
|
|
1335
|
+
"EXTERNAL_SERVICE_ERROR" /* EXTERNAL_SERVICE_ERROR */,
|
|
1336
|
+
502,
|
|
1337
|
+
{ service: serviceName, ...details }
|
|
1338
|
+
);
|
|
1339
|
+
this.name = "ExternalServiceError";
|
|
1340
|
+
this.serviceName = serviceName;
|
|
1341
|
+
}
|
|
1342
|
+
};
|
|
1343
|
+
function isPrismaError(error) {
|
|
1344
|
+
return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string";
|
|
1345
|
+
}
|
|
1346
|
+
function toAppError(error) {
|
|
1347
|
+
if (error instanceof AppError) {
|
|
1348
|
+
return error;
|
|
1349
|
+
}
|
|
1350
|
+
if (isPrismaError(error)) {
|
|
1351
|
+
return DatabaseError.fromPrismaError(error);
|
|
1352
|
+
}
|
|
1353
|
+
if (error instanceof Error) {
|
|
1354
|
+
return new AppError(
|
|
1355
|
+
error.message,
|
|
1356
|
+
"INTERNAL_ERROR" /* INTERNAL_ERROR */,
|
|
1357
|
+
500,
|
|
1358
|
+
process.env.NODE_ENV === "development" ? { stack: error.stack } : void 0
|
|
1359
|
+
);
|
|
1360
|
+
}
|
|
1361
|
+
return new AppError(
|
|
1362
|
+
"An unexpected error occurred",
|
|
1363
|
+
"INTERNAL_ERROR" /* INTERNAL_ERROR */,
|
|
1364
|
+
500
|
|
1365
|
+
);
|
|
1366
|
+
}
|
|
1367
|
+
function sanitizeHtml(input) {
|
|
1368
|
+
return DOMPurify__default.default.sanitize(input, {
|
|
1369
|
+
ALLOWED_TAGS: ["b", "i", "em", "strong", "a", "p", "br"],
|
|
1370
|
+
ALLOWED_ATTR: ["href"],
|
|
1371
|
+
ALLOW_DATA_ATTR: false
|
|
1372
|
+
});
|
|
1373
|
+
}
|
|
1374
|
+
function sanitizeSearchQuery(query) {
|
|
1375
|
+
if (!query || typeof query !== "string") {
|
|
1376
|
+
return "";
|
|
1377
|
+
}
|
|
1378
|
+
return query.trim().replace(/[.*+?^${}()|[\]\\]/g, "\\$&").substring(0, 100);
|
|
1379
|
+
}
|
|
1380
|
+
function validateIdentifier(input) {
|
|
1381
|
+
return /^[a-zA-Z0-9_-]+$/.test(input);
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
// src/lib/llm-sanitize.ts
|
|
1385
|
+
var MAX_PROMPT_LENGTH = 2e3;
|
|
1386
|
+
var MAX_CHAT_MESSAGE_LENGTH = 1e3;
|
|
1387
|
+
var INJECTION_PATTERNS = [
|
|
1388
|
+
// System/role injection attempts
|
|
1389
|
+
/\bSYSTEM\s*:/gi,
|
|
1390
|
+
/\bASSISTANT\s*:/gi,
|
|
1391
|
+
/\bUSER\s*:/gi,
|
|
1392
|
+
/\bHUMAN\s*:/gi,
|
|
1393
|
+
/\bAI\s*:/gi,
|
|
1394
|
+
// Instruction override attempts
|
|
1395
|
+
/\bignore\s+(previous|above|all)\s+instructions?\b/gi,
|
|
1396
|
+
/\bdisregard\s+(previous|above|all)\s+instructions?\b/gi,
|
|
1397
|
+
/\bforget\s+(previous|above|all)\s+instructions?\b/gi,
|
|
1398
|
+
/\bnew\s+instructions?\s*:/gi,
|
|
1399
|
+
/\boverride\s*:/gi,
|
|
1400
|
+
// Role-playing attempts
|
|
1401
|
+
/\byou\s+are\s+now\b/gi,
|
|
1402
|
+
/\bact\s+as\s+(if|a|an|the)\b/gi,
|
|
1403
|
+
/\bpretend\s+(to\s+be|you\s+are)\b/gi,
|
|
1404
|
+
// JSON/schema manipulation
|
|
1405
|
+
/\b(output|respond|return)\s+only\s*:/gi,
|
|
1406
|
+
/\bformat\s*:\s*json\b/gi
|
|
1407
|
+
];
|
|
1408
|
+
var FORMATTING_MARKERS = [
|
|
1409
|
+
"```",
|
|
1410
|
+
// Code blocks
|
|
1411
|
+
"---",
|
|
1412
|
+
// Horizontal rules (when at line start)
|
|
1413
|
+
"***",
|
|
1414
|
+
// Alternative horizontal rules
|
|
1415
|
+
"##",
|
|
1416
|
+
// Headers (when at line start)
|
|
1417
|
+
">>"
|
|
1418
|
+
// Potential quote injection
|
|
1419
|
+
];
|
|
1420
|
+
var CONTROL_CHAR_REGEX = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g;
|
|
1421
|
+
function sanitizeUserInput(input, maxLength = MAX_PROMPT_LENGTH) {
|
|
1422
|
+
if (!input || typeof input !== "string") {
|
|
1423
|
+
return "";
|
|
1424
|
+
}
|
|
1425
|
+
let sanitized = input;
|
|
1426
|
+
sanitized = sanitized.replace(CONTROL_CHAR_REGEX, "");
|
|
1427
|
+
sanitized = sanitized.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\n{3,}/g, "\n\n").replace(/[ \t]{3,}/g, " ").trim();
|
|
1428
|
+
for (const pattern of INJECTION_PATTERNS) {
|
|
1429
|
+
sanitized = sanitized.replace(pattern, (match) => {
|
|
1430
|
+
return `[${match.replace(/:/g, "")}]`;
|
|
1431
|
+
});
|
|
1432
|
+
}
|
|
1433
|
+
for (const marker of FORMATTING_MARKERS) {
|
|
1434
|
+
const escapeRegex = new RegExp(`(^|\\n)(${escapeRegexChars(marker)})`, "g");
|
|
1435
|
+
sanitized = sanitized.replace(escapeRegex, (_, prefix, match) => {
|
|
1436
|
+
return `${prefix}[${match}]`;
|
|
1437
|
+
});
|
|
1438
|
+
}
|
|
1439
|
+
if (sanitized.length > maxLength) {
|
|
1440
|
+
sanitized = truncateAtWordBoundary(sanitized, maxLength);
|
|
1441
|
+
}
|
|
1442
|
+
return sanitized;
|
|
1443
|
+
}
|
|
1444
|
+
function sanitizeChatMessage(input) {
|
|
1445
|
+
return sanitizeUserInput(input, MAX_CHAT_MESSAGE_LENGTH);
|
|
1446
|
+
}
|
|
1447
|
+
function truncateAtWordBoundary(text, maxLength) {
|
|
1448
|
+
if (text.length <= maxLength) {
|
|
1449
|
+
return text;
|
|
1450
|
+
}
|
|
1451
|
+
const truncated = text.slice(0, maxLength);
|
|
1452
|
+
const lastSpace = truncated.lastIndexOf(" ");
|
|
1453
|
+
if (lastSpace > maxLength * 0.8) {
|
|
1454
|
+
return truncated.slice(0, lastSpace).trim();
|
|
1455
|
+
}
|
|
1456
|
+
return truncated.trim();
|
|
1457
|
+
}
|
|
1458
|
+
function escapeRegexChars(str) {
|
|
1459
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
// src/lib/cors.ts
|
|
1463
|
+
var DEFAULT_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"];
|
|
1464
|
+
var DEFAULT_HEADERS = ["Content-Type", "Authorization", "X-Requested-With", "Accept", "X-CSRF-Token"];
|
|
1465
|
+
function isAllowed(origin, options) {
|
|
1466
|
+
if (!origin) return false;
|
|
1467
|
+
if (options.origins === "*") return true;
|
|
1468
|
+
return options.origins.includes(origin);
|
|
1469
|
+
}
|
|
1470
|
+
function getCorsHeaders(origin, options) {
|
|
1471
|
+
if (!isAllowed(origin, options)) {
|
|
1472
|
+
return {};
|
|
1473
|
+
}
|
|
1474
|
+
const methods = options.methods ?? DEFAULT_METHODS;
|
|
1475
|
+
const headers = options.headers ?? DEFAULT_HEADERS;
|
|
1476
|
+
const credentials = options.credentials ?? options.origins !== "*";
|
|
1477
|
+
const maxAge = options.maxAge ?? 86400;
|
|
1478
|
+
const result = {
|
|
1479
|
+
"Access-Control-Allow-Origin": options.origins === "*" ? "*" : origin,
|
|
1480
|
+
"Access-Control-Allow-Methods": methods.join(", "),
|
|
1481
|
+
"Access-Control-Allow-Headers": headers.join(", "),
|
|
1482
|
+
"Access-Control-Max-Age": String(maxAge)
|
|
1483
|
+
};
|
|
1484
|
+
if (credentials && options.origins !== "*") {
|
|
1485
|
+
result["Access-Control-Allow-Credentials"] = "true";
|
|
1486
|
+
}
|
|
1487
|
+
return result;
|
|
1488
|
+
}
|
|
1489
|
+
function applyCorsHeaders(responseHeaders, origin, options) {
|
|
1490
|
+
const corsHeaders = getCorsHeaders(origin, options);
|
|
1491
|
+
for (const [key, value] of Object.entries(corsHeaders)) {
|
|
1492
|
+
responseHeaders.set(key, value);
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
function createCorsMiddleware(options) {
|
|
1496
|
+
return function corsMiddleware(request) {
|
|
1497
|
+
const origin = request.headers.get("origin");
|
|
1498
|
+
if (request.method === "OPTIONS") {
|
|
1499
|
+
const corsHeaders = getCorsHeaders(origin, options);
|
|
1500
|
+
return new Response(null, { status: 204, headers: corsHeaders });
|
|
1501
|
+
}
|
|
1502
|
+
return null;
|
|
1503
|
+
};
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
// src/types/response.ts
|
|
1507
|
+
var ErrorCodes = /* @__PURE__ */ ((ErrorCodes2) => {
|
|
1508
|
+
ErrorCodes2["UNAUTHORIZED"] = "UNAUTHORIZED";
|
|
1509
|
+
ErrorCodes2["FORBIDDEN"] = "FORBIDDEN";
|
|
1510
|
+
ErrorCodes2["NOT_FOUND"] = "NOT_FOUND";
|
|
1511
|
+
ErrorCodes2["VALIDATION_ERROR"] = "VALIDATION_ERROR";
|
|
1512
|
+
ErrorCodes2["CONFLICT"] = "CONFLICT";
|
|
1513
|
+
ErrorCodes2["INTERNAL_ERROR"] = "INTERNAL_ERROR";
|
|
1514
|
+
ErrorCodes2["SERVICE_UNAVAILABLE"] = "SERVICE_UNAVAILABLE";
|
|
1515
|
+
ErrorCodes2["RATE_LIMITED"] = "RATE_LIMITED";
|
|
1516
|
+
return ErrorCodes2;
|
|
1517
|
+
})(ErrorCodes || {});
|
|
1518
|
+
var FieldErrorSchema = zod.z.object({
|
|
1519
|
+
field: zod.z.string(),
|
|
1520
|
+
messages: zod.z.array(zod.z.string()),
|
|
1521
|
+
code: zod.z.string().optional()
|
|
1522
|
+
});
|
|
1523
|
+
var StandardErrorResponseSchema = zod.z.object({
|
|
1524
|
+
error: zod.z.string(),
|
|
1525
|
+
code: zod.z.string(),
|
|
1526
|
+
statusCode: zod.z.number().int().min(400).max(599),
|
|
1527
|
+
fieldErrors: zod.z.array(FieldErrorSchema).optional(),
|
|
1528
|
+
details: zod.z.record(zod.z.unknown()).optional(),
|
|
1529
|
+
requestId: zod.z.string().optional(),
|
|
1530
|
+
timestamp: zod.z.string().datetime().optional()
|
|
1531
|
+
});
|
|
1532
|
+
var ErrorCode = /* @__PURE__ */ ((ErrorCode2) => {
|
|
1533
|
+
ErrorCode2["BAD_REQUEST"] = "bad_request";
|
|
1534
|
+
ErrorCode2["UNAUTHORIZED"] = "unauthorized";
|
|
1535
|
+
ErrorCode2["FORBIDDEN"] = "forbidden";
|
|
1536
|
+
ErrorCode2["NOT_FOUND"] = "not_found";
|
|
1537
|
+
ErrorCode2["METHOD_NOT_ALLOWED"] = "method_not_allowed";
|
|
1538
|
+
ErrorCode2["VALIDATION_ERROR"] = "validation_error";
|
|
1539
|
+
ErrorCode2["CONFLICT"] = "conflict";
|
|
1540
|
+
ErrorCode2["RATE_LIMITED"] = "rate_limited";
|
|
1541
|
+
ErrorCode2["INTERNAL_ERROR"] = "internal_error";
|
|
1542
|
+
ErrorCode2["SERVICE_UNAVAILABLE"] = "service_unavailable";
|
|
1543
|
+
ErrorCode2["GATEWAY_TIMEOUT"] = "gateway_timeout";
|
|
1544
|
+
ErrorCode2["NETWORK_ERROR"] = "network_error";
|
|
1545
|
+
ErrorCode2["TIMEOUT"] = "timeout";
|
|
1546
|
+
ErrorCode2["UNKNOWN"] = "unknown";
|
|
1547
|
+
return ErrorCode2;
|
|
1548
|
+
})(ErrorCode || {});
|
|
1549
|
+
function getErrorCodeFromStatus(status) {
|
|
1550
|
+
const codeMap = {
|
|
1551
|
+
400: "bad_request" /* BAD_REQUEST */,
|
|
1552
|
+
401: "unauthorized" /* UNAUTHORIZED */,
|
|
1553
|
+
403: "forbidden" /* FORBIDDEN */,
|
|
1554
|
+
404: "not_found" /* NOT_FOUND */,
|
|
1555
|
+
405: "method_not_allowed" /* METHOD_NOT_ALLOWED */,
|
|
1556
|
+
409: "conflict" /* CONFLICT */,
|
|
1557
|
+
422: "validation_error" /* VALIDATION_ERROR */,
|
|
1558
|
+
429: "rate_limited" /* RATE_LIMITED */,
|
|
1559
|
+
500: "internal_error" /* INTERNAL_ERROR */,
|
|
1560
|
+
503: "service_unavailable" /* SERVICE_UNAVAILABLE */,
|
|
1561
|
+
504: "gateway_timeout" /* GATEWAY_TIMEOUT */
|
|
1562
|
+
};
|
|
1563
|
+
return codeMap[status] || "unknown" /* UNKNOWN */;
|
|
1564
|
+
}
|
|
1565
|
+
var ErrorMessages = {
|
|
1566
|
+
["bad_request" /* BAD_REQUEST */]: "The request was invalid. Please check your input.",
|
|
1567
|
+
["unauthorized" /* UNAUTHORIZED */]: "You need to log in to access this resource.",
|
|
1568
|
+
["forbidden" /* FORBIDDEN */]: "You don't have permission to access this resource.",
|
|
1569
|
+
["not_found" /* NOT_FOUND */]: "The requested resource was not found.",
|
|
1570
|
+
["method_not_allowed" /* METHOD_NOT_ALLOWED */]: "This operation is not allowed.",
|
|
1571
|
+
["validation_error" /* VALIDATION_ERROR */]: "Please fix the errors in your form.",
|
|
1572
|
+
["conflict" /* CONFLICT */]: "This operation conflicts with existing data.",
|
|
1573
|
+
["rate_limited" /* RATE_LIMITED */]: "Too many requests. Please slow down.",
|
|
1574
|
+
["internal_error" /* INTERNAL_ERROR */]: "An unexpected error occurred. Please try again.",
|
|
1575
|
+
["service_unavailable" /* SERVICE_UNAVAILABLE */]: "The service is temporarily unavailable.",
|
|
1576
|
+
["gateway_timeout" /* GATEWAY_TIMEOUT */]: "The request took too long. Please try again.",
|
|
1577
|
+
["network_error" /* NETWORK_ERROR */]: "Network error. Please check your connection.",
|
|
1578
|
+
["timeout" /* TIMEOUT */]: "The request timed out. Please try again.",
|
|
1579
|
+
["unknown" /* UNKNOWN */]: "An unknown error occurred."
|
|
1580
|
+
};
|
|
1581
|
+
|
|
1582
|
+
// src/lib/rate-limit.ts
|
|
1583
|
+
var CLEANUP_INTERVAL_MS = 10 * 60 * 1e3;
|
|
1584
|
+
function createRateLimiter(options) {
|
|
1585
|
+
const { windowMs, maxRequests, keyPrefix = "" } = options;
|
|
1586
|
+
const store = /* @__PURE__ */ new Map();
|
|
1587
|
+
let lastCleanup = Date.now();
|
|
1588
|
+
function cleanup() {
|
|
1589
|
+
const now = Date.now();
|
|
1590
|
+
if (now - lastCleanup < CLEANUP_INTERVAL_MS) return;
|
|
1591
|
+
lastCleanup = now;
|
|
1592
|
+
for (const [key, entry] of store.entries()) {
|
|
1593
|
+
if (now > entry.resetAt) store.delete(key);
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
return function check(key) {
|
|
1597
|
+
cleanup();
|
|
1598
|
+
const storeKey = keyPrefix ? `${keyPrefix}:${key}` : key;
|
|
1599
|
+
const now = Date.now();
|
|
1600
|
+
const entry = store.get(storeKey);
|
|
1601
|
+
if (!entry || now > entry.resetAt) {
|
|
1602
|
+
const resetAt = now + windowMs;
|
|
1603
|
+
store.set(storeKey, { count: 1, resetAt });
|
|
1604
|
+
return { success: true, remaining: maxRequests - 1, resetAt };
|
|
1605
|
+
}
|
|
1606
|
+
if (entry.count >= maxRequests) {
|
|
1607
|
+
const retryAfter = Math.ceil((entry.resetAt - now) / 1e3);
|
|
1608
|
+
return { success: false, remaining: 0, resetAt: entry.resetAt, retryAfter };
|
|
1609
|
+
}
|
|
1610
|
+
entry.count++;
|
|
1611
|
+
return { success: true, remaining: maxRequests - entry.count, resetAt: entry.resetAt };
|
|
1612
|
+
};
|
|
1613
|
+
}
|
|
1614
|
+
function getClientIP(request) {
|
|
1615
|
+
const forwarded = request.headers.get("x-forwarded-for");
|
|
1616
|
+
if (forwarded) return forwarded.split(",")[0].trim();
|
|
1617
|
+
const real = request.headers.get("x-real-ip");
|
|
1618
|
+
if (real) return real;
|
|
1619
|
+
return "unknown";
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
// src/lib/cache-store.ts
|
|
1623
|
+
var DEFAULT_CONFIG = {
|
|
1624
|
+
maxSize: 1e3,
|
|
1625
|
+
defaultTTL: 60 * 1e3
|
|
1626
|
+
};
|
|
1627
|
+
var CacheStore = class {
|
|
1628
|
+
constructor(config = {}) {
|
|
1629
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
1630
|
+
this.hits = 0;
|
|
1631
|
+
this.misses = 0;
|
|
1632
|
+
this.accessCounter = 0;
|
|
1633
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
1634
|
+
}
|
|
1635
|
+
get(key) {
|
|
1636
|
+
const entry = this.cache.get(key);
|
|
1637
|
+
if (!entry) {
|
|
1638
|
+
this.misses++;
|
|
1639
|
+
return void 0;
|
|
1640
|
+
}
|
|
1641
|
+
if (Date.now() > entry.expiresAt) {
|
|
1642
|
+
this.cache.delete(key);
|
|
1643
|
+
this.misses++;
|
|
1644
|
+
return void 0;
|
|
1645
|
+
}
|
|
1646
|
+
entry.accessOrder = ++this.accessCounter;
|
|
1647
|
+
this.hits++;
|
|
1648
|
+
return entry.value;
|
|
1649
|
+
}
|
|
1650
|
+
set(key, value, ttl) {
|
|
1651
|
+
if (this.cache.size >= this.config.maxSize) this.evictLRU();
|
|
1652
|
+
this.cache.set(key, {
|
|
1653
|
+
value,
|
|
1654
|
+
expiresAt: Date.now() + (ttl ?? this.config.defaultTTL),
|
|
1655
|
+
accessOrder: ++this.accessCounter
|
|
1656
|
+
});
|
|
1657
|
+
}
|
|
1658
|
+
has(key) {
|
|
1659
|
+
const entry = this.cache.get(key);
|
|
1660
|
+
if (!entry) return false;
|
|
1661
|
+
if (Date.now() > entry.expiresAt) {
|
|
1662
|
+
this.cache.delete(key);
|
|
1663
|
+
return false;
|
|
1664
|
+
}
|
|
1665
|
+
return true;
|
|
1666
|
+
}
|
|
1667
|
+
delete(key) {
|
|
1668
|
+
return this.cache.delete(key);
|
|
1669
|
+
}
|
|
1670
|
+
/** Delete all keys matching a string prefix or RegExp pattern */
|
|
1671
|
+
deletePattern(pattern) {
|
|
1672
|
+
const regex = typeof pattern === "string" ? new RegExp(pattern) : pattern;
|
|
1673
|
+
let deleted = 0;
|
|
1674
|
+
for (const key of this.cache.keys()) {
|
|
1675
|
+
if (regex.test(key)) {
|
|
1676
|
+
this.cache.delete(key);
|
|
1677
|
+
deleted++;
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
return deleted;
|
|
1681
|
+
}
|
|
1682
|
+
clear() {
|
|
1683
|
+
this.cache.clear();
|
|
1684
|
+
this.hits = 0;
|
|
1685
|
+
this.misses = 0;
|
|
1686
|
+
this.accessCounter = 0;
|
|
1687
|
+
}
|
|
1688
|
+
/** Remove expired entries and return count removed */
|
|
1689
|
+
cleanup() {
|
|
1690
|
+
const now = Date.now();
|
|
1691
|
+
let removed = 0;
|
|
1692
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
1693
|
+
if (now > entry.expiresAt) {
|
|
1694
|
+
this.cache.delete(key);
|
|
1695
|
+
removed++;
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
return removed;
|
|
1699
|
+
}
|
|
1700
|
+
getStats() {
|
|
1701
|
+
const total = this.hits + this.misses;
|
|
1702
|
+
return {
|
|
1703
|
+
size: this.cache.size,
|
|
1704
|
+
maxSize: this.config.maxSize,
|
|
1705
|
+
hits: this.hits,
|
|
1706
|
+
misses: this.misses,
|
|
1707
|
+
hitRate: total > 0 ? this.hits / total : 0
|
|
1708
|
+
};
|
|
1709
|
+
}
|
|
1710
|
+
evictLRU() {
|
|
1711
|
+
let oldestKey = null;
|
|
1712
|
+
let oldestOrder = Infinity;
|
|
1713
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
1714
|
+
if (entry.accessOrder < oldestOrder) {
|
|
1715
|
+
oldestOrder = entry.accessOrder;
|
|
1716
|
+
oldestKey = key;
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
if (oldestKey) this.cache.delete(oldestKey);
|
|
1720
|
+
}
|
|
1721
|
+
};
|
|
1722
|
+
|
|
1723
|
+
// src/lib/cache-manager.ts
|
|
1724
|
+
var DEFAULT_CONFIG2 = {
|
|
1725
|
+
entity: { maxSize: 500, defaultTTL: 5 * 60 * 1e3 },
|
|
1726
|
+
query: { maxSize: 200, defaultTTL: 60 * 1e3 }
|
|
1727
|
+
};
|
|
1728
|
+
var CacheManager = class {
|
|
1729
|
+
constructor(enabled = true, config = {}) {
|
|
1730
|
+
this.entity = new CacheStore({ ...DEFAULT_CONFIG2.entity, ...config.entity });
|
|
1731
|
+
this.query = new CacheStore({ ...DEFAULT_CONFIG2.query, ...config.query });
|
|
1732
|
+
this.enabled = enabled;
|
|
1733
|
+
}
|
|
1734
|
+
/**
|
|
1735
|
+
* Cache-aside: return the cached value if present, otherwise call fetchFn,
|
|
1736
|
+
* store the result, and return it.
|
|
1737
|
+
*/
|
|
1738
|
+
async getOrSet(key, ttlMs, fetchFn) {
|
|
1739
|
+
if (this.enabled) {
|
|
1740
|
+
const cached = this.query.get(key);
|
|
1741
|
+
if (cached !== void 0) return cached;
|
|
1742
|
+
}
|
|
1743
|
+
const value = await fetchFn();
|
|
1744
|
+
if (this.enabled) this.query.set(key, value, ttlMs);
|
|
1745
|
+
return value;
|
|
1746
|
+
}
|
|
1747
|
+
/** Invalidate a specific key in the query cache */
|
|
1748
|
+
invalidateKey(key) {
|
|
1749
|
+
this.query.delete(key);
|
|
1750
|
+
}
|
|
1751
|
+
/** Invalidate all query cache keys matching a string prefix or RegExp */
|
|
1752
|
+
invalidatePattern(pattern) {
|
|
1753
|
+
this.query.deletePattern(pattern);
|
|
1754
|
+
}
|
|
1755
|
+
/** Invalidate all entity and query cache entries for the given prefix */
|
|
1756
|
+
invalidateAll(prefix) {
|
|
1757
|
+
this.entity.deletePattern(prefix);
|
|
1758
|
+
this.query.deletePattern(prefix);
|
|
1759
|
+
}
|
|
1760
|
+
setEnabled(enabled) {
|
|
1761
|
+
this.enabled = enabled;
|
|
1762
|
+
}
|
|
1763
|
+
isEnabled() {
|
|
1764
|
+
return this.enabled;
|
|
1765
|
+
}
|
|
1766
|
+
/** Remove expired entries from both stores */
|
|
1767
|
+
maintenance() {
|
|
1768
|
+
return {
|
|
1769
|
+
entityRemoved: this.entity.cleanup(),
|
|
1770
|
+
queryRemoved: this.query.cleanup()
|
|
1771
|
+
};
|
|
1772
|
+
}
|
|
1773
|
+
clear() {
|
|
1774
|
+
this.entity.clear();
|
|
1775
|
+
this.query.clear();
|
|
1776
|
+
}
|
|
1777
|
+
getStats() {
|
|
1778
|
+
return { entity: this.entity.getStats(), query: this.query.getStats() };
|
|
1779
|
+
}
|
|
1780
|
+
};
|
|
1781
|
+
var _cacheManager = null;
|
|
1782
|
+
function getCacheManager(config) {
|
|
1783
|
+
if (!_cacheManager) _cacheManager = new CacheManager(true, config);
|
|
1784
|
+
return _cacheManager;
|
|
1785
|
+
}
|
|
1786
|
+
function resetCacheManager() {
|
|
1787
|
+
_cacheManager?.clear();
|
|
1788
|
+
_cacheManager = null;
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1791
|
+
// src/constants/options.ts
|
|
1792
|
+
var COMPANY_SIZE_OPTIONS = [
|
|
1793
|
+
{ value: "startup", label: "Startup (1-10)" },
|
|
1794
|
+
{ value: "small", label: "Small (11-50)" },
|
|
1795
|
+
{ value: "smb", label: "SMB (51-200)" },
|
|
1796
|
+
{ value: "mid_market", label: "Mid-Market (201-1000)" },
|
|
1797
|
+
{ value: "enterprise", label: "Enterprise (1000+)" }
|
|
1798
|
+
];
|
|
1799
|
+
var LIFECYCLE_STAGE_OPTIONS = [
|
|
1800
|
+
{ value: "subscriber", label: "Subscriber" },
|
|
1801
|
+
{ value: "lead", label: "Lead" },
|
|
1802
|
+
{ value: "mql", label: "MQL" },
|
|
1803
|
+
{ value: "sql", label: "SQL" },
|
|
1804
|
+
{ value: "opportunity", label: "Opportunity" },
|
|
1805
|
+
{ value: "customer", label: "Customer" },
|
|
1806
|
+
{ value: "evangelist", label: "Evangelist" }
|
|
1807
|
+
];
|
|
1808
|
+
var REVENUE_RANGE_OPTIONS = [
|
|
1809
|
+
{ value: "pre_revenue", label: "Pre-revenue" },
|
|
1810
|
+
{ value: "0_1m", label: "$0-$1M" },
|
|
1811
|
+
{ value: "1m_10m", label: "$1M-$10M" },
|
|
1812
|
+
{ value: "10m_50m", label: "$10M-$50M" },
|
|
1813
|
+
{ value: "50m_plus", label: "$50M+" }
|
|
1814
|
+
];
|
|
1815
|
+
var ACTIVITY_TYPE_OPTIONS = [
|
|
1816
|
+
{ value: "call", label: "Call" },
|
|
1817
|
+
{ value: "email", label: "Email" },
|
|
1818
|
+
{ value: "meeting", label: "Meeting" },
|
|
1819
|
+
{ value: "demo", label: "Demo" },
|
|
1820
|
+
{ value: "note", label: "Note" },
|
|
1821
|
+
{ value: "task", label: "Task" }
|
|
1822
|
+
];
|
|
1823
|
+
var ACTIVITY_OUTCOME_OPTIONS = [
|
|
1824
|
+
{ value: "scheduled_demo", label: "Scheduled Demo" },
|
|
1825
|
+
{ value: "scheduled_meeting", label: "Scheduled Meeting" },
|
|
1826
|
+
{ value: "callback_later", label: "Callback Later" },
|
|
1827
|
+
{ value: "left_voicemail", label: "Left Voicemail" },
|
|
1828
|
+
{ value: "no_answer", label: "No Answer" },
|
|
1829
|
+
{ value: "not_interested", label: "Not Interested" },
|
|
1830
|
+
{ value: "successful", label: "Successful" },
|
|
1831
|
+
{ value: "unsuccessful", label: "Unsuccessful" }
|
|
1832
|
+
];
|
|
1833
|
+
var LOSS_REASON_OPTIONS = [
|
|
1834
|
+
{ value: "price", label: "Price / Budget" },
|
|
1835
|
+
{ value: "timing", label: "Bad Timing" },
|
|
1836
|
+
{ value: "competitor", label: "Chose Competitor" },
|
|
1837
|
+
{ value: "no_need", label: "No Need" },
|
|
1838
|
+
{ value: "no_response", label: "No Response" },
|
|
1839
|
+
{ value: "other", label: "Other" }
|
|
1840
|
+
];
|
|
1841
|
+
var DEAL_STAGE_OPTIONS = [
|
|
1842
|
+
{ value: "qualification", label: "Qualification" },
|
|
1843
|
+
{ value: "discovery", label: "Discovery" },
|
|
1844
|
+
{ value: "demo", label: "Demo" },
|
|
1845
|
+
{ value: "proposal", label: "Proposal" },
|
|
1846
|
+
{ value: "negotiation", label: "Negotiation" },
|
|
1847
|
+
{ value: "closed_won", label: "Closed Won" },
|
|
1848
|
+
{ value: "closed_lost", label: "Closed Lost" }
|
|
1849
|
+
];
|
|
1850
|
+
function useServerList(endpoint, options = {}) {
|
|
1851
|
+
const {
|
|
1852
|
+
pageSize: initialPageSize = 25,
|
|
1853
|
+
initialPage = 1,
|
|
1854
|
+
initialSearch = "",
|
|
1855
|
+
initialSortField = "",
|
|
1856
|
+
initialSortDir = "asc",
|
|
1857
|
+
params: extraParams = {},
|
|
1858
|
+
disabled = false,
|
|
1859
|
+
fetcher = fetch
|
|
1860
|
+
} = options;
|
|
1861
|
+
const [page, setPage] = react.useState(initialPage);
|
|
1862
|
+
const [pageSize, setPageSize] = react.useState(initialPageSize);
|
|
1863
|
+
const [search, setSearchState] = react.useState(initialSearch);
|
|
1864
|
+
const [sortField, setSortField] = react.useState(initialSortField);
|
|
1865
|
+
const [sortDir, setSortDir] = react.useState(initialSortDir);
|
|
1866
|
+
const [data, setData] = react.useState([]);
|
|
1867
|
+
const [total, setTotal] = react.useState(0);
|
|
1868
|
+
const [loading, setLoading] = react.useState(!disabled);
|
|
1869
|
+
const [error, setError] = react.useState(null);
|
|
1870
|
+
const refreshCountRef = react.useRef(0);
|
|
1871
|
+
const fetchData = react.useCallback(async () => {
|
|
1872
|
+
if (disabled) return;
|
|
1873
|
+
setLoading(true);
|
|
1874
|
+
setError(null);
|
|
1875
|
+
const searchParams = new URLSearchParams();
|
|
1876
|
+
searchParams.set("page", String(page));
|
|
1877
|
+
searchParams.set("pageSize", String(pageSize));
|
|
1878
|
+
if (sortField) {
|
|
1879
|
+
searchParams.set("sortField", sortField);
|
|
1880
|
+
searchParams.set("sortDirection", sortDir);
|
|
1881
|
+
}
|
|
1882
|
+
if (search) searchParams.set("search", search);
|
|
1883
|
+
for (const [k, v] of Object.entries(extraParams)) {
|
|
1884
|
+
searchParams.set(k, String(v));
|
|
1885
|
+
}
|
|
1886
|
+
const sep = endpoint.includes("?") ? "&" : "?";
|
|
1887
|
+
const url = `${endpoint}${sep}${searchParams.toString()}`;
|
|
1888
|
+
try {
|
|
1889
|
+
const response = await fetcher(url);
|
|
1890
|
+
if (!response.ok) {
|
|
1891
|
+
throw new Error(`Request failed: ${response.status} ${response.statusText}`);
|
|
1892
|
+
}
|
|
1893
|
+
const json = await response.json();
|
|
1894
|
+
if (Array.isArray(json)) {
|
|
1895
|
+
setData(json);
|
|
1896
|
+
setTotal(json.length);
|
|
1897
|
+
} else if (json.results !== void 0) {
|
|
1898
|
+
setData(json.results);
|
|
1899
|
+
setTotal(json.count ?? json.results.length);
|
|
1900
|
+
} else if (json.data !== void 0) {
|
|
1901
|
+
setData(json.data);
|
|
1902
|
+
setTotal(json.total ?? json.data.length);
|
|
1903
|
+
} else {
|
|
1904
|
+
setData([]);
|
|
1905
|
+
setTotal(0);
|
|
1906
|
+
}
|
|
1907
|
+
} catch (err) {
|
|
1908
|
+
setError(err instanceof Error ? err.message : "Failed to load data");
|
|
1909
|
+
setData([]);
|
|
1910
|
+
} finally {
|
|
1911
|
+
setLoading(false);
|
|
1912
|
+
}
|
|
1913
|
+
}, [endpoint, page, pageSize, search, sortField, sortDir, disabled, fetcher, refreshCountRef.current]);
|
|
1914
|
+
react.useEffect(() => {
|
|
1915
|
+
fetchData();
|
|
1916
|
+
}, [fetchData]);
|
|
1917
|
+
const setSearch = react.useCallback((value) => {
|
|
1918
|
+
setPage(1);
|
|
1919
|
+
setSearchState(value);
|
|
1920
|
+
}, []);
|
|
1921
|
+
const setSort = react.useCallback(
|
|
1922
|
+
(field, dir) => {
|
|
1923
|
+
if (field === sortField && !dir) {
|
|
1924
|
+
setSortDir((prev) => prev === "asc" ? "desc" : "asc");
|
|
1925
|
+
} else {
|
|
1926
|
+
setSortField(field);
|
|
1927
|
+
setSortDir(dir ?? "asc");
|
|
1928
|
+
}
|
|
1929
|
+
setPage(1);
|
|
1930
|
+
},
|
|
1931
|
+
[sortField]
|
|
1932
|
+
);
|
|
1933
|
+
const refresh = react.useCallback(() => {
|
|
1934
|
+
refreshCountRef.current += 1;
|
|
1935
|
+
fetchData();
|
|
1936
|
+
}, [fetchData]);
|
|
1937
|
+
return {
|
|
1938
|
+
data,
|
|
1939
|
+
total,
|
|
1940
|
+
loading,
|
|
1941
|
+
error,
|
|
1942
|
+
page,
|
|
1943
|
+
pageSize,
|
|
1944
|
+
search,
|
|
1945
|
+
sortField,
|
|
1946
|
+
sortDir,
|
|
1947
|
+
setPage,
|
|
1948
|
+
setPageSize,
|
|
1949
|
+
setSearch,
|
|
1950
|
+
setSort,
|
|
1951
|
+
refresh
|
|
1952
|
+
};
|
|
1953
|
+
}
|
|
1954
|
+
function useServerDetail(endpoint, id, options = {}) {
|
|
1955
|
+
const { fetcher = fetch, disabled = false } = options;
|
|
1956
|
+
const [data, setData] = react.useState(null);
|
|
1957
|
+
const [loading, setLoading] = react.useState(!disabled && id !== null && id !== void 0);
|
|
1958
|
+
const [error, setError] = react.useState(null);
|
|
1959
|
+
const fetchData = react.useCallback(async () => {
|
|
1960
|
+
if (disabled || id === null) return;
|
|
1961
|
+
const url = id !== void 0 ? `${endpoint}${endpoint.endsWith("/") ? "" : "/"}${id}/` : endpoint;
|
|
1962
|
+
setLoading(true);
|
|
1963
|
+
setError(null);
|
|
1964
|
+
try {
|
|
1965
|
+
const response = await fetcher(url);
|
|
1966
|
+
if (!response.ok) {
|
|
1967
|
+
throw new Error(`Request failed: ${response.status} ${response.statusText}`);
|
|
1968
|
+
}
|
|
1969
|
+
setData(await response.json());
|
|
1970
|
+
} catch (err) {
|
|
1971
|
+
setError(err instanceof Error ? err.message : "Failed to load data");
|
|
1972
|
+
} finally {
|
|
1973
|
+
setLoading(false);
|
|
1974
|
+
}
|
|
1975
|
+
}, [endpoint, id, disabled, fetcher]);
|
|
1976
|
+
react.useEffect(() => {
|
|
1977
|
+
fetchData();
|
|
1978
|
+
}, [fetchData]);
|
|
1979
|
+
return { data, loading, error, refresh: fetchData };
|
|
1980
|
+
}
|
|
1981
|
+
|
|
1982
|
+
// src/lib/env.ts
|
|
1983
|
+
function getRequiredEnv(key) {
|
|
1984
|
+
const value = process.env[key];
|
|
1985
|
+
if (!value) {
|
|
1986
|
+
throw new Error(`Missing required environment variable: ${key}`);
|
|
1987
|
+
}
|
|
1988
|
+
return value;
|
|
1989
|
+
}
|
|
1990
|
+
function getOptionalEnv(key, defaultValue) {
|
|
1991
|
+
return process.env[key] ?? defaultValue;
|
|
1992
|
+
}
|
|
1993
|
+
function validateEnvVars(required) {
|
|
1994
|
+
const missing = required.filter((key) => !process.env[key]);
|
|
1995
|
+
if (missing.length > 0) {
|
|
1996
|
+
throw new Error(`Missing required environment variables: ${missing.join(", ")}`);
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
|
|
2000
|
+
// src/index.ts
|
|
2001
|
+
function createStartSimpliApi(config = {}) {
|
|
2002
|
+
const client = createApiClient(config);
|
|
2003
|
+
return {
|
|
2004
|
+
client,
|
|
2005
|
+
contacts: new ContactsApi(client),
|
|
2006
|
+
organizations: new OrganizationsApi(client),
|
|
2007
|
+
entities: new EntitiesApi(client),
|
|
2008
|
+
workflows: new WorkflowsApi(client),
|
|
2009
|
+
messages: new MessagesApi(client),
|
|
2010
|
+
funnels: new FunnelsApi(client)
|
|
2011
|
+
};
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
exports.ACTIVITY_OUTCOME_OPTIONS = ACTIVITY_OUTCOME_OPTIONS;
|
|
2015
|
+
exports.ACTIVITY_TYPE_OPTIONS = ACTIVITY_TYPE_OPTIONS;
|
|
2016
|
+
exports.ApiClient = ApiClient;
|
|
2017
|
+
exports.ApiException = ApiException;
|
|
2018
|
+
exports.AppError = AppError;
|
|
2019
|
+
exports.AppErrorCode = AppErrorCode;
|
|
2020
|
+
exports.AuthenticationError = AuthenticationError;
|
|
2021
|
+
exports.AuthorizationError = AuthorizationError;
|
|
2022
|
+
exports.COMPANY_SIZE_OPTIONS = COMPANY_SIZE_OPTIONS;
|
|
2023
|
+
exports.CacheManager = CacheManager;
|
|
2024
|
+
exports.CacheStore = CacheStore;
|
|
2025
|
+
exports.ConflictError = ConflictError;
|
|
2026
|
+
exports.ContactsApi = ContactsApi;
|
|
2027
|
+
exports.DEAL_STAGE_OPTIONS = DEAL_STAGE_OPTIONS;
|
|
2028
|
+
exports.DatabaseError = DatabaseError;
|
|
2029
|
+
exports.ENDPOINTS = ENDPOINTS;
|
|
2030
|
+
exports.EntitiesApi = EntitiesApi;
|
|
2031
|
+
exports.EntityQueryBuilder = EntityQueryBuilder;
|
|
2032
|
+
exports.ErrorCode = ErrorCode;
|
|
2033
|
+
exports.ErrorCodes = ErrorCodes;
|
|
2034
|
+
exports.ErrorMessages = ErrorMessages;
|
|
2035
|
+
exports.ExternalServiceError = ExternalServiceError;
|
|
2036
|
+
exports.FetchWrapper = FetchWrapper;
|
|
2037
|
+
exports.FieldErrorSchema = FieldErrorSchema;
|
|
2038
|
+
exports.FunnelsApi = FunnelsApi;
|
|
2039
|
+
exports.LIFECYCLE_STAGE_OPTIONS = LIFECYCLE_STAGE_OPTIONS;
|
|
2040
|
+
exports.LOSS_REASON_OPTIONS = LOSS_REASON_OPTIONS;
|
|
2041
|
+
exports.MAX_CHAT_MESSAGE_LENGTH = MAX_CHAT_MESSAGE_LENGTH;
|
|
2042
|
+
exports.MAX_PROMPT_LENGTH = MAX_PROMPT_LENGTH;
|
|
2043
|
+
exports.MessagesApi = MessagesApi;
|
|
2044
|
+
exports.NotFoundError = NotFoundError;
|
|
2045
|
+
exports.OrganizationsApi = OrganizationsApi;
|
|
2046
|
+
exports.REVENUE_RANGE_OPTIONS = REVENUE_RANGE_OPTIONS;
|
|
2047
|
+
exports.RateLimitError = RateLimitError;
|
|
2048
|
+
exports.StandardErrorResponseSchema = StandardErrorResponseSchema;
|
|
2049
|
+
exports.ValidationError = ValidationError;
|
|
2050
|
+
exports.WorkflowsApi = WorkflowsApi;
|
|
2051
|
+
exports.applyCorsHeaders = applyCorsHeaders;
|
|
2052
|
+
exports.buildFilterParams = buildFilterParams;
|
|
2053
|
+
exports.buildOrderingParam = buildOrderingParam;
|
|
2054
|
+
exports.buildQueryString = buildQueryString;
|
|
2055
|
+
exports.buildUrl = buildUrl;
|
|
2056
|
+
exports.createApiClient = createApiClient;
|
|
2057
|
+
exports.createCorsMiddleware = createCorsMiddleware;
|
|
2058
|
+
exports.createRateLimiter = createRateLimiter;
|
|
2059
|
+
exports.createStartSimpliApi = createStartSimpliApi;
|
|
2060
|
+
exports.getCacheManager = getCacheManager;
|
|
2061
|
+
exports.getClientIP = getClientIP;
|
|
2062
|
+
exports.getCorsHeaders = getCorsHeaders;
|
|
2063
|
+
exports.getErrorCodeFromStatus = getErrorCodeFromStatus;
|
|
2064
|
+
exports.getOptionalEnv = getOptionalEnv;
|
|
2065
|
+
exports.getRequiredEnv = getRequiredEnv;
|
|
2066
|
+
exports.handleFetchError = handleFetchError;
|
|
2067
|
+
exports.isApiException = isApiException;
|
|
2068
|
+
exports.isAuthError = isAuthError;
|
|
2069
|
+
exports.isDRFPaginatedResponse = isDRFPaginatedResponse;
|
|
2070
|
+
exports.isFunnelRunConflict = isFunnelRunConflict;
|
|
2071
|
+
exports.isFunnelValidationError = isFunnelValidationError;
|
|
2072
|
+
exports.isNotFoundError = isNotFoundError;
|
|
2073
|
+
exports.isPrismaError = isPrismaError;
|
|
2074
|
+
exports.isValidationError = isValidationError;
|
|
2075
|
+
exports.mergeQueryParams = mergeQueryParams;
|
|
2076
|
+
exports.normalizeId = normalizeId;
|
|
2077
|
+
exports.normalizePaginated = normalizePaginated;
|
|
2078
|
+
exports.parseErrorResponse = parseErrorResponse;
|
|
2079
|
+
exports.resetCacheManager = resetCacheManager;
|
|
2080
|
+
exports.resolveApiUrl = resolveApiUrl;
|
|
2081
|
+
exports.sanitizeChatMessage = sanitizeChatMessage;
|
|
2082
|
+
exports.sanitizeHtml = sanitizeHtml;
|
|
2083
|
+
exports.sanitizeSearchQuery = sanitizeSearchQuery;
|
|
2084
|
+
exports.sanitizeUserInput = sanitizeUserInput;
|
|
2085
|
+
exports.toAppError = toAppError;
|
|
2086
|
+
exports.useServerDetail = useServerDetail;
|
|
2087
|
+
exports.useServerList = useServerList;
|
|
2088
|
+
exports.validateApiResponse = validateApiResponse;
|
|
2089
|
+
exports.validateEnvVars = validateEnvVars;
|
|
2090
|
+
exports.validateIdentifier = validateIdentifier;
|
|
2091
|
+
//# sourceMappingURL=index.js.map
|
|
2092
|
+
//# sourceMappingURL=index.js.map
|