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