@snackbase/sdk 0.1.0

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.mjs ADDED
@@ -0,0 +1,2516 @@
1
+ //#region src/types/config.ts
2
+ const DEFAULT_CONFIG = {
3
+ timeout: 3e4,
4
+ enableAutoRefresh: true,
5
+ refreshBeforeExpiry: 300,
6
+ maxRetries: 3,
7
+ retryDelay: 1e3,
8
+ logLevel: "error",
9
+ enableLogging: false,
10
+ defaultAccount: void 0,
11
+ maxRealTimeRetries: 10,
12
+ realTimeReconnectionDelay: 1e3
13
+ };
14
+
15
+ //#endregion
16
+ //#region src/utils/platform.ts
17
+ /**
18
+ * Detects the platform and returns the recommended storage backend.
19
+ * - Web: localStorage
20
+ * - React Native: asyncStorage
21
+ */
22
+ function getAutoDetectedStorage() {
23
+ if (typeof navigator !== "undefined" && navigator.product === "ReactNative") return "asyncStorage";
24
+ if (typeof window !== "undefined" && typeof window.localStorage !== "undefined") return "localStorage";
25
+ return "memory";
26
+ }
27
+
28
+ //#endregion
29
+ //#region src/core/errors.ts
30
+ /**
31
+ * Base error class for all SnackBase SDK errors.
32
+ */
33
+ var SnackBaseError = class SnackBaseError extends Error {
34
+ code;
35
+ status;
36
+ details;
37
+ field;
38
+ retryable;
39
+ constructor(message, code, status, details, retryable = false, field) {
40
+ super(message);
41
+ this.name = this.constructor.name;
42
+ this.code = code;
43
+ this.status = status;
44
+ this.details = details;
45
+ this.retryable = retryable;
46
+ this.field = field;
47
+ Object.setPrototypeOf(this, SnackBaseError.prototype);
48
+ }
49
+ };
50
+ /**
51
+ * Thrown when authentication fails (401).
52
+ */
53
+ var AuthenticationError = class AuthenticationError extends SnackBaseError {
54
+ constructor(message = "Authentication failed", details) {
55
+ super(message, "AUTHENTICATION_ERROR", 401, details, false);
56
+ Object.setPrototypeOf(this, AuthenticationError.prototype);
57
+ }
58
+ };
59
+ /**
60
+ * Thrown when the user is not authorized to perform an action (403).
61
+ */
62
+ var AuthorizationError = class AuthorizationError extends SnackBaseError {
63
+ constructor(message = "Not authorized", details) {
64
+ super(message, "AUTHORIZATION_ERROR", 403, details, false);
65
+ Object.setPrototypeOf(this, AuthorizationError.prototype);
66
+ }
67
+ };
68
+ /**
69
+ * Thrown when a resource is not found (404).
70
+ */
71
+ var NotFoundError = class NotFoundError extends SnackBaseError {
72
+ constructor(message = "Resource not found", details) {
73
+ super(message, "NOT_FOUND_ERROR", 404, details, false);
74
+ Object.setPrototypeOf(this, NotFoundError.prototype);
75
+ }
76
+ };
77
+ /**
78
+ * Thrown when a conflict occurs (409).
79
+ */
80
+ var ConflictError = class ConflictError extends SnackBaseError {
81
+ constructor(message = "Resource conflict", details) {
82
+ super(message, "CONFLICT_ERROR", 409, details, false);
83
+ Object.setPrototypeOf(this, ConflictError.prototype);
84
+ }
85
+ };
86
+ /**
87
+ * Thrown when validation fails (422).
88
+ */
89
+ var ValidationError = class ValidationError extends SnackBaseError {
90
+ fields;
91
+ constructor(message = "Validation failed", details) {
92
+ super(message, "VALIDATION_ERROR", 422, details, false, details?.field);
93
+ Object.setPrototypeOf(this, ValidationError.prototype);
94
+ if (details?.errors) this.fields = details.errors;
95
+ }
96
+ };
97
+ /**
98
+ * Thrown when rate limit is exceeded (429).
99
+ */
100
+ var RateLimitError = class RateLimitError extends SnackBaseError {
101
+ retryAfter;
102
+ constructor(message = "Rate limit exceeded", details, retryAfter) {
103
+ super(message, "RATE_LIMIT_ERROR", 429, details, true);
104
+ Object.setPrototypeOf(this, RateLimitError.prototype);
105
+ this.retryAfter = retryAfter;
106
+ }
107
+ };
108
+ /**
109
+ * Thrown when a network failure occurs.
110
+ */
111
+ var NetworkError = class NetworkError extends SnackBaseError {
112
+ constructor(message = "Network error", details) {
113
+ super(message, "NETWORK_ERROR", void 0, details, true);
114
+ Object.setPrototypeOf(this, NetworkError.prototype);
115
+ }
116
+ };
117
+ /**
118
+ * Thrown when a request times out.
119
+ */
120
+ var TimeoutError = class TimeoutError extends SnackBaseError {
121
+ constructor(message = "Request timed out", details) {
122
+ super(message, "TIMEOUT_ERROR", void 0, details, true);
123
+ Object.setPrototypeOf(this, TimeoutError.prototype);
124
+ }
125
+ };
126
+ /**
127
+ * Thrown when a server error occurs (500+).
128
+ */
129
+ var ServerError = class ServerError extends SnackBaseError {
130
+ constructor(message = "Internal server error", status = 500, details) {
131
+ super(message, "SERVER_ERROR", status, details, true);
132
+ Object.setPrototypeOf(this, ServerError.prototype);
133
+ }
134
+ };
135
+
136
+ //#endregion
137
+ //#region src/core/http-client.ts
138
+ /**
139
+ * Robust HTTP client wrapping the fetch API.
140
+ */
141
+ var HttpClient = class {
142
+ config;
143
+ requestInterceptors = [];
144
+ responseInterceptors = [];
145
+ errorInterceptors = [];
146
+ constructor(config) {
147
+ this.config = {
148
+ timeout: 3e4,
149
+ maxRetries: 3,
150
+ retryDelay: 1e3,
151
+ ...config
152
+ };
153
+ }
154
+ addRequestInterceptor(interceptor) {
155
+ this.requestInterceptors.push(interceptor);
156
+ }
157
+ addResponseInterceptor(interceptor) {
158
+ this.responseInterceptors.push(interceptor);
159
+ }
160
+ addErrorInterceptor(interceptor) {
161
+ this.errorInterceptors.push(interceptor);
162
+ }
163
+ async request(req) {
164
+ let currentReq = {
165
+ url: req.url || "",
166
+ method: req.method || "GET",
167
+ headers: req.headers || {},
168
+ body: req.body,
169
+ params: req.params,
170
+ timeout: req.timeout ?? this.config.timeout,
171
+ signal: req.signal
172
+ };
173
+ const startTime = Date.now();
174
+ const requestId = Math.random().toString(36).substring(7);
175
+ try {
176
+ for (const interceptor of this.requestInterceptors) currentReq = await interceptor(currentReq);
177
+ const fullUrl = this.resolveUrl(currentReq.url, currentReq);
178
+ if (this.config.logger) this.config.logger.debug(`[${requestId}] Request: ${currentReq.method} ${fullUrl}`, {
179
+ headers: currentReq.headers,
180
+ body: currentReq.body
181
+ });
182
+ const controller = new AbortController();
183
+ const { signal } = controller;
184
+ if (currentReq.signal) currentReq.signal.addEventListener("abort", () => controller.abort());
185
+ const timeoutId = setTimeout(() => controller.abort(), currentReq.timeout);
186
+ let retryCount = 0;
187
+ const executeFetch = async () => {
188
+ try {
189
+ const fetchOptions = {
190
+ method: currentReq.method,
191
+ headers: currentReq.headers,
192
+ body: currentReq.body ? JSON.stringify(currentReq.body) : void 0,
193
+ signal
194
+ };
195
+ const response = await fetch(fullUrl, fetchOptions);
196
+ clearTimeout(timeoutId);
197
+ let data;
198
+ const contentType = response.headers.get("content-type");
199
+ if (contentType && contentType.includes("application/json")) data = await response.json();
200
+ else data = await response.text();
201
+ let httpResponse = {
202
+ data,
203
+ status: response.status,
204
+ headers: response.headers,
205
+ request: currentReq
206
+ };
207
+ const duration = Date.now() - startTime;
208
+ if (this.config.logger) {
209
+ this.config.logger.debug(`[${requestId}] Response: ${response.status} (${duration}ms)`, {
210
+ status: response.status,
211
+ headers: response.headers,
212
+ data
213
+ });
214
+ if (duration > 1e3) this.config.logger.warn(`[${requestId}] Slow request detected: ${currentReq.method} ${fullUrl} took ${duration}ms`);
215
+ }
216
+ for (const interceptor of this.responseInterceptors) httpResponse = await interceptor(httpResponse);
217
+ return httpResponse;
218
+ } catch (error) {
219
+ clearTimeout(timeoutId);
220
+ let processedError = error;
221
+ if (error.name === "AbortError") processedError = new TimeoutError(`Request timed out after ${currentReq.timeout}ms`);
222
+ else if (!(error instanceof Error)) processedError = new NetworkError(error.message || "Network request failed", error);
223
+ for (const interceptor of this.errorInterceptors) try {
224
+ processedError = await interceptor(processedError);
225
+ } catch (interceptorError) {
226
+ processedError = interceptorError;
227
+ }
228
+ if (this.shouldRetry(processedError, retryCount)) {
229
+ retryCount++;
230
+ const delay = this.calculateRetryDelay(retryCount);
231
+ if (this.config.logger) this.config.logger.info(`[${requestId}] Retrying request (attempt ${retryCount}/${this.config.maxRetries})...`);
232
+ await new Promise((resolve) => setTimeout(resolve, delay));
233
+ return executeFetch();
234
+ }
235
+ throw processedError;
236
+ }
237
+ };
238
+ return await executeFetch();
239
+ } catch (error) {
240
+ const duration = Date.now() - startTime;
241
+ if (this.config.logger) this.config.logger.error(`[${requestId}] Request failed (${duration}ms): ${error.message}`, error);
242
+ throw error;
243
+ }
244
+ }
245
+ async get(url, config) {
246
+ return this.request({
247
+ ...config,
248
+ url,
249
+ method: "GET"
250
+ });
251
+ }
252
+ async post(url, body, config) {
253
+ return this.request({
254
+ ...config,
255
+ url,
256
+ method: "POST",
257
+ body
258
+ });
259
+ }
260
+ async put(url, body, config) {
261
+ return this.request({
262
+ ...config,
263
+ url,
264
+ method: "PUT",
265
+ body
266
+ });
267
+ }
268
+ async patch(url, body, config) {
269
+ return this.request({
270
+ ...config,
271
+ url,
272
+ method: "PATCH",
273
+ body
274
+ });
275
+ }
276
+ async delete(url, config) {
277
+ return this.request({
278
+ ...config,
279
+ url,
280
+ method: "DELETE"
281
+ });
282
+ }
283
+ resolveUrl(url, currentReq) {
284
+ if (url.startsWith("http://") || url.startsWith("https://")) return url;
285
+ let fullUrl = `${this.config.baseUrl.endsWith("/") ? this.config.baseUrl : `${this.config.baseUrl}/`}${url.startsWith("/") ? url.slice(1) : url}`;
286
+ if (currentReq?.params) {
287
+ const urlObj = new URL(fullUrl);
288
+ Object.entries(currentReq.params).forEach(([key, value]) => {
289
+ if (value !== void 0) urlObj.searchParams.append(key, String(value));
290
+ });
291
+ fullUrl = urlObj.toString();
292
+ }
293
+ return fullUrl;
294
+ }
295
+ shouldRetry(error, retryCount) {
296
+ if (retryCount >= this.config.maxRetries) return false;
297
+ return error.retryable === true;
298
+ }
299
+ calculateRetryDelay(retryCount) {
300
+ return this.config.retryDelay * Math.pow(2, retryCount - 1);
301
+ }
302
+ };
303
+
304
+ //#endregion
305
+ //#region src/core/logger.ts
306
+ let LogLevel = /* @__PURE__ */ function(LogLevel) {
307
+ LogLevel[LogLevel["NONE"] = 0] = "NONE";
308
+ LogLevel[LogLevel["ERROR"] = 1] = "ERROR";
309
+ LogLevel[LogLevel["WARN"] = 2] = "WARN";
310
+ LogLevel[LogLevel["INFO"] = 3] = "INFO";
311
+ LogLevel[LogLevel["DEBUG"] = 4] = "DEBUG";
312
+ return LogLevel;
313
+ }({});
314
+ var Logger = class {
315
+ level;
316
+ handlers = [];
317
+ logs = [];
318
+ maxLogs = 1e3;
319
+ constructor(level = LogLevel.NONE) {
320
+ this.level = level;
321
+ this.handlers.push((entry) => {
322
+ const { level: level$1, message, data } = entry;
323
+ const args = data ? [message, data] : [message];
324
+ switch (level$1) {
325
+ case LogLevel.ERROR:
326
+ console.error("[SnackBase]", ...args);
327
+ break;
328
+ case LogLevel.WARN:
329
+ console.warn("[SnackBase]", ...args);
330
+ break;
331
+ case LogLevel.INFO:
332
+ console.info("[SnackBase]", ...args);
333
+ break;
334
+ case LogLevel.DEBUG:
335
+ console.debug("[SnackBase]", ...args);
336
+ break;
337
+ }
338
+ });
339
+ this.handlers.push((entry) => {
340
+ this.logs.push(entry);
341
+ if (this.logs.length > this.maxLogs) this.logs.shift();
342
+ });
343
+ }
344
+ setLevel(level) {
345
+ this.level = level;
346
+ }
347
+ getLogs() {
348
+ return [...this.logs];
349
+ }
350
+ clearLogs() {
351
+ this.logs = [];
352
+ }
353
+ log(level, message, data) {
354
+ if (this.level < level) return;
355
+ const entry = {
356
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
357
+ level,
358
+ message,
359
+ data
360
+ };
361
+ this.handlers.forEach((handler) => handler(entry));
362
+ }
363
+ error(message, data) {
364
+ this.log(LogLevel.ERROR, message, data);
365
+ }
366
+ warn(message, data) {
367
+ this.log(LogLevel.WARN, message, data);
368
+ }
369
+ info(message, data) {
370
+ this.log(LogLevel.INFO, message, data);
371
+ }
372
+ debug(message, data) {
373
+ this.log(LogLevel.DEBUG, message, data);
374
+ }
375
+ };
376
+
377
+ //#endregion
378
+ //#region src/core/interceptors.ts
379
+ /**
380
+ * Interceptor to set Content-Type: application/json for requests with a body.
381
+ */
382
+ const contentTypeInterceptor = (request) => {
383
+ if (request.body && !request.headers["Content-Type"]) request.headers["Content-Type"] = "application/json";
384
+ return request;
385
+ };
386
+ /**
387
+ * Interceptor to inject Authorization and API Key headers.
388
+ */
389
+ const createAuthInterceptor = (getToken, apiKey) => {
390
+ return (request) => {
391
+ const isUserSpecific = request.url.includes("/auth/oauth/") || request.url.includes("/auth/saml/");
392
+ if (apiKey && !isUserSpecific) request.headers["X-API-Key"] = apiKey;
393
+ const token = getToken();
394
+ if (token) request.headers["Authorization"] = `Bearer ${token}`;
395
+ return request;
396
+ };
397
+ };
398
+ /**
399
+ * Interceptor to normalize error responses into SnackBaseError instances.
400
+ */
401
+ const errorNormalizationInterceptor = (response) => {
402
+ if (response.status >= 400) throw createErrorFromResponse(response);
403
+ return response;
404
+ };
405
+ /**
406
+ * Error interceptor to handle raw fetch errors.
407
+ */
408
+ const errorInterceptor = (error) => {
409
+ if (error instanceof SnackBaseError) throw error;
410
+ if (error instanceof Error) {
411
+ if (error.name === "AbortError") return error;
412
+ throw new NetworkError(error.message, error);
413
+ }
414
+ throw new NetworkError(String(error), error);
415
+ };
416
+ /**
417
+ * Creates a specific SnackBaseError based on the response status and body.
418
+ */
419
+ function createErrorFromResponse(response) {
420
+ const { status, data } = response;
421
+ const message = data?.message || data?.error || "An unexpected error occurred";
422
+ switch (status) {
423
+ case 401: return new AuthenticationError(message, data);
424
+ case 403: return new AuthorizationError(message, data);
425
+ case 404: return new NotFoundError(message, data);
426
+ case 409: return new ConflictError(message, data);
427
+ case 422: return new ValidationError(message, data);
428
+ case 429:
429
+ const retryAfter = response.headers.get("Retry-After") || data?.retryAfter;
430
+ return new RateLimitError(message, data, retryAfter ? parseInt(retryAfter, 10) : void 0);
431
+ default:
432
+ if (status >= 500) return new ServerError(message, status, data);
433
+ return new SnackBaseError(message, "UNKNOWN_ERROR", status, data, false);
434
+ }
435
+ }
436
+
437
+ //#endregion
438
+ //#region src/core/events.ts
439
+ var AuthEventEmitter = class {
440
+ listeners = {};
441
+ on(event, listener) {
442
+ if (!this.listeners[event]) this.listeners[event] = /* @__PURE__ */ new Set();
443
+ this.listeners[event].add(listener);
444
+ return () => this.off(event, listener);
445
+ }
446
+ off(event, listener) {
447
+ this.listeners[event]?.delete(listener);
448
+ }
449
+ emit(event, ...args) {
450
+ this.listeners[event]?.forEach((listener) => {
451
+ listener(...args);
452
+ });
453
+ }
454
+ };
455
+
456
+ //#endregion
457
+ //#region src/core/auth.ts
458
+ const DEFAULT_AUTH_STATE = {
459
+ user: null,
460
+ account: null,
461
+ token: null,
462
+ refreshToken: null,
463
+ isAuthenticated: false,
464
+ expiresAt: null
465
+ };
466
+ var AuthManager = class {
467
+ state = { ...DEFAULT_AUTH_STATE };
468
+ storage;
469
+ storageKey;
470
+ events;
471
+ constructor(options) {
472
+ this.storage = options.storage;
473
+ this.storageKey = options.storageKey || "sb_auth_state";
474
+ this.events = new AuthEventEmitter();
475
+ }
476
+ async initialize() {
477
+ await this.hydrate();
478
+ this.validateSession();
479
+ }
480
+ getState() {
481
+ return { ...this.state };
482
+ }
483
+ get user() {
484
+ return this.state.user;
485
+ }
486
+ get account() {
487
+ return this.state.account;
488
+ }
489
+ get token() {
490
+ return this.state.token;
491
+ }
492
+ get refreshToken() {
493
+ return this.state.refreshToken;
494
+ }
495
+ get isAuthenticated() {
496
+ return this.state.isAuthenticated;
497
+ }
498
+ async setState(newState) {
499
+ this.state = {
500
+ ...this.state,
501
+ ...newState
502
+ };
503
+ this.state.isAuthenticated = !!(this.state.token && this.state.user);
504
+ await this.persist();
505
+ if (newState.token || newState.user) this.events.emit("auth:login", this.state);
506
+ }
507
+ async clear() {
508
+ this.state = { ...DEFAULT_AUTH_STATE };
509
+ await this.storage.removeItem(this.storageKey);
510
+ this.events.emit("auth:logout");
511
+ }
512
+ on(event, listener) {
513
+ return this.events.on(event, listener);
514
+ }
515
+ async hydrate() {
516
+ try {
517
+ const stored = await this.storage.getItem(this.storageKey);
518
+ if (stored) {
519
+ const parsed = JSON.parse(stored);
520
+ this.state = {
521
+ ...DEFAULT_AUTH_STATE,
522
+ ...parsed,
523
+ isAuthenticated: !!(parsed.token && parsed.user)
524
+ };
525
+ }
526
+ } catch (error) {
527
+ console.error("Failed to hydrate auth state:", error);
528
+ await this.clear();
529
+ }
530
+ }
531
+ async persist() {
532
+ try {
533
+ await this.storage.setItem(this.storageKey, JSON.stringify(this.state));
534
+ } catch (error) {
535
+ console.error("Failed to persist auth state:", error);
536
+ }
537
+ }
538
+ validateSession() {
539
+ if (!this.state.expiresAt) return;
540
+ if (new Date(this.state.expiresAt).getTime() <= Date.now()) this.clear();
541
+ }
542
+ };
543
+
544
+ //#endregion
545
+ //#region src/core/auth-service.ts
546
+ /**
547
+ * Service for handling authentication operations.
548
+ */
549
+ var AuthService = class {
550
+ oauthStates = /* @__PURE__ */ new Map();
551
+ STATE_EXPIRY_MS = 600 * 1e3;
552
+ constructor(http, auth, apiKey, defaultAccount) {
553
+ this.http = http;
554
+ this.auth = auth;
555
+ this.apiKey = apiKey;
556
+ this.defaultAccount = defaultAccount;
557
+ }
558
+ /**
559
+ * Helper to ensure API key is not used for user-specific operations.
560
+ */
561
+ checkApiKeyRestriction() {
562
+ if (this.apiKey && !this.auth.token) {}
563
+ }
564
+ /**
565
+ * Authenticate a user with email and password.
566
+ */
567
+ async login(credentials) {
568
+ const data = { ...credentials };
569
+ if (!data.account && this.defaultAccount) data.account = this.defaultAccount;
570
+ const authData = (await this.http.post("/api/v1/auth/login", data)).data;
571
+ await this.auth.setState({
572
+ user: authData.user,
573
+ account: authData.account,
574
+ token: authData.token,
575
+ refreshToken: authData.refreshToken,
576
+ expiresAt: authData.expiresAt
577
+ });
578
+ return authData;
579
+ }
580
+ /**
581
+ * Register a new user and account.
582
+ */
583
+ async register(data) {
584
+ const payload = { ...data };
585
+ if (!payload.accountName && this.defaultAccount) payload.accountName = this.defaultAccount;
586
+ return (await this.http.post("/api/v1/auth/register", payload)).data;
587
+ }
588
+ /**
589
+ * Refresh the access token using the refresh token.
590
+ */
591
+ async refreshToken() {
592
+ const refreshToken = this.auth.refreshToken;
593
+ if (!refreshToken) throw new Error("No refresh token available");
594
+ const authData = (await this.http.post("/api/v1/auth/refresh", { refreshToken })).data;
595
+ await this.auth.setState({
596
+ user: authData.user,
597
+ account: authData.account,
598
+ token: authData.token,
599
+ refreshToken: authData.refreshToken,
600
+ expiresAt: authData.expiresAt
601
+ });
602
+ return authData;
603
+ }
604
+ /**
605
+ * Log out the current user.
606
+ */
607
+ async logout() {
608
+ try {
609
+ await this.http.post("/api/v1/auth/logout", {});
610
+ } catch (e) {} finally {
611
+ await this.auth.clear();
612
+ }
613
+ return { success: true };
614
+ }
615
+ /**
616
+ * Get the current authenticated user profile.
617
+ */
618
+ async getCurrentUser() {
619
+ const authData = (await this.http.get("/api/v1/auth/me")).data;
620
+ await this.auth.setState({
621
+ user: authData.user,
622
+ account: authData.account
623
+ });
624
+ return authData;
625
+ }
626
+ /**
627
+ * Initiate password reset flow.
628
+ */
629
+ async forgotPassword(data) {
630
+ const payload = { ...data };
631
+ if (!payload.account && this.defaultAccount) payload.account = this.defaultAccount;
632
+ return (await this.http.post("/api/v1/auth/forgot-password", payload)).data;
633
+ }
634
+ /**
635
+ * Reset password using a token.
636
+ */
637
+ async resetPassword(data) {
638
+ return (await this.http.post("/api/v1/auth/reset-password", data)).data;
639
+ }
640
+ /**
641
+ * Verify email using a token.
642
+ */
643
+ async verifyEmail(token) {
644
+ return (await this.http.post("/api/v1/auth/verify-email", { token })).data;
645
+ }
646
+ /**
647
+ * Resend the verification email to the current user.
648
+ */
649
+ async resendVerificationEmail() {
650
+ return (await this.http.post("/api/v1/auth/resend-verification", {})).data;
651
+ }
652
+ /**
653
+ * Send a verification email to a specific email address.
654
+ * This can be used by admins to send verification emails to users.
655
+ */
656
+ async sendVerification(email) {
657
+ return (await this.http.post("/api/v1/auth/send-verification", { email })).data;
658
+ }
659
+ /**
660
+ * Verify a password reset token is valid.
661
+ * Returns the email associated with the token if valid.
662
+ */
663
+ async verifyResetToken(token) {
664
+ return (await this.http.get(`/api/v1/auth/verify-reset-token/${token}`)).data;
665
+ }
666
+ /**
667
+ * Generate OAuth authorization URL for the specified provider.
668
+ */
669
+ async getOAuthUrl(provider, redirectUri, state) {
670
+ this.checkApiKeyRestriction();
671
+ const response = await this.http.post(`/api/v1/auth/oauth/${provider}/authorize`, {
672
+ redirectUri,
673
+ state
674
+ });
675
+ const { url, state: stateToken } = response.data;
676
+ this.oauthStates.set(stateToken, Date.now() + this.STATE_EXPIRY_MS);
677
+ this.cleanupExpiredStates();
678
+ return response.data;
679
+ }
680
+ /**
681
+ * Handle OAuth callback and authenticate user.
682
+ */
683
+ async handleOAuthCallback(params) {
684
+ this.checkApiKeyRestriction();
685
+ const { provider, code, redirectUri, state } = params;
686
+ const expiry = this.oauthStates.get(state);
687
+ if (!expiry || expiry < Date.now()) {
688
+ this.oauthStates.delete(state);
689
+ throw new AuthenticationError("Invalid or expired state token", { code: "INVALID_STATE" });
690
+ }
691
+ this.oauthStates.delete(state);
692
+ const authData = (await this.http.post(`/api/v1/auth/oauth/${provider}/callback`, {
693
+ code,
694
+ redirectUri,
695
+ state
696
+ })).data;
697
+ await this.auth.setState({
698
+ user: authData.user,
699
+ account: authData.account,
700
+ token: authData.token,
701
+ refreshToken: authData.refreshToken,
702
+ expiresAt: authData.expiresAt
703
+ });
704
+ return authData;
705
+ }
706
+ /**
707
+ * Generate SAML SSO authorization URL for the specified provider and account.
708
+ */
709
+ async getSAMLUrl(provider, account, relayState) {
710
+ this.checkApiKeyRestriction();
711
+ return (await this.http.get("/api/v1/auth/saml/sso", { params: {
712
+ provider,
713
+ account,
714
+ relayState
715
+ } })).data;
716
+ }
717
+ /**
718
+ * Handle SAML callback (ACS) and authenticate user.
719
+ */
720
+ async handleSAMLCallback(params) {
721
+ const authData = (await this.http.post("/api/v1/auth/saml/acs", params)).data;
722
+ await this.auth.setState({
723
+ user: authData.user,
724
+ account: authData.account,
725
+ token: authData.token,
726
+ refreshToken: authData.refreshToken,
727
+ expiresAt: authData.expiresAt
728
+ });
729
+ return authData;
730
+ }
731
+ /**
732
+ * Get SAML metadata for the specified provider and account.
733
+ */
734
+ async getSAMLMetadata(provider, account) {
735
+ return (await this.http.get("/api/v1/auth/saml/metadata", { params: {
736
+ provider,
737
+ account
738
+ } })).data;
739
+ }
740
+ /**
741
+ * Cleanup expired state tokens.
742
+ */
743
+ cleanupExpiredStates() {
744
+ const now = Date.now();
745
+ for (const [state, expiry] of this.oauthStates.entries()) if (expiry < now) this.oauthStates.delete(state);
746
+ }
747
+ };
748
+
749
+ //#endregion
750
+ //#region src/core/account-service.ts
751
+ /**
752
+ * Service for managing accounts and their users.
753
+ * Requires superadmin authentication.
754
+ */
755
+ var AccountService = class {
756
+ constructor(http) {
757
+ this.http = http;
758
+ }
759
+ /**
760
+ * List all accounts with pagination, filtering, and sorting.
761
+ */
762
+ async list(params) {
763
+ return (await this.http.get("/api/v1/accounts", { params })).data;
764
+ }
765
+ /**
766
+ * Get details for a specific account.
767
+ */
768
+ async get(accountId) {
769
+ return (await this.http.get(`/api/v1/accounts/${accountId}`)).data;
770
+ }
771
+ /**
772
+ * Create a new account.
773
+ */
774
+ async create(data) {
775
+ return (await this.http.post("/api/v1/accounts", data)).data;
776
+ }
777
+ /**
778
+ * Update an existing account.
779
+ */
780
+ async update(accountId, data) {
781
+ return (await this.http.patch(`/api/v1/accounts/${accountId}`, data)).data;
782
+ }
783
+ /**
784
+ * Delete an account and all its associated data.
785
+ */
786
+ async delete(accountId) {
787
+ await this.http.delete(`/api/v1/accounts/${accountId}`);
788
+ return { success: true };
789
+ }
790
+ /**
791
+ * Get all users belonging to a specific account.
792
+ */
793
+ async getUsers(accountId, params) {
794
+ return (await this.http.get(`/api/v1/accounts/${accountId}/users`, { params })).data;
795
+ }
796
+ };
797
+
798
+ //#endregion
799
+ //#region src/core/user-service.ts
800
+ /**
801
+ * Service for managing users.
802
+ * Requires superadmin authentication for most operations.
803
+ */
804
+ var UserService = class {
805
+ constructor(http) {
806
+ this.http = http;
807
+ }
808
+ /**
809
+ * List all users with pagination, filtering, and sorting.
810
+ */
811
+ async list(params) {
812
+ return (await this.http.get("/api/v1/users", { params })).data;
813
+ }
814
+ /**
815
+ * Get details for a specific user.
816
+ */
817
+ async get(userId) {
818
+ return (await this.http.get(`/api/v1/users/${userId}`)).data;
819
+ }
820
+ /**
821
+ * Create a new user in a specific account.
822
+ */
823
+ async create(data) {
824
+ return (await this.http.post("/api/v1/users", data)).data;
825
+ }
826
+ /**
827
+ * Update an existing user.
828
+ */
829
+ async update(userId, data) {
830
+ return (await this.http.patch(`/api/v1/users/${userId}`, data)).data;
831
+ }
832
+ /**
833
+ * Soft delete (deactivate) a user.
834
+ */
835
+ async delete(userId) {
836
+ await this.http.delete(`/api/v1/users/${userId}`);
837
+ return { success: true };
838
+ }
839
+ /**
840
+ * Manually set a new password for a user.
841
+ */
842
+ async setPassword(userId, password) {
843
+ await this.http.post(`/api/v1/users/${userId}/password`, { password });
844
+ return { success: true };
845
+ }
846
+ /**
847
+ * Manually verify a user's email address.
848
+ */
849
+ async verifyEmail(userId) {
850
+ await this.http.post(`/api/v1/users/${userId}/verify`, {});
851
+ return { success: true };
852
+ }
853
+ /**
854
+ * Resend the verification email to a user.
855
+ */
856
+ async resendVerification(userId) {
857
+ await this.http.post(`/api/v1/users/${userId}/resend-verification`, {});
858
+ return { success: true };
859
+ }
860
+ };
861
+
862
+ //#endregion
863
+ //#region src/core/collection-service.ts
864
+ /**
865
+ * Service for managing collections and their schemas.
866
+ * Requires superadmin authentication.
867
+ */
868
+ var CollectionService = class {
869
+ constructor(http) {
870
+ this.http = http;
871
+ }
872
+ /**
873
+ * List all collections.
874
+ */
875
+ async list() {
876
+ return (await this.http.get("/api/v1/collections")).data;
877
+ }
878
+ /**
879
+ * List collection names only.
880
+ */
881
+ async listNames() {
882
+ return (await this.http.get("/api/v1/collections/names")).data;
883
+ }
884
+ /**
885
+ * Get schema details for a specific collection.
886
+ */
887
+ async get(collectionId) {
888
+ return (await this.http.get(`/api/v1/collections/${collectionId}`)).data;
889
+ }
890
+ /**
891
+ * Create a new collection and its physical table.
892
+ */
893
+ async create(data) {
894
+ return (await this.http.post("/api/v1/collections", data)).data;
895
+ }
896
+ /**
897
+ * Update an existing collection schema.
898
+ * Note: Field types cannot be changed for data safety.
899
+ */
900
+ async update(collectionId, data) {
901
+ return (await this.http.patch(`/api/v1/collections/${collectionId}`, data)).data;
902
+ }
903
+ /**
904
+ * Delete a collection and drop its physical table.
905
+ */
906
+ async delete(collectionId) {
907
+ await this.http.delete(`/api/v1/collections/${collectionId}`);
908
+ return { success: true };
909
+ }
910
+ };
911
+
912
+ //#endregion
913
+ //#region src/core/query-builder.ts
914
+ /**
915
+ * Fluent interface for building complex queries.
916
+ */
917
+ var QueryBuilder = class {
918
+ _fields = [];
919
+ _expand = [];
920
+ _filterParts = [];
921
+ _sortParts = [];
922
+ _page = 1;
923
+ _perPage = 30;
924
+ _skip = 0;
925
+ _limit = 30;
926
+ _useLegacyPagination = false;
927
+ constructor(service, collection) {
928
+ this.service = service;
929
+ this.collection = collection;
930
+ }
931
+ /**
932
+ * Specify fields to return.
933
+ * @param fields Array of field names or comma-separated string
934
+ */
935
+ select(fields) {
936
+ if (Array.isArray(fields)) this._fields = [...this._fields, ...fields];
937
+ else this._fields.push(fields);
938
+ return this;
939
+ }
940
+ /**
941
+ * Expand related records.
942
+ * @param relations Array of relation names or comma-separated string
943
+ */
944
+ expand(relations) {
945
+ if (Array.isArray(relations)) this._expand = [...this._expand, ...relations];
946
+ else this._expand.push(relations);
947
+ return this;
948
+ }
949
+ filter(fieldOrString, operator, value) {
950
+ if (operator === void 0) this._filterParts.push(`(${fieldOrString})`);
951
+ else {
952
+ let expression = "";
953
+ const formattedValue = this.formatValue(value);
954
+ switch (operator) {
955
+ case "=":
956
+ expression = `${fieldOrString} = ${formattedValue}`;
957
+ break;
958
+ case "!=":
959
+ expression = `${fieldOrString} != ${formattedValue}`;
960
+ break;
961
+ case ">":
962
+ expression = `${fieldOrString} > ${formattedValue}`;
963
+ break;
964
+ case ">=":
965
+ expression = `${fieldOrString} >= ${formattedValue}`;
966
+ break;
967
+ case "<":
968
+ expression = `${fieldOrString} < ${formattedValue}`;
969
+ break;
970
+ case "<=":
971
+ expression = `${fieldOrString} <= ${formattedValue}`;
972
+ break;
973
+ case "~":
974
+ expression = `${fieldOrString} ~ ${formattedValue}`;
975
+ break;
976
+ case "!~":
977
+ expression = `${fieldOrString} !~ ${formattedValue}`;
978
+ break;
979
+ case "?=":
980
+ expression = `${fieldOrString} ?= ${formattedValue}`;
981
+ break;
982
+ case "?!=":
983
+ expression = `${fieldOrString} ?!= ${formattedValue}`;
984
+ break;
985
+ default: expression = `${fieldOrString} = ${formattedValue}`;
986
+ }
987
+ this._filterParts.push(expression);
988
+ }
989
+ return this;
990
+ }
991
+ /**
992
+ * Add sorting.
993
+ * @param field Field name
994
+ * @param direction 'asc' or 'desc' (default: 'asc')
995
+ */
996
+ sort(field, direction = "asc") {
997
+ let sortStr = field;
998
+ if (direction === "desc") sortStr = `-${field}`;
999
+ else sortStr = `+${field}`;
1000
+ this._sortParts.push(sortStr);
1001
+ return this;
1002
+ }
1003
+ /**
1004
+ * Set limit (and optionally skip).
1005
+ * Note: Using limit/skip switches to manual offset pagination.
1006
+ * @param limit Max records
1007
+ * @param skip Records to skip
1008
+ */
1009
+ limit(limit) {
1010
+ this._limit = limit;
1011
+ this._useLegacyPagination = true;
1012
+ return this;
1013
+ }
1014
+ /**
1015
+ * Set skip.
1016
+ * Note: Using limit/skip switches to manual offset pagination.
1017
+ * @param skip Records to skip
1018
+ */
1019
+ skip(skip) {
1020
+ this._skip = skip;
1021
+ this._useLegacyPagination = true;
1022
+ return this;
1023
+ }
1024
+ /**
1025
+ * Set page number and page size.
1026
+ * @param page Page number (1-based)
1027
+ * @param perPage Records per page
1028
+ */
1029
+ page(page, perPage = 30) {
1030
+ this._page = page;
1031
+ this._perPage = perPage;
1032
+ this._useLegacyPagination = false;
1033
+ return this;
1034
+ }
1035
+ /**
1036
+ * Execute query and get list of records.
1037
+ */
1038
+ async get() {
1039
+ const params = {};
1040
+ if (this._fields.length > 0) params.fields = this._fields.join(",");
1041
+ if (this._expand.length > 0) params.expand = this._expand.join(",");
1042
+ if (this._filterParts.length > 0) params.filter = this._filterParts.join(" && ");
1043
+ if (this._sortParts.length > 0) params.sort = this._sortParts.join(",");
1044
+ if (this._useLegacyPagination) {
1045
+ params.skip = this._skip;
1046
+ params.limit = this._limit;
1047
+ } else {
1048
+ if (this._page < 1) this._page = 1;
1049
+ params.skip = (this._page - 1) * this._perPage;
1050
+ params.limit = this._perPage;
1051
+ }
1052
+ return this.service.list(this.collection, params);
1053
+ }
1054
+ /**
1055
+ * Execute query and get the first matching record.
1056
+ */
1057
+ async first() {
1058
+ this.limit(1);
1059
+ this.skip(0);
1060
+ const result = await this.get();
1061
+ return result.items.length > 0 ? result.items[0] : null;
1062
+ }
1063
+ formatValue(value) {
1064
+ if (value === null || value === void 0) return "null";
1065
+ if (typeof value === "string") return `'${value.replace(/'/g, "\\'")}'`;
1066
+ return String(value);
1067
+ }
1068
+ };
1069
+
1070
+ //#endregion
1071
+ //#region src/core/record-service.ts
1072
+ /**
1073
+ * Service for performing CRUD operations on dynamic collections.
1074
+ */
1075
+ var RecordService = class {
1076
+ constructor(http) {
1077
+ this.http = http;
1078
+ }
1079
+ /**
1080
+ * List records from a collection with pagination, filtering, and sorting.
1081
+ * @template T The record type
1082
+ * @param collection Collection name
1083
+ * @param params Query parameters
1084
+ */
1085
+ async list(collection, params) {
1086
+ const formattedParams = {};
1087
+ if (params) {
1088
+ if (params.skip !== void 0) formattedParams.skip = params.skip;
1089
+ if (params.limit !== void 0) formattedParams.limit = params.limit;
1090
+ if (params.sort !== void 0) formattedParams.sort = params.sort;
1091
+ if (params.fields) formattedParams.fields = Array.isArray(params.fields) ? params.fields.join(",") : params.fields;
1092
+ if (params.expand) formattedParams.expand = Array.isArray(params.expand) ? params.expand.join(",") : params.expand;
1093
+ if (params.filter) formattedParams.filter = typeof params.filter === "string" ? params.filter : JSON.stringify(params.filter);
1094
+ }
1095
+ return (await this.http.get(`/api/v1/records/${collection}`, { params: formattedParams })).data;
1096
+ }
1097
+ /**
1098
+ * Create a query builder for the collection.
1099
+ * @template T The record type
1100
+ * @param collection Collection name
1101
+ */
1102
+ query(collection) {
1103
+ return new QueryBuilder(this, collection);
1104
+ }
1105
+ /**
1106
+ * Get a single record by ID.
1107
+ * @template T The record type
1108
+ * @param collection Collection name
1109
+ * @param recordId Record ID
1110
+ * @param params Optional query parameters (e.g., fields)
1111
+ */
1112
+ async get(collection, recordId, params) {
1113
+ const formattedParams = {};
1114
+ if (params) {
1115
+ if (params.fields) formattedParams.fields = Array.isArray(params.fields) ? params.fields.join(",") : params.fields;
1116
+ if (params.expand) formattedParams.expand = Array.isArray(params.expand) ? params.expand.join(",") : params.expand;
1117
+ }
1118
+ return (await this.http.get(`/api/v1/records/${collection}/${recordId}`, { params: formattedParams })).data;
1119
+ }
1120
+ /**
1121
+ * Create a new record in a collection.
1122
+ * @template T The record type
1123
+ * @param collection Collection name
1124
+ * @param data Record data
1125
+ */
1126
+ async create(collection, data) {
1127
+ return (await this.http.post(`/api/v1/records/${collection}`, data)).data;
1128
+ }
1129
+ /**
1130
+ * Full update of an existing record (PUT).
1131
+ * @template T The record type
1132
+ * @param collection Collection name
1133
+ * @param recordId Record ID
1134
+ * @param data Record data
1135
+ */
1136
+ async update(collection, recordId, data) {
1137
+ return (await this.http.put(`/api/v1/records/${collection}/${recordId}`, data)).data;
1138
+ }
1139
+ /**
1140
+ * Partial update of an existing record (PATCH).
1141
+ * @template T The record type
1142
+ * @param collection Collection name
1143
+ * @param recordId Record ID
1144
+ * @param data Partial record data
1145
+ */
1146
+ async patch(collection, recordId, data) {
1147
+ return (await this.http.patch(`/api/v1/records/${collection}/${recordId}`, data)).data;
1148
+ }
1149
+ /**
1150
+ * Delete a record from a collection.
1151
+ * @param collection Collection name
1152
+ * @param recordId Record ID
1153
+ */
1154
+ async delete(collection, recordId) {
1155
+ await this.http.delete(`/api/v1/records/${collection}/${recordId}`);
1156
+ return { success: true };
1157
+ }
1158
+ };
1159
+
1160
+ //#endregion
1161
+ //#region src/core/group-service.ts
1162
+ /**
1163
+ * Service for managing groups.
1164
+ */
1165
+ var GroupsService = class {
1166
+ constructor(http) {
1167
+ this.http = http;
1168
+ }
1169
+ /**
1170
+ * List all groups in the current account.
1171
+ */
1172
+ async list(params) {
1173
+ return (await this.http.get("/api/v1/groups", { params })).data;
1174
+ }
1175
+ /**
1176
+ * Get details for a specific group.
1177
+ */
1178
+ async get(groupId) {
1179
+ return (await this.http.get(`/api/v1/groups/${groupId}`)).data;
1180
+ }
1181
+ /**
1182
+ * Create a new group in the current account.
1183
+ */
1184
+ async create(data) {
1185
+ return (await this.http.post("/api/v1/groups", data)).data;
1186
+ }
1187
+ /**
1188
+ * Update a group's name or description.
1189
+ */
1190
+ async update(groupId, data) {
1191
+ return (await this.http.patch(`/api/v1/groups/${groupId}`, data)).data;
1192
+ }
1193
+ /**
1194
+ * Delete a group.
1195
+ */
1196
+ async delete(groupId) {
1197
+ await this.http.delete(`/api/v1/groups/${groupId}`);
1198
+ return { success: true };
1199
+ }
1200
+ /**
1201
+ * Add a user to a group.
1202
+ */
1203
+ async addMember(groupId, userId) {
1204
+ await this.http.post(`/api/v1/groups/${groupId}/members`, { user_id: userId });
1205
+ return { success: true };
1206
+ }
1207
+ /**
1208
+ * Remove a user from a group.
1209
+ */
1210
+ async removeMember(groupId, userId) {
1211
+ await this.http.delete(`/api/v1/groups/${groupId}/members/${userId}`);
1212
+ return { success: true };
1213
+ }
1214
+ };
1215
+
1216
+ //#endregion
1217
+ //#region src/core/invitation-service.ts
1218
+ /**
1219
+ * Service for managing user invitations.
1220
+ * Allows admins to invite users and tracks invitation status.
1221
+ */
1222
+ var InvitationService = class {
1223
+ constructor(http) {
1224
+ this.http = http;
1225
+ }
1226
+ /**
1227
+ * List all invitations in the current account.
1228
+ */
1229
+ async list(params) {
1230
+ return (await this.http.get("/api/v1/invitations", { params })).data;
1231
+ }
1232
+ /**
1233
+ * Create a new invitation for a user.
1234
+ */
1235
+ async create(data) {
1236
+ return (await this.http.post("/api/v1/invitations", data)).data;
1237
+ }
1238
+ /**
1239
+ * Resend an invitation email.
1240
+ */
1241
+ async resend(invitationId) {
1242
+ await this.http.post(`/api/v1/invitations/${invitationId}/resend`, {});
1243
+ return { success: true };
1244
+ }
1245
+ /**
1246
+ * Get public details of an invitation using a token.
1247
+ * No authentication required.
1248
+ */
1249
+ async getPublic(token) {
1250
+ return (await this.http.get(`/api/v1/invitations/${token}`)).data;
1251
+ }
1252
+ /**
1253
+ * Accept an invitation using a token and password.
1254
+ * Creates the user account and returns authentication tokens.
1255
+ */
1256
+ async accept(token, password) {
1257
+ return (await this.http.post(`/api/v1/invitations/${token}/accept`, { password })).data;
1258
+ }
1259
+ /**
1260
+ * Cancel a pending invitation.
1261
+ */
1262
+ async cancel(invitationId) {
1263
+ await this.http.delete(`/api/v1/invitations/${invitationId}`);
1264
+ return { success: true };
1265
+ }
1266
+ };
1267
+
1268
+ //#endregion
1269
+ //#region src/core/api-key-service.ts
1270
+ /**
1271
+ * Service for managing API keys.
1272
+ * API keys are used for service-to-service communication.
1273
+ */
1274
+ var ApiKeyService = class {
1275
+ constructor(http) {
1276
+ this.http = http;
1277
+ }
1278
+ /**
1279
+ * List all API keys for the current user.
1280
+ * Keys are masked except for the last 4 characters.
1281
+ */
1282
+ async list() {
1283
+ return (await this.http.get("/api/v1/admin/api-keys")).data;
1284
+ }
1285
+ /**
1286
+ * Get details for a specific API key.
1287
+ * The key itself is masked.
1288
+ */
1289
+ async get(keyId) {
1290
+ return (await this.http.get(`/api/v1/admin/api-keys/${keyId}`)).data;
1291
+ }
1292
+ /**
1293
+ * Create a new API key.
1294
+ * The response includes the full key, which is shown only once.
1295
+ */
1296
+ async create(data) {
1297
+ return (await this.http.post("/api/v1/admin/api-keys", data)).data;
1298
+ }
1299
+ /**
1300
+ * Revoke an existing API key.
1301
+ * Once revoked, the key can no longer be used.
1302
+ */
1303
+ async revoke(keyId) {
1304
+ await this.http.delete(`/api/v1/admin/api-keys/${keyId}`);
1305
+ return { success: true };
1306
+ }
1307
+ };
1308
+
1309
+ //#endregion
1310
+ //#region src/core/audit-log-service.ts
1311
+ var AuditLogService = class {
1312
+ constructor(httpClient) {
1313
+ this.httpClient = httpClient;
1314
+ }
1315
+ /**
1316
+ * Lists audit logs with optional filtering, pagination, and sorting.
1317
+ */
1318
+ async list(params) {
1319
+ return (await this.httpClient.get("/api/v1/audit-logs", { params })).data;
1320
+ }
1321
+ /**
1322
+ * Retrieves a single audit log entry by ID.
1323
+ */
1324
+ async get(logId) {
1325
+ return (await this.httpClient.get(`/api/v1/audit-logs/${logId}`)).data;
1326
+ }
1327
+ /**
1328
+ * Exports audit logs in the specified format.
1329
+ */
1330
+ async export(params, format = "json") {
1331
+ return (await this.httpClient.get("/api/v1/audit-logs/export", { params: {
1332
+ ...params,
1333
+ format
1334
+ } })).data;
1335
+ }
1336
+ };
1337
+
1338
+ //#endregion
1339
+ //#region src/core/role-service.ts
1340
+ /**
1341
+ * Service for managing roles.
1342
+ * Requires superadmin authentication.
1343
+ */
1344
+ var RoleService = class {
1345
+ constructor(http) {
1346
+ this.http = http;
1347
+ }
1348
+ /**
1349
+ * List all roles with pagination.
1350
+ */
1351
+ async list() {
1352
+ return (await this.http.get("/api/v1/roles")).data;
1353
+ }
1354
+ /**
1355
+ * Get details for a specific role.
1356
+ */
1357
+ async get(roleId) {
1358
+ return (await this.http.get(`/api/v1/roles/${roleId}`)).data;
1359
+ }
1360
+ /**
1361
+ * Create a new role.
1362
+ */
1363
+ async create(data) {
1364
+ return (await this.http.post("/api/v1/roles", data)).data;
1365
+ }
1366
+ /**
1367
+ * Update an existing role.
1368
+ */
1369
+ async update(roleId, data) {
1370
+ return (await this.http.patch(`/api/v1/roles/${roleId}`, data)).data;
1371
+ }
1372
+ /**
1373
+ * Delete a role.
1374
+ * Fails if the role is currently in use.
1375
+ */
1376
+ async delete(roleId) {
1377
+ await this.http.delete(`/api/v1/roles/${roleId}`);
1378
+ return { success: true };
1379
+ }
1380
+ };
1381
+
1382
+ //#endregion
1383
+ //#region src/core/collection-rule-service.ts
1384
+ /**
1385
+ * Service for managing collection-level access rules and field permissions.
1386
+ * Requires superadmin authentication.
1387
+ */
1388
+ var CollectionRuleService = class {
1389
+ constructor(http) {
1390
+ this.http = http;
1391
+ }
1392
+ /**
1393
+ * Get access rules and field permissions for a specific collection.
1394
+ */
1395
+ async get(collectionName) {
1396
+ return (await this.http.get(`/api/v1/collections/${collectionName}/rules`)).data;
1397
+ }
1398
+ /**
1399
+ * Update access rules and field permissions for a specific collection.
1400
+ */
1401
+ async update(collectionName, data) {
1402
+ return (await this.http.put(`/api/v1/collections/${collectionName}/rules`, data)).data;
1403
+ }
1404
+ /**
1405
+ * Validate a rule expression against a collection schema.
1406
+ */
1407
+ async validateRule(rule, operation, collectionFields) {
1408
+ return (await this.http.post("/api/v1/rules/validate", {
1409
+ rule,
1410
+ operation,
1411
+ collectionFields
1412
+ })).data;
1413
+ }
1414
+ /**
1415
+ * Test a rule evaluation with a sample context.
1416
+ */
1417
+ async testRule(rule, context) {
1418
+ return (await this.http.post("/api/v1/rules/test", {
1419
+ rule,
1420
+ context
1421
+ })).data;
1422
+ }
1423
+ };
1424
+
1425
+ //#endregion
1426
+ //#region src/core/macro-service.ts
1427
+ /**
1428
+ * Service for managing SQL macros.
1429
+ * Macros can be used in permission rules.
1430
+ * Requires superadmin authentication for most operations.
1431
+ */
1432
+ var MacroService = class {
1433
+ constructor(http) {
1434
+ this.http = http;
1435
+ }
1436
+ /**
1437
+ * List all macros, including built-in ones.
1438
+ */
1439
+ async list() {
1440
+ return (await this.http.get("/api/v1/macros")).data;
1441
+ }
1442
+ /**
1443
+ * Get details for a specific macro.
1444
+ */
1445
+ async get(macroId) {
1446
+ return (await this.http.get(`/api/v1/macros/${macroId}`)).data;
1447
+ }
1448
+ /**
1449
+ * Create a new custom macro.
1450
+ */
1451
+ async create(data) {
1452
+ return (await this.http.post("/api/v1/macros", data)).data;
1453
+ }
1454
+ /**
1455
+ * Update an existing custom macro.
1456
+ * Built-in macros cannot be updated.
1457
+ */
1458
+ async update(macroId, data) {
1459
+ return (await this.http.patch(`/api/v1/macros/${macroId}`, data)).data;
1460
+ }
1461
+ /**
1462
+ * Delete a macro.
1463
+ * Fails if the macro is built-in or currently in use.
1464
+ */
1465
+ async delete(macroId) {
1466
+ await this.http.delete(`/api/v1/macros/${macroId}`);
1467
+ return { success: true };
1468
+ }
1469
+ /**
1470
+ * Test a macro with parameters.
1471
+ */
1472
+ async test(macroId, params) {
1473
+ return (await this.http.post(`/api/v1/macros/${macroId}/test`, { params })).data;
1474
+ }
1475
+ };
1476
+
1477
+ //#endregion
1478
+ //#region src/core/dashboard-service.ts
1479
+ /**
1480
+ * Service for interacting with the Dashboard API.
1481
+ * Provides statistics and metrics for system monitoring.
1482
+ */
1483
+ var DashboardService = class {
1484
+ constructor(httpClient) {
1485
+ this.httpClient = httpClient;
1486
+ }
1487
+ /**
1488
+ * Retrieves dashboard statistics including counts for accounts, users,
1489
+ * collections, and records, as well as recent activity and health metrics.
1490
+ *
1491
+ * @returns {Promise<DashboardStats>} A promise that resolves to dashboard statistics.
1492
+ * @throws {AuthenticationError} If not authenticated.
1493
+ * @throws {AuthorizationError} If the user is not a superadmin.
1494
+ */
1495
+ async getStats() {
1496
+ return (await this.httpClient.get("/api/v1/dashboard/stats")).data;
1497
+ }
1498
+ };
1499
+
1500
+ //#endregion
1501
+ //#region src/core/admin-service.ts
1502
+ /**
1503
+ * Service for superadmin operations and system configuration management.
1504
+ */
1505
+ var AdminService = class {
1506
+ constructor(http) {
1507
+ this.http = http;
1508
+ }
1509
+ /**
1510
+ * Returns configuration statistics by category.
1511
+ */
1512
+ async getConfigurationStats() {
1513
+ return (await this.http.get("/api/v1/admin/configuration/stats")).data;
1514
+ }
1515
+ /**
1516
+ * Returns recently modified configurations.
1517
+ * @param limit Number of configurations to return
1518
+ */
1519
+ async getRecentConfigurations(limit = 10) {
1520
+ return (await this.http.get("/api/v1/admin/configuration/recent", { params: { limit } })).data;
1521
+ }
1522
+ /**
1523
+ * Returns all system-level configurations.
1524
+ * @param category Optional category filter
1525
+ */
1526
+ async listSystemConfigurations(category) {
1527
+ return (await this.http.get("/api/v1/admin/configuration/system", { params: { category } })).data;
1528
+ }
1529
+ /**
1530
+ * Returns configurations for specific account.
1531
+ * @param accountId Account ID
1532
+ * @param category Optional category filter
1533
+ */
1534
+ async listAccountConfigurations(accountId, category) {
1535
+ return (await this.http.get("/api/v1/admin/configuration/account", { params: {
1536
+ account_id: accountId,
1537
+ category
1538
+ } })).data;
1539
+ }
1540
+ /**
1541
+ * Returns decrypted configuration values with secrets masked.
1542
+ * @param configId Configuration ID
1543
+ */
1544
+ async getConfigurationValues(configId) {
1545
+ return (await this.http.get(`/api/v1/admin/configuration/${configId}/values`)).data;
1546
+ }
1547
+ /**
1548
+ * Updates configuration values.
1549
+ * @param configId Configuration ID
1550
+ * @param values New configuration values
1551
+ */
1552
+ async updateConfigurationValues(configId, values) {
1553
+ return (await this.http.patch(`/api/v1/admin/configuration/${configId}/values`, values)).data;
1554
+ }
1555
+ /**
1556
+ * Enables or disables configuration.
1557
+ * @param configId Configuration ID
1558
+ * @param enabled Enabled status
1559
+ */
1560
+ async updateConfigurationStatus(configId, enabled) {
1561
+ return (await this.http.patch(`/api/v1/admin/configuration/${configId}`, { enabled })).data;
1562
+ }
1563
+ /**
1564
+ * Creates new configuration record.
1565
+ * @param data Configuration data
1566
+ */
1567
+ async createConfiguration(data) {
1568
+ return (await this.http.post("/api/v1/admin/configuration", data)).data;
1569
+ }
1570
+ /**
1571
+ * Deletes configuration.
1572
+ * @param configId Configuration ID
1573
+ */
1574
+ async deleteConfiguration(configId) {
1575
+ return (await this.http.delete(`/api/v1/admin/configuration/${configId}`)).data;
1576
+ }
1577
+ /**
1578
+ * Lists all available provider definitions.
1579
+ * @param category Optional category filter
1580
+ */
1581
+ async listProviders(category) {
1582
+ return (await this.http.get("/api/v1/admin/configuration/providers", { params: { category } })).data;
1583
+ }
1584
+ /**
1585
+ * Returns JSON schema for provider configuration.
1586
+ * @param category Provider category
1587
+ * @param providerName Provider name
1588
+ */
1589
+ async getProviderSchema(category, providerName) {
1590
+ return (await this.http.get(`/api/v1/admin/configuration/schema/${category}/${providerName}`)).data;
1591
+ }
1592
+ /**
1593
+ * Tests provider connection.
1594
+ * @param category Provider category
1595
+ * @param providerName Provider name
1596
+ * @param config Configuration values to test
1597
+ */
1598
+ async testConnection(category, providerName, config) {
1599
+ return (await this.http.post("/api/v1/admin/configuration/test-connection", {
1600
+ category,
1601
+ provider_name: providerName,
1602
+ config
1603
+ }, { timeout: 15e3 })).data;
1604
+ }
1605
+ };
1606
+
1607
+ //#endregion
1608
+ //#region src/core/email-template-service.ts
1609
+ /**
1610
+ * Service for managing email templates and logs.
1611
+ */
1612
+ var EmailTemplateService = class {
1613
+ constructor(http) {
1614
+ this.http = http;
1615
+ }
1616
+ /**
1617
+ * Returns a list of email templates.
1618
+ * @param filters Optional filters for listing templates
1619
+ */
1620
+ async list(filters) {
1621
+ return (await this.http.get("/api/v1/admin/email/templates", { params: filters })).data;
1622
+ }
1623
+ /**
1624
+ * Returns email template details.
1625
+ * @param templateId Template ID
1626
+ */
1627
+ async get(templateId) {
1628
+ return (await this.http.get(`/api/v1/admin/email/templates/${templateId}`)).data;
1629
+ }
1630
+ /**
1631
+ * Updates an email template.
1632
+ * @param templateId Template ID
1633
+ * @param data Update data
1634
+ */
1635
+ async update(templateId, data) {
1636
+ return (await this.http.put(`/api/v1/admin/email/templates/${templateId}`, data)).data;
1637
+ }
1638
+ /**
1639
+ * Renders an email template with provided variables.
1640
+ * @param request Render request data
1641
+ */
1642
+ async render(request) {
1643
+ return (await this.http.post("/api/v1/admin/email/templates/render", request)).data;
1644
+ }
1645
+ /**
1646
+ * Sends a test email using the specified template.
1647
+ * @param templateId Template ID
1648
+ * @param recipientEmail Recipient email address
1649
+ * @param variables Template variables
1650
+ * @param provider Optional provider override
1651
+ */
1652
+ async sendTest(templateId, recipientEmail, variables = {}, provider) {
1653
+ return (await this.http.post(`/api/v1/admin/email/templates/${templateId}/test`, {
1654
+ recipient: recipientEmail,
1655
+ variables,
1656
+ provider
1657
+ })).data;
1658
+ }
1659
+ /**
1660
+ * Returns a paginated list of email logs.
1661
+ * @param filters Optional filters for listing logs
1662
+ */
1663
+ async listLogs(filters) {
1664
+ return (await this.http.get("/api/v1/admin/email/logs", { params: filters })).data;
1665
+ }
1666
+ /**
1667
+ * Returns single email log details.
1668
+ * @param logId Log ID
1669
+ */
1670
+ async getLog(logId) {
1671
+ return (await this.http.get(`/api/v1/admin/email/logs/${logId}`)).data;
1672
+ }
1673
+ };
1674
+
1675
+ //#endregion
1676
+ //#region src/core/file-service.ts
1677
+ /**
1678
+ * Service for working with files (upload, download, delete).
1679
+ */
1680
+ var FileService = class {
1681
+ constructor(http, getBaseUrl, getToken) {
1682
+ this.http = http;
1683
+ this.getBaseUrl = getBaseUrl;
1684
+ this.getToken = getToken;
1685
+ }
1686
+ /**
1687
+ * Upload a file to the server.
1688
+ * @param file The file or blob to upload
1689
+ * @param options Optional upload options (filename, contentType)
1690
+ */
1691
+ async upload(file, options) {
1692
+ const formData = new FormData();
1693
+ const filename = options?.filename || file.name || "file";
1694
+ formData.append("file", file, filename);
1695
+ if (options?.contentType) {}
1696
+ return (await this.http.post("/api/v1/files/upload", formData, { headers: { "Content-Type": void 0 } })).data;
1697
+ }
1698
+ /**
1699
+ * Get the download URL for a file.
1700
+ * @param path The server path to the file
1701
+ */
1702
+ getDownloadUrl(path) {
1703
+ const baseUrl = this.getBaseUrl().replace(/\/$/, "");
1704
+ const cleanPath = path.startsWith("/") ? path : `/${path}`;
1705
+ const url = new URL(`${baseUrl}/api/v1/files/download${cleanPath}`);
1706
+ const token = this.getToken();
1707
+ if (token) url.searchParams.set("token", token);
1708
+ return url.toString();
1709
+ }
1710
+ /**
1711
+ * Delete a file from the server.
1712
+ * @param path The server path to the file
1713
+ */
1714
+ async delete(path) {
1715
+ const cleanPath = path.startsWith("/") ? path : `/${path}`;
1716
+ await this.http.delete(`/api/v1/files${cleanPath}`);
1717
+ return { success: true };
1718
+ }
1719
+ };
1720
+
1721
+ //#endregion
1722
+ //#region src/core/realtime-service.ts
1723
+ var RealTimeService = class {
1724
+ state = "disconnected";
1725
+ socket = null;
1726
+ sse = null;
1727
+ listeners = {};
1728
+ retryCount = 0;
1729
+ reconnectTimer = null;
1730
+ heartbeatTimer = null;
1731
+ subscriptions = /* @__PURE__ */ new Set();
1732
+ subscriptionRequests = /* @__PURE__ */ new Map();
1733
+ pendingSubscriptions = /* @__PURE__ */ new Map();
1734
+ authUnsubscribe;
1735
+ isReconnectingForAuth = false;
1736
+ constructor(options) {
1737
+ this.options = options;
1738
+ if (this.options.authManager) this.authUnsubscribe = this.options.authManager.on("auth:login", () => {
1739
+ this.handleTokenRefresh();
1740
+ });
1741
+ }
1742
+ async connect() {
1743
+ if (this.state === "connected" || this.state === "connecting") return;
1744
+ this.setState("connecting");
1745
+ this.retryCount = 0;
1746
+ if (this.options.logger) this.options.logger.info("RealTimeService: Connecting...");
1747
+ return this.doConnect();
1748
+ }
1749
+ disconnect() {
1750
+ if (this.options.logger) this.options.logger.info("RealTimeService: Disconnecting...");
1751
+ this.clearTimers();
1752
+ this.closeConnections();
1753
+ this.setState("disconnected");
1754
+ this.subscriptions.clear();
1755
+ if (this.authUnsubscribe) {
1756
+ this.authUnsubscribe();
1757
+ this.authUnsubscribe = void 0;
1758
+ }
1759
+ }
1760
+ getState() {
1761
+ return this.state;
1762
+ }
1763
+ on(event, handler) {
1764
+ const eventName = event;
1765
+ if (!this.listeners[eventName]) this.listeners[eventName] = /* @__PURE__ */ new Set();
1766
+ this.listeners[eventName].add(handler);
1767
+ return () => this.off(event, handler);
1768
+ }
1769
+ off(event, handler) {
1770
+ this.listeners[event]?.delete(handler);
1771
+ }
1772
+ async subscribe(collection, operations = [
1773
+ "create",
1774
+ "update",
1775
+ "delete"
1776
+ ]) {
1777
+ if (this.subscriptions.size >= 100) throw new Error("Maximum 100 subscriptions per connection");
1778
+ this.subscriptions.add(collection);
1779
+ this.subscriptionRequests.set(collection, operations);
1780
+ if (this.options.logger) this.options.logger.debug(`RealTimeService: Subscribing to ${collection}`, { operations });
1781
+ if (this.state === "connected" && this.socket) return new Promise((resolve, reject) => {
1782
+ this.pendingSubscriptions.set(`subscribe:${collection}`, {
1783
+ resolve,
1784
+ reject
1785
+ });
1786
+ this.send({
1787
+ action: "subscribe",
1788
+ collection,
1789
+ operations
1790
+ });
1791
+ setTimeout(() => {
1792
+ if (this.pendingSubscriptions.has(`subscribe:${collection}`)) {
1793
+ this.pendingSubscriptions.delete(`subscribe:${collection}`);
1794
+ const error = /* @__PURE__ */ new Error(`Subscription to ${collection} timed out`);
1795
+ if (this.options.logger) this.options.logger.warn(`RealTimeService: ${error.message}`);
1796
+ reject(error);
1797
+ }
1798
+ }, 5e3);
1799
+ });
1800
+ return Promise.resolve();
1801
+ }
1802
+ async unsubscribe(collection) {
1803
+ this.subscriptions.delete(collection);
1804
+ this.subscriptionRequests.delete(collection);
1805
+ if (this.options.logger) this.options.logger.debug(`RealTimeService: Unsubscribing from ${collection}`);
1806
+ if (this.state === "connected" && this.socket) return new Promise((resolve, reject) => {
1807
+ this.pendingSubscriptions.set(`unsubscribe:${collection}`, {
1808
+ resolve,
1809
+ reject
1810
+ });
1811
+ this.send({
1812
+ action: "unsubscribe",
1813
+ collection
1814
+ });
1815
+ setTimeout(() => {
1816
+ if (this.pendingSubscriptions.has(`unsubscribe:${collection}`)) {
1817
+ this.pendingSubscriptions.delete(`unsubscribe:${collection}`);
1818
+ const error = /* @__PURE__ */ new Error(`Unsubscription from ${collection} timed out`);
1819
+ if (this.options.logger) this.options.logger.warn(`RealTimeService: ${error.message}`);
1820
+ reject(error);
1821
+ }
1822
+ }, 5e3);
1823
+ });
1824
+ return Promise.resolve();
1825
+ }
1826
+ getSubscriptions() {
1827
+ return Array.from(this.subscriptions);
1828
+ }
1829
+ async doConnect() {
1830
+ if (this.state === "error" && !this.isReconnectingForAuth) return;
1831
+ const token = this.options.getToken();
1832
+ if (!token) {
1833
+ this.setState("error");
1834
+ const error = /* @__PURE__ */ new Error("Authentication token is required for real-time connection");
1835
+ this.emit("error", error);
1836
+ this.emit("auth_error", error);
1837
+ if (this.options.logger) this.options.logger.error(`RealTimeService: ${error.message}`);
1838
+ return;
1839
+ }
1840
+ if (this.options.authManager) {
1841
+ const authState = this.options.authManager.getState();
1842
+ if (authState.expiresAt) {
1843
+ if (new Date(authState.expiresAt).getTime() <= Date.now()) {
1844
+ this.setState("error");
1845
+ const error = /* @__PURE__ */ new Error("Authentication token has expired");
1846
+ this.emit("error", error);
1847
+ this.emit("auth_error", error);
1848
+ if (this.options.logger) this.options.logger.error(`RealTimeService: ${error.message}`);
1849
+ return;
1850
+ }
1851
+ }
1852
+ }
1853
+ let webSocketAttempted = false;
1854
+ try {
1855
+ if (typeof WebSocket !== "undefined") {
1856
+ webSocketAttempted = true;
1857
+ await this.connectWebSocket(token);
1858
+ } else await this.connectSSE(token);
1859
+ } catch (err) {
1860
+ if (webSocketAttempted && !this.sse) try {
1861
+ if (this.options.logger) this.options.logger.info(`RealTimeService: WebSocket connection failed, falling back to SSE`);
1862
+ await this.connectSSE(token);
1863
+ } catch (sseErr) {
1864
+ this.handleError(sseErr);
1865
+ }
1866
+ else this.handleError(err);
1867
+ }
1868
+ }
1869
+ connectWebSocket(token) {
1870
+ return new Promise((resolve, reject) => {
1871
+ const url = new URL(this.options.baseUrl);
1872
+ url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
1873
+ url.pathname = "/api/v1/realtime/ws";
1874
+ url.searchParams.set("token", token);
1875
+ this.socket = new WebSocket(url.toString());
1876
+ let connectionResolved = false;
1877
+ this.socket.onopen = () => {
1878
+ this.setState("connected");
1879
+ this.retryCount = 0;
1880
+ this.startHeartbeat();
1881
+ this.resubscribe();
1882
+ connectionResolved = true;
1883
+ if (this.options.logger) this.options.logger.info(`RealTimeService: WebSocket connected`);
1884
+ resolve();
1885
+ };
1886
+ this.socket.onmessage = (event) => {
1887
+ try {
1888
+ const message = JSON.parse(event.data);
1889
+ this.handleMessage(message);
1890
+ } catch (e) {}
1891
+ };
1892
+ this.socket.onclose = () => {
1893
+ this.socket = null;
1894
+ if (!connectionResolved) reject(/* @__PURE__ */ new Error("WebSocket closed during connection"));
1895
+ else if (this.state !== "disconnected") {
1896
+ if (this.options.logger) this.options.logger.info(`RealTimeService: WebSocket closed, reconnecting...`);
1897
+ this.handleReconnect();
1898
+ }
1899
+ };
1900
+ this.socket.onerror = (e) => {
1901
+ if (!connectionResolved) reject(e);
1902
+ else this.emit("error", /* @__PURE__ */ new Error("WebSocket error"));
1903
+ };
1904
+ });
1905
+ }
1906
+ connectSSE(token) {
1907
+ return new Promise((resolve, reject) => {
1908
+ const url = new URL(this.options.baseUrl);
1909
+ url.pathname = "/api/v1/realtime/subscribe";
1910
+ url.searchParams.set("token", token);
1911
+ this.subscriptionRequests.forEach((operations, collection) => {
1912
+ const value = operations.length > 0 ? `${collection}:${operations.join(",")}` : collection;
1913
+ url.searchParams.append("collections", value);
1914
+ });
1915
+ this.sse = new EventSource(url.toString());
1916
+ this.sse.onopen = () => {
1917
+ this.setState("connected");
1918
+ this.retryCount = 0;
1919
+ if (this.options.logger) this.options.logger.info(`RealTimeService: SSE connected`);
1920
+ resolve();
1921
+ };
1922
+ this.sse.onmessage = (event) => {
1923
+ try {
1924
+ const message = JSON.parse(event.data);
1925
+ this.handleMessage(message);
1926
+ } catch (e) {}
1927
+ };
1928
+ this.sse.onerror = (e) => {
1929
+ if (this.state === "connecting") reject(e);
1930
+ else {
1931
+ if (this.options.logger) this.options.logger.info(`RealTimeService: SSE connection error, reconnecting...`);
1932
+ this.handleReconnect();
1933
+ }
1934
+ };
1935
+ });
1936
+ }
1937
+ handleMessage(message) {
1938
+ if (this.options.logger && message.type !== "heartbeat" && message.type !== "pong") this.options.logger.debug(`RealTimeService: Received message`, message);
1939
+ if (message.type === "heartbeat" || message.type === "pong") return;
1940
+ if (message.type === "subscribed" && message.collection) {
1941
+ const key = `subscribe:${message.collection}`;
1942
+ const pending = this.pendingSubscriptions.get(key);
1943
+ if (pending) {
1944
+ pending.resolve();
1945
+ this.pendingSubscriptions.delete(key);
1946
+ }
1947
+ return;
1948
+ }
1949
+ if (message.type === "unsubscribed" && message.collection) {
1950
+ const key = `unsubscribe:${message.collection}`;
1951
+ const pending = this.pendingSubscriptions.get(key);
1952
+ if (pending) {
1953
+ pending.resolve();
1954
+ this.pendingSubscriptions.delete(key);
1955
+ }
1956
+ return;
1957
+ }
1958
+ this.emit("message", message);
1959
+ if (message.type) {
1960
+ this.emit(message.type, message.data);
1961
+ const parts = message.type.split(".");
1962
+ if (parts.length === 2) {
1963
+ const [collection, operation] = parts;
1964
+ this.emit(`${collection}.*`, message.data);
1965
+ }
1966
+ this.emit("*", message.data);
1967
+ }
1968
+ }
1969
+ handleReconnect() {
1970
+ if (this.state === "disconnected") return;
1971
+ if (!this.isReconnectingForAuth) {
1972
+ if (!this.options.getToken()) {
1973
+ this.setState("error");
1974
+ const error = /* @__PURE__ */ new Error("Cannot reconnect: no authentication token available");
1975
+ this.emit("error", error);
1976
+ this.emit("auth_error", error);
1977
+ if (this.options.logger) this.options.logger.error(`RealTimeService: ${error.message}`);
1978
+ return;
1979
+ }
1980
+ if (this.options.authManager) {
1981
+ const authState = this.options.authManager.getState();
1982
+ if (authState.expiresAt) {
1983
+ if (new Date(authState.expiresAt).getTime() <= Date.now()) {
1984
+ this.setState("error");
1985
+ const error = /* @__PURE__ */ new Error("Cannot reconnect: authentication token has expired");
1986
+ this.emit("error", error);
1987
+ this.emit("auth_error", error);
1988
+ if (this.options.logger) this.options.logger.error(`RealTimeService: ${error.message}`);
1989
+ return;
1990
+ }
1991
+ }
1992
+ }
1993
+ }
1994
+ this.setState("connecting");
1995
+ const maxRetries = this.options.maxRetries ?? 10;
1996
+ if (this.retryCount >= maxRetries) {
1997
+ this.setState("error");
1998
+ const error = /* @__PURE__ */ new Error("Maximum reconnection attempts reached");
1999
+ this.emit("error", error);
2000
+ if (this.options.logger) this.options.logger.error(`RealTimeService: ${error.message}`);
2001
+ return;
2002
+ }
2003
+ const delay = Math.min(3e4, (this.options.reconnectionDelay ?? 1e3) * Math.pow(2, this.retryCount));
2004
+ this.retryCount++;
2005
+ if (this.options.logger) this.options.logger.info(`RealTimeService: Reconnecting in ${delay}ms (attempt ${this.retryCount}/${maxRetries})`);
2006
+ this.reconnectTimer = setTimeout(() => {
2007
+ this.doConnect();
2008
+ }, delay);
2009
+ }
2010
+ handleError(error) {
2011
+ this.emit("error", error);
2012
+ if (this.options.logger) this.options.logger.error(`RealTimeService: Error: ${error.message}`, error);
2013
+ this.handleReconnect();
2014
+ }
2015
+ setState(state) {
2016
+ if (this.state !== state && this.options.logger) this.options.logger.debug(`RealTimeService: State changed from ${this.state} to ${state}`);
2017
+ this.state = state;
2018
+ this.emit(state);
2019
+ }
2020
+ emit(event, ...args) {
2021
+ this.listeners[event]?.forEach((handler) => handler(...args));
2022
+ }
2023
+ send(message) {
2024
+ if (this.options.logger) this.options.logger.debug(`RealTimeService: Sending message`, message);
2025
+ if (this.socket && this.socket.readyState === 1) this.socket.send(JSON.stringify(message));
2026
+ }
2027
+ startHeartbeat() {
2028
+ this.heartbeatTimer = setInterval(() => {
2029
+ this.send({ action: "ping" });
2030
+ }, 3e4);
2031
+ }
2032
+ resubscribe() {
2033
+ this.subscriptionRequests.forEach((operations, collection) => {
2034
+ this.send({
2035
+ action: "subscribe",
2036
+ collection,
2037
+ operations
2038
+ });
2039
+ });
2040
+ }
2041
+ clearTimers() {
2042
+ if (this.reconnectTimer) clearTimeout(this.reconnectTimer);
2043
+ if (this.heartbeatTimer) clearInterval(this.heartbeatTimer);
2044
+ this.reconnectTimer = null;
2045
+ this.heartbeatTimer = null;
2046
+ }
2047
+ closeConnections() {
2048
+ if (this.socket) {
2049
+ this.socket.close();
2050
+ this.socket = null;
2051
+ }
2052
+ if (this.sse) {
2053
+ this.sse.close();
2054
+ this.sse = null;
2055
+ }
2056
+ }
2057
+ /**
2058
+ * Handle token refresh by reconnecting with new token
2059
+ */
2060
+ handleTokenRefresh() {
2061
+ if (this.state === "connected") {
2062
+ if (this.options.logger) this.options.logger.info(`RealTimeService: Token refreshed, reconnecting...`);
2063
+ this.isReconnectingForAuth = true;
2064
+ this.clearTimers();
2065
+ this.closeConnections();
2066
+ this.setState("connecting");
2067
+ this.retryCount = 0;
2068
+ this.doConnect().finally(() => {
2069
+ this.isReconnectingForAuth = false;
2070
+ });
2071
+ }
2072
+ }
2073
+ };
2074
+
2075
+ //#endregion
2076
+ //#region src/core/migration-service.ts
2077
+ /**
2078
+ * Service for viewing migration status and history.
2079
+ * Requires superadmin authentication.
2080
+ */
2081
+ var MigrationService = class {
2082
+ constructor(http) {
2083
+ this.http = http;
2084
+ }
2085
+ /**
2086
+ * List all Alembic migration revisions.
2087
+ * Returns all migrations with their application status.
2088
+ */
2089
+ async list() {
2090
+ return (await this.http.get("/api/v1/migrations")).data;
2091
+ }
2092
+ /**
2093
+ * Get the current database revision.
2094
+ * Returns the currently applied migration.
2095
+ */
2096
+ async getCurrent() {
2097
+ try {
2098
+ return (await this.http.get("/api/v1/migrations/current")).data;
2099
+ } catch (error) {
2100
+ if (error?.status === 404) return null;
2101
+ throw error;
2102
+ }
2103
+ }
2104
+ /**
2105
+ * Get full migration history.
2106
+ * Returns all applied migrations in chronological order.
2107
+ */
2108
+ async getHistory() {
2109
+ return (await this.http.get("/api/v1/migrations/history")).data;
2110
+ }
2111
+ };
2112
+
2113
+ //#endregion
2114
+ //#region src/core/storage.ts
2115
+ var MemoryStorage = class {
2116
+ storage = /* @__PURE__ */ new Map();
2117
+ getItem(key) {
2118
+ return this.storage.get(key) || null;
2119
+ }
2120
+ setItem(key, value) {
2121
+ this.storage.set(key, value);
2122
+ }
2123
+ removeItem(key) {
2124
+ this.storage.delete(key);
2125
+ }
2126
+ };
2127
+ var LocalStorageBackend = class {
2128
+ getItem(key) {
2129
+ if (typeof localStorage === "undefined") return null;
2130
+ return localStorage.getItem(key);
2131
+ }
2132
+ setItem(key, value) {
2133
+ if (typeof localStorage === "undefined") return;
2134
+ localStorage.setItem(key, value);
2135
+ }
2136
+ removeItem(key) {
2137
+ if (typeof localStorage === "undefined") return;
2138
+ localStorage.removeItem(key);
2139
+ }
2140
+ };
2141
+ var SessionStorageBackend = class {
2142
+ getItem(key) {
2143
+ if (typeof sessionStorage === "undefined") return null;
2144
+ return sessionStorage.getItem(key);
2145
+ }
2146
+ setItem(key, value) {
2147
+ if (typeof sessionStorage === "undefined") return;
2148
+ sessionStorage.setItem(key, value);
2149
+ }
2150
+ removeItem(key) {
2151
+ if (typeof sessionStorage === "undefined") return;
2152
+ sessionStorage.removeItem(key);
2153
+ }
2154
+ };
2155
+ function createStorageBackend(type) {
2156
+ switch (type) {
2157
+ case "localStorage": return new LocalStorageBackend();
2158
+ case "sessionStorage": return new SessionStorageBackend();
2159
+ case "memory": return new MemoryStorage();
2160
+ case "asyncStorage": return new MemoryStorage();
2161
+ default: return new LocalStorageBackend();
2162
+ }
2163
+ }
2164
+
2165
+ //#endregion
2166
+ //#region src/core/client.ts
2167
+ /**
2168
+ * Main SDK client for interacting with SnackBase API.
2169
+ */
2170
+ var SnackBaseClient = class {
2171
+ config;
2172
+ http;
2173
+ logger;
2174
+ authManager;
2175
+ authService;
2176
+ accountService;
2177
+ userService;
2178
+ collectionService;
2179
+ recordService;
2180
+ groupsService;
2181
+ invitationService;
2182
+ apiKeyService;
2183
+ auditLogService;
2184
+ roleService;
2185
+ collectionRuleService;
2186
+ macroService;
2187
+ dashboardService;
2188
+ adminService;
2189
+ emailTemplateService;
2190
+ fileService;
2191
+ realtimeService;
2192
+ migrationService;
2193
+ /**
2194
+ * Initialize a new SnackBaseClient instance.
2195
+ * @param config Configuration options
2196
+ */
2197
+ constructor(config) {
2198
+ this.validateConfig(config);
2199
+ this.config = {
2200
+ ...DEFAULT_CONFIG,
2201
+ storageBackend: config.storageBackend || getAutoDetectedStorage(),
2202
+ ...config
2203
+ };
2204
+ let logLevel = LogLevel.NONE;
2205
+ if (this.config.enableLogging) switch (this.config.logLevel) {
2206
+ case "debug":
2207
+ logLevel = LogLevel.DEBUG;
2208
+ break;
2209
+ case "info":
2210
+ logLevel = LogLevel.INFO;
2211
+ break;
2212
+ case "warn":
2213
+ logLevel = LogLevel.WARN;
2214
+ break;
2215
+ case "error":
2216
+ logLevel = LogLevel.ERROR;
2217
+ break;
2218
+ default: logLevel = LogLevel.ERROR;
2219
+ }
2220
+ this.logger = new Logger(logLevel);
2221
+ if (typeof window !== "undefined") window.snackbase_debug = {
2222
+ logger: this.logger,
2223
+ client: this
2224
+ };
2225
+ this.http = new HttpClient({
2226
+ baseUrl: this.config.baseUrl,
2227
+ timeout: this.config.timeout,
2228
+ maxRetries: this.config.maxRetries,
2229
+ retryDelay: this.config.retryDelay,
2230
+ logger: this.logger
2231
+ });
2232
+ this.authManager = new AuthManager({ storage: createStorageBackend(this.config.storageBackend) });
2233
+ this.authService = new AuthService(this.http, this.authManager, this.config.apiKey, this.config.defaultAccount);
2234
+ this.accountService = new AccountService(this.http);
2235
+ this.userService = new UserService(this.http);
2236
+ this.collectionService = new CollectionService(this.http);
2237
+ this.recordService = new RecordService(this.http);
2238
+ this.groupsService = new GroupsService(this.http);
2239
+ this.invitationService = new InvitationService(this.http);
2240
+ this.apiKeyService = new ApiKeyService(this.http);
2241
+ this.auditLogService = new AuditLogService(this.http);
2242
+ this.roleService = new RoleService(this.http);
2243
+ this.collectionRuleService = new CollectionRuleService(this.http);
2244
+ this.macroService = new MacroService(this.http);
2245
+ this.dashboardService = new DashboardService(this.http);
2246
+ this.adminService = new AdminService(this.http);
2247
+ this.emailTemplateService = new EmailTemplateService(this.http);
2248
+ this.fileService = new FileService(this.http, () => this.config.baseUrl, () => this.authManager.token);
2249
+ this.realtimeService = new RealTimeService({
2250
+ baseUrl: this.config.baseUrl,
2251
+ getToken: () => this.authManager.token,
2252
+ authManager: this.authManager,
2253
+ maxRetries: this.config.maxRealTimeRetries,
2254
+ reconnectionDelay: this.config.realTimeReconnectionDelay,
2255
+ logger: this.logger
2256
+ });
2257
+ this.migrationService = new MigrationService(this.http);
2258
+ this.setupInterceptors();
2259
+ this.authManager.initialize();
2260
+ }
2261
+ /**
2262
+ * Returns the current client configuration.
2263
+ */
2264
+ getConfig() {
2265
+ return { ...this.config };
2266
+ }
2267
+ /**
2268
+ * Internal helper to access the HTTP client.
2269
+ * @internal
2270
+ */
2271
+ get httpClient() {
2272
+ return this.http;
2273
+ }
2274
+ /**
2275
+ * Sets up the default interceptors for the HTTP client.
2276
+ */
2277
+ setupInterceptors() {
2278
+ this.http.addRequestInterceptor(contentTypeInterceptor);
2279
+ this.http.addRequestInterceptor(createAuthInterceptor(() => this.authManager.token || void 0, this.config.apiKey));
2280
+ this.http.addResponseInterceptor(errorNormalizationInterceptor);
2281
+ this.http.addErrorInterceptor(errorInterceptor);
2282
+ }
2283
+ /**
2284
+ * Returns the current authenticated user.
2285
+ */
2286
+ get user() {
2287
+ return this.authManager.user;
2288
+ }
2289
+ /**
2290
+ * Returns the current account.
2291
+ */
2292
+ get account() {
2293
+ return this.authManager.account;
2294
+ }
2295
+ /**
2296
+ * Returns whether the client is currently authenticated.
2297
+ */
2298
+ get isAuthenticated() {
2299
+ return this.authManager.isAuthenticated;
2300
+ }
2301
+ /**
2302
+ * Access to authentication methods.
2303
+ */
2304
+ get auth() {
2305
+ return this.authService;
2306
+ }
2307
+ /**
2308
+ * Access to account management methods.
2309
+ */
2310
+ get accounts() {
2311
+ return this.accountService;
2312
+ }
2313
+ /**
2314
+ * Access to user management methods.
2315
+ */
2316
+ get users() {
2317
+ return this.userService;
2318
+ }
2319
+ /**
2320
+ * Access to collection management methods.
2321
+ */
2322
+ get collections() {
2323
+ return this.collectionService;
2324
+ }
2325
+ /**
2326
+ * Access to record management methods (CRUD on dynamic collections).
2327
+ */
2328
+ get records() {
2329
+ return this.recordService;
2330
+ }
2331
+ /**
2332
+ * Access to group management methods.
2333
+ */
2334
+ get groups() {
2335
+ return this.groupsService;
2336
+ }
2337
+ /**
2338
+ * Access to invitation management methods.
2339
+ */
2340
+ get invitations() {
2341
+ return this.invitationService;
2342
+ }
2343
+ /**
2344
+ * Access to API key management methods.
2345
+ */
2346
+ get apiKeys() {
2347
+ return this.apiKeyService;
2348
+ }
2349
+ /**
2350
+ * Access to audit log management methods.
2351
+ */
2352
+ get auditLogs() {
2353
+ return this.auditLogService;
2354
+ }
2355
+ /**
2356
+ * Access to role management methods.
2357
+ */
2358
+ get roles() {
2359
+ return this.roleService;
2360
+ }
2361
+ /**
2362
+ * Access to collection rule management methods.
2363
+ */
2364
+ get collectionRules() {
2365
+ return this.collectionRuleService;
2366
+ }
2367
+ /**
2368
+ * Access to macro management methods.
2369
+ */
2370
+ get macros() {
2371
+ return this.macroService;
2372
+ }
2373
+ /**
2374
+ * Access to dashboard statistics and monitoring.
2375
+ */
2376
+ get dashboard() {
2377
+ return this.dashboardService;
2378
+ }
2379
+ /**
2380
+ * Access to system administration and configuration methods.
2381
+ */
2382
+ get admin() {
2383
+ return this.adminService;
2384
+ }
2385
+ /**
2386
+ * Access to email template management methods.
2387
+ */
2388
+ get emailTemplates() {
2389
+ return this.emailTemplateService;
2390
+ }
2391
+ /**
2392
+ * Access to file management methods.
2393
+ */
2394
+ get files() {
2395
+ return this.fileService;
2396
+ }
2397
+ /**
2398
+ * Access to real-time features and subscriptions.
2399
+ */
2400
+ get realtime() {
2401
+ return this.realtimeService;
2402
+ }
2403
+ /**
2404
+ * Access to migration status and history.
2405
+ */
2406
+ get migrations() {
2407
+ return this.migrationService;
2408
+ }
2409
+ /**
2410
+ * Subscribe to authentication events.
2411
+ * @param event Event name
2412
+ * @param listener Callback function
2413
+ */
2414
+ on(event, listener) {
2415
+ return this.authManager.on(event, listener);
2416
+ }
2417
+ /**
2418
+ * Authenticate a user with email and password.
2419
+ */
2420
+ async login(credentials) {
2421
+ return this.authService.login(credentials);
2422
+ }
2423
+ /**
2424
+ * Log out the current user.
2425
+ */
2426
+ async logout() {
2427
+ return this.authService.logout();
2428
+ }
2429
+ /**
2430
+ * Register a new user and account.
2431
+ */
2432
+ async register(data) {
2433
+ return this.authService.register(data);
2434
+ }
2435
+ /**
2436
+ * Refresh the access token using the refresh token.
2437
+ */
2438
+ async refreshToken() {
2439
+ return this.authService.refreshToken();
2440
+ }
2441
+ /**
2442
+ * Get the current authenticated user profile.
2443
+ */
2444
+ async getCurrentUser() {
2445
+ return this.authService.getCurrentUser();
2446
+ }
2447
+ /**
2448
+ * Initiate password reset flow.
2449
+ */
2450
+ async forgotPassword(data) {
2451
+ return this.authService.forgotPassword(data);
2452
+ }
2453
+ /**
2454
+ * Reset password using a token.
2455
+ */
2456
+ async resetPassword(data) {
2457
+ return this.authService.resetPassword(data);
2458
+ }
2459
+ /**
2460
+ * Verify email using a token.
2461
+ */
2462
+ async verifyEmail(token) {
2463
+ return this.authService.verifyEmail(token);
2464
+ }
2465
+ /**
2466
+ * Resend the verification email to the current user.
2467
+ */
2468
+ async resendVerificationEmail() {
2469
+ return this.authService.resendVerificationEmail();
2470
+ }
2471
+ /**
2472
+ * Generate SAML SSO authorization URL.
2473
+ */
2474
+ async getSAMLUrl(provider, account, relayState) {
2475
+ return this.authService.getSAMLUrl(provider, account, relayState);
2476
+ }
2477
+ /**
2478
+ * Handle SAML callback.
2479
+ */
2480
+ async handleSAMLCallback(params) {
2481
+ return this.authService.handleSAMLCallback(params);
2482
+ }
2483
+ /**
2484
+ * Get SAML metadata.
2485
+ */
2486
+ async getSAMLMetadata(provider, account) {
2487
+ return this.authService.getSAMLMetadata(provider, account);
2488
+ }
2489
+ /**
2490
+ * Internal access to AuthManager.
2491
+ * @internal
2492
+ */
2493
+ get internalAuthManager() {
2494
+ return this.authManager;
2495
+ }
2496
+ /**
2497
+ * Validates the configuration object.
2498
+ * Throws descriptive errors for invalid options.
2499
+ */
2500
+ validateConfig(config) {
2501
+ if (!config.baseUrl) throw new Error("SnackBaseClient: baseUrl is required");
2502
+ try {
2503
+ new URL(config.baseUrl);
2504
+ } catch (e) {
2505
+ throw new Error("SnackBaseClient: baseUrl must be a valid URL");
2506
+ }
2507
+ if (config.timeout !== void 0 && (typeof config.timeout !== "number" || config.timeout < 0)) throw new Error("SnackBaseClient: timeout must be a non-negative number");
2508
+ if (config.maxRetries !== void 0 && (typeof config.maxRetries !== "number" || config.maxRetries < 0)) throw new Error("SnackBaseClient: maxRetries must be a non-negative number");
2509
+ if (config.retryDelay !== void 0 && (typeof config.retryDelay !== "number" || config.retryDelay < 0)) throw new Error("SnackBaseClient: retryDelay must be a non-negative number");
2510
+ if (config.refreshBeforeExpiry !== void 0 && (typeof config.refreshBeforeExpiry !== "number" || config.refreshBeforeExpiry < 0)) throw new Error("SnackBaseClient: refreshBeforeExpiry must be a non-negative number");
2511
+ if (config.defaultAccount !== void 0 && typeof config.defaultAccount !== "string") throw new Error("SnackBaseClient: defaultAccount must be a string");
2512
+ }
2513
+ };
2514
+
2515
+ //#endregion
2516
+ export { DEFAULT_CONFIG, QueryBuilder, SnackBaseClient, getAutoDetectedStorage };