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