@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 +2835 -0
- package/dist/{index-Dr6K4PMl.d.mts → index.d.cts} +367 -38
- package/dist/index.d.mts +2447 -2
- package/dist/index.mjs +357 -56
- package/package.json +33 -38
- package/CHANGELOG.md +0 -61
- package/README.md +0 -287
- package/dist/react/index.d.mts +0 -63
- package/dist/react/index.mjs +0 -271
package/dist/index.mjs
CHANGED
|
@@ -36,8 +36,11 @@ var SnackBaseError = class SnackBaseError extends Error {
|
|
|
36
36
|
details;
|
|
37
37
|
field;
|
|
38
38
|
retryable;
|
|
39
|
-
constructor(message, code, status, details, retryable = false, field) {
|
|
39
|
+
constructor(message, code, status, details, retryable = false, field, redirectUrl, authProvider, providerName) {
|
|
40
40
|
super(message);
|
|
41
|
+
this.redirectUrl = redirectUrl;
|
|
42
|
+
this.authProvider = authProvider;
|
|
43
|
+
this.providerName = providerName;
|
|
41
44
|
this.name = this.constructor.name;
|
|
42
45
|
this.code = code;
|
|
43
46
|
this.status = status;
|
|
@@ -66,6 +69,30 @@ var AuthorizationError = class AuthorizationError extends SnackBaseError {
|
|
|
66
69
|
}
|
|
67
70
|
};
|
|
68
71
|
/**
|
|
72
|
+
* Thrown when an API key is restricted to superadmin users (403).
|
|
73
|
+
*/
|
|
74
|
+
var ApiKeyRestrictedError = class ApiKeyRestrictedError extends SnackBaseError {
|
|
75
|
+
constructor(message, details) {
|
|
76
|
+
super(message || "API keys are restricted to superadmin users. Please use JWT authentication.", "API_KEY_RESTRICTED", 403, details || {
|
|
77
|
+
suggestion: "Remove apiKey config and use login() instead",
|
|
78
|
+
documentation: "https://docs.snackbase.com/authentication/api-keys"
|
|
79
|
+
}, false);
|
|
80
|
+
Object.setPrototypeOf(this, ApiKeyRestrictedError.prototype);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* Thrown when email verification is required (401).
|
|
85
|
+
*/
|
|
86
|
+
var EmailVerificationRequiredError = class EmailVerificationRequiredError extends SnackBaseError {
|
|
87
|
+
constructor(message, details) {
|
|
88
|
+
super(message || "Please check your email inbox to verify your account before logging in.", "EMAIL_VERIFICATION_REQUIRED", 401, details || {
|
|
89
|
+
suggestion: "Click the verification link sent to your email address",
|
|
90
|
+
canResend: true
|
|
91
|
+
}, false);
|
|
92
|
+
Object.setPrototypeOf(this, EmailVerificationRequiredError.prototype);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
69
96
|
* Thrown when a resource is not found (404).
|
|
70
97
|
*/
|
|
71
98
|
var NotFoundError = class NotFoundError extends SnackBaseError {
|
|
@@ -319,9 +346,9 @@ var Logger = class {
|
|
|
319
346
|
constructor(level = LogLevel.NONE) {
|
|
320
347
|
this.level = level;
|
|
321
348
|
this.handlers.push((entry) => {
|
|
322
|
-
const { level
|
|
349
|
+
const { level, message, data } = entry;
|
|
323
350
|
const args = data ? [message, data] : [message];
|
|
324
|
-
switch (level
|
|
351
|
+
switch (level) {
|
|
325
352
|
case LogLevel.ERROR:
|
|
326
353
|
console.error("[SnackBase]", ...args);
|
|
327
354
|
break;
|
|
@@ -389,7 +416,9 @@ const contentTypeInterceptor = (request) => {
|
|
|
389
416
|
const createAuthInterceptor = (getToken, apiKey) => {
|
|
390
417
|
return (request) => {
|
|
391
418
|
const isUserSpecific = request.url.includes("/auth/oauth/") || request.url.includes("/auth/saml/");
|
|
392
|
-
if (apiKey && !isUserSpecific)
|
|
419
|
+
if (apiKey && !isUserSpecific) {
|
|
420
|
+
if (!(request.url.includes("/auth/login") || request.url.includes("/auth/register"))) request.headers["X-API-Key"] = apiKey;
|
|
421
|
+
}
|
|
393
422
|
const token = getToken();
|
|
394
423
|
if (token) request.headers["Authorization"] = `Bearer ${token}`;
|
|
395
424
|
return request;
|
|
@@ -433,6 +462,51 @@ function createErrorFromResponse(response) {
|
|
|
433
462
|
return new SnackBaseError(message, "UNKNOWN_ERROR", status, data, false);
|
|
434
463
|
}
|
|
435
464
|
}
|
|
465
|
+
/**
|
|
466
|
+
* Enhanced error interceptor to handle 403 API key errors and preserve redirects.
|
|
467
|
+
*/
|
|
468
|
+
const createAuthErrorInterceptor = (onAuthError) => {
|
|
469
|
+
return (error) => {
|
|
470
|
+
if (error.status === 403 && error.details) {
|
|
471
|
+
const detail = error.details.detail || error.details.message || "";
|
|
472
|
+
if (detail.includes("superadmin") || detail.includes("restricted")) {
|
|
473
|
+
const restrictedError = new ApiKeyRestrictedError(detail, error.details);
|
|
474
|
+
if (onAuthError) onAuthError(restrictedError);
|
|
475
|
+
throw restrictedError;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
if (error.status === 403 && error.details?.redirect_url) {
|
|
479
|
+
error.redirectUrl = error.details.redirect_url;
|
|
480
|
+
error.authProvider = error.details.auth_provider;
|
|
481
|
+
error.providerName = error.details.provider_name;
|
|
482
|
+
}
|
|
483
|
+
if (error.status === 401 && error.details) {
|
|
484
|
+
const detail = error.details.detail || error.details.message || "";
|
|
485
|
+
if (detail.includes("verify") || detail.includes("email")) {
|
|
486
|
+
const verificationError = new EmailVerificationRequiredError(detail, error.details);
|
|
487
|
+
if (onAuthError) onAuthError(verificationError);
|
|
488
|
+
throw verificationError;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
if (error.status === 401 || error.status === 403) {
|
|
492
|
+
if (onAuthError) onAuthError(error);
|
|
493
|
+
}
|
|
494
|
+
throw error;
|
|
495
|
+
};
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
//#endregion
|
|
499
|
+
//#region src/types/auth.ts
|
|
500
|
+
/**
|
|
501
|
+
* Token type enum matching backend TokenType
|
|
502
|
+
*/
|
|
503
|
+
let TokenType = /* @__PURE__ */ function(TokenType) {
|
|
504
|
+
TokenType["JWT"] = "jwt";
|
|
505
|
+
TokenType["API_KEY"] = "api_key";
|
|
506
|
+
TokenType["PERSONAL_TOKEN"] = "personal_token";
|
|
507
|
+
TokenType["OAUTH"] = "oauth";
|
|
508
|
+
return TokenType;
|
|
509
|
+
}({});
|
|
436
510
|
|
|
437
511
|
//#endregion
|
|
438
512
|
//#region src/core/events.ts
|
|
@@ -453,6 +527,74 @@ var AuthEventEmitter = class {
|
|
|
453
527
|
}
|
|
454
528
|
};
|
|
455
529
|
|
|
530
|
+
//#endregion
|
|
531
|
+
//#region src/core/constants.ts
|
|
532
|
+
/**
|
|
533
|
+
* System account ID for superadmin detection
|
|
534
|
+
* Nil UUID format used by backend
|
|
535
|
+
* @see https://github.com/snackbase/snackbase/blob/main/src/snackbase/infrastructure/api/middleware/authorization.py
|
|
536
|
+
*/
|
|
537
|
+
const SYSTEM_ACCOUNT_ID = "00000000-0000-0000-0000-000000000000";
|
|
538
|
+
/**
|
|
539
|
+
* Token type prefixes matching backend TokenCodec
|
|
540
|
+
*/
|
|
541
|
+
const TOKEN_PREFIXES = {
|
|
542
|
+
JWT: "sb_jwt",
|
|
543
|
+
API_KEY: "sb_ak",
|
|
544
|
+
PERSONAL_TOKEN: "sb_pt",
|
|
545
|
+
OAUTH: "sb_ot"
|
|
546
|
+
};
|
|
547
|
+
/**
|
|
548
|
+
* Valid token prefixes for validation
|
|
549
|
+
*/
|
|
550
|
+
const VALID_TOKEN_PREFIXES = new Set(Object.values(TOKEN_PREFIXES));
|
|
551
|
+
/**
|
|
552
|
+
* API key endpoint path
|
|
553
|
+
*/
|
|
554
|
+
const API_KEY_BASE_PATH = "/api/v1/admin/api-keys";
|
|
555
|
+
|
|
556
|
+
//#endregion
|
|
557
|
+
//#region src/utils/token-utils.ts
|
|
558
|
+
/**
|
|
559
|
+
* Detect token type from token string
|
|
560
|
+
* @param token - The token to analyze
|
|
561
|
+
* @returns Detected token type or undefined
|
|
562
|
+
*/
|
|
563
|
+
function detectTokenType(token) {
|
|
564
|
+
if (!token) return void 0;
|
|
565
|
+
switch (token.split(".")[0]) {
|
|
566
|
+
case TOKEN_PREFIXES.API_KEY: return TokenType.API_KEY;
|
|
567
|
+
case TOKEN_PREFIXES.PERSONAL_TOKEN: return TokenType.PERSONAL_TOKEN;
|
|
568
|
+
case TOKEN_PREFIXES.OAUTH: return TokenType.OAUTH;
|
|
569
|
+
case TOKEN_PREFIXES.JWT: return TokenType.JWT;
|
|
570
|
+
default: return token.startsWith("ey") ? TokenType.JWT : void 0;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* Check if user is a superadmin
|
|
575
|
+
* @param user - User object to check
|
|
576
|
+
* @returns true if user is superadmin
|
|
577
|
+
*/
|
|
578
|
+
function isSuperadmin(user) {
|
|
579
|
+
if (!user) return false;
|
|
580
|
+
return user.account_id === SYSTEM_ACCOUNT_ID;
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Format masked API key for display
|
|
584
|
+
* @param key - The full or masked key
|
|
585
|
+
* @returns Formatted masked key
|
|
586
|
+
*/
|
|
587
|
+
function formatMaskedKey(key) {
|
|
588
|
+
if (!key) return "";
|
|
589
|
+
if (key.includes("...")) return key;
|
|
590
|
+
const parts = key.split(".");
|
|
591
|
+
if (parts.length === 3) {
|
|
592
|
+
const [, payload, signature] = parts;
|
|
593
|
+
return `${parts[0]}.${payload.slice(0, 4)}...${signature.slice(-4)}`;
|
|
594
|
+
}
|
|
595
|
+
return key;
|
|
596
|
+
}
|
|
597
|
+
|
|
456
598
|
//#endregion
|
|
457
599
|
//#region src/core/auth.ts
|
|
458
600
|
const DEFAULT_AUTH_STATE = {
|
|
@@ -461,7 +603,8 @@ const DEFAULT_AUTH_STATE = {
|
|
|
461
603
|
token: null,
|
|
462
604
|
refreshToken: null,
|
|
463
605
|
isAuthenticated: false,
|
|
464
|
-
expiresAt: null
|
|
606
|
+
expiresAt: null,
|
|
607
|
+
tokenType: TokenType.JWT
|
|
465
608
|
};
|
|
466
609
|
var AuthManager = class {
|
|
467
610
|
state = { ...DEFAULT_AUTH_STATE };
|
|
@@ -495,6 +638,76 @@ var AuthManager = class {
|
|
|
495
638
|
get isAuthenticated() {
|
|
496
639
|
return this.state.isAuthenticated;
|
|
497
640
|
}
|
|
641
|
+
get tokenType() {
|
|
642
|
+
return this.state.tokenType;
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* Update auth state (enhanced to extract token_type)
|
|
646
|
+
*/
|
|
647
|
+
async updateState(data) {
|
|
648
|
+
const user = data.user || (data.user_id ? {
|
|
649
|
+
id: data.user_id,
|
|
650
|
+
email: data.email || "",
|
|
651
|
+
role: data.role || "user",
|
|
652
|
+
account_id: data.account_id || "",
|
|
653
|
+
groups: [],
|
|
654
|
+
is_active: true,
|
|
655
|
+
created_at: "",
|
|
656
|
+
last_login: null,
|
|
657
|
+
token_type: TokenType.JWT
|
|
658
|
+
} : null);
|
|
659
|
+
const token = data.token || null;
|
|
660
|
+
const refreshToken = data.refresh_token || data.refreshToken || null;
|
|
661
|
+
let tokenType = TokenType.JWT;
|
|
662
|
+
if (token) tokenType = detectTokenType(token) || TokenType.JWT;
|
|
663
|
+
else if (user?.token_type) tokenType = user.token_type;
|
|
664
|
+
this.state = {
|
|
665
|
+
...this.state,
|
|
666
|
+
user,
|
|
667
|
+
account: data.account || (data.account_id ? {
|
|
668
|
+
id: data.account_id,
|
|
669
|
+
slug: "",
|
|
670
|
+
name: "",
|
|
671
|
+
created_at: ""
|
|
672
|
+
} : this.state.account),
|
|
673
|
+
token: token || this.state.token,
|
|
674
|
+
refreshToken: refreshToken || this.state.refreshToken,
|
|
675
|
+
isAuthenticated: !!((token || this.state.token) && user),
|
|
676
|
+
expiresAt: this.calculateExpiry(data) || this.state.expiresAt,
|
|
677
|
+
tokenType
|
|
678
|
+
};
|
|
679
|
+
await this.persist();
|
|
680
|
+
if (token || user) this.events.emit("auth:login", this.state);
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* Check if current user is superadmin
|
|
684
|
+
*/
|
|
685
|
+
isSuperadmin() {
|
|
686
|
+
return isSuperadmin(this.state.user);
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* Check if current session uses API key authentication
|
|
690
|
+
*/
|
|
691
|
+
isApiKeySession() {
|
|
692
|
+
return this.state.tokenType === TokenType.API_KEY;
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Check if current session uses personal token authentication
|
|
696
|
+
*/
|
|
697
|
+
isPersonalTokenSession() {
|
|
698
|
+
return this.state.tokenType === TokenType.PERSONAL_TOKEN;
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* Check if current session uses OAuth authentication
|
|
702
|
+
*/
|
|
703
|
+
isOAuthSession() {
|
|
704
|
+
return this.state.tokenType === TokenType.OAUTH;
|
|
705
|
+
}
|
|
706
|
+
calculateExpiry(data) {
|
|
707
|
+
if (data.expiresAt) return data.expiresAt;
|
|
708
|
+
if (data.expires_in) return new Date(Date.now() + data.expires_in * 1e3).toISOString();
|
|
709
|
+
return null;
|
|
710
|
+
}
|
|
498
711
|
async setState(newState) {
|
|
499
712
|
this.state = {
|
|
500
713
|
...this.state,
|
|
@@ -568,13 +781,8 @@ var AuthService = class {
|
|
|
568
781
|
const data = { ...credentials };
|
|
569
782
|
if (!data.account && this.defaultAccount) data.account = this.defaultAccount;
|
|
570
783
|
const authData = (await this.http.post("/api/v1/auth/login", data)).data;
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
account: authData.account,
|
|
574
|
-
token: authData.token,
|
|
575
|
-
refreshToken: authData.refreshToken,
|
|
576
|
-
expiresAt: authData.expiresAt
|
|
577
|
-
});
|
|
784
|
+
console.log("Login Response:", JSON.stringify(authData, null, 2));
|
|
785
|
+
await this.auth.updateState(authData);
|
|
578
786
|
return authData;
|
|
579
787
|
}
|
|
580
788
|
/**
|
|
@@ -582,7 +790,7 @@ var AuthService = class {
|
|
|
582
790
|
*/
|
|
583
791
|
async register(data) {
|
|
584
792
|
const payload = { ...data };
|
|
585
|
-
if (!payload.
|
|
793
|
+
if (!payload.account_name && this.defaultAccount) payload.account_name = this.defaultAccount;
|
|
586
794
|
return (await this.http.post("/api/v1/auth/register", payload)).data;
|
|
587
795
|
}
|
|
588
796
|
/**
|
|
@@ -591,14 +799,8 @@ var AuthService = class {
|
|
|
591
799
|
async refreshToken() {
|
|
592
800
|
const refreshToken = this.auth.refreshToken;
|
|
593
801
|
if (!refreshToken) throw new Error("No refresh token available");
|
|
594
|
-
const authData = (await this.http.post("/api/v1/auth/refresh", { refreshToken })).data;
|
|
595
|
-
await this.auth.
|
|
596
|
-
user: authData.user,
|
|
597
|
-
account: authData.account,
|
|
598
|
-
token: authData.token,
|
|
599
|
-
refreshToken: authData.refreshToken,
|
|
600
|
-
expiresAt: authData.expiresAt
|
|
601
|
-
});
|
|
802
|
+
const authData = (await this.http.post("/api/v1/auth/refresh", { refresh_token: refreshToken })).data;
|
|
803
|
+
await this.auth.updateState(authData);
|
|
602
804
|
return authData;
|
|
603
805
|
}
|
|
604
806
|
/**
|
|
@@ -617,10 +819,8 @@ var AuthService = class {
|
|
|
617
819
|
*/
|
|
618
820
|
async getCurrentUser() {
|
|
619
821
|
const authData = (await this.http.get("/api/v1/auth/me")).data;
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
account: authData.account
|
|
623
|
-
});
|
|
822
|
+
console.log("GetMe Response:", JSON.stringify(authData, null, 2));
|
|
823
|
+
await this.auth.updateState(authData);
|
|
624
824
|
return authData;
|
|
625
825
|
}
|
|
626
826
|
/**
|
|
@@ -694,13 +894,7 @@ var AuthService = class {
|
|
|
694
894
|
redirectUri,
|
|
695
895
|
state
|
|
696
896
|
})).data;
|
|
697
|
-
await this.auth.
|
|
698
|
-
user: authData.user,
|
|
699
|
-
account: authData.account,
|
|
700
|
-
token: authData.token,
|
|
701
|
-
refreshToken: authData.refreshToken,
|
|
702
|
-
expiresAt: authData.expiresAt
|
|
703
|
-
});
|
|
897
|
+
await this.auth.updateState(authData);
|
|
704
898
|
return authData;
|
|
705
899
|
}
|
|
706
900
|
/**
|
|
@@ -719,13 +913,7 @@ var AuthService = class {
|
|
|
719
913
|
*/
|
|
720
914
|
async handleSAMLCallback(params) {
|
|
721
915
|
const authData = (await this.http.post("/api/v1/auth/saml/acs", params)).data;
|
|
722
|
-
await this.auth.
|
|
723
|
-
user: authData.user,
|
|
724
|
-
account: authData.account,
|
|
725
|
-
token: authData.token,
|
|
726
|
-
refreshToken: authData.refreshToken,
|
|
727
|
-
expiresAt: authData.expiresAt
|
|
728
|
-
});
|
|
916
|
+
await this.auth.updateState(authData);
|
|
729
917
|
return authData;
|
|
730
918
|
}
|
|
731
919
|
/**
|
|
@@ -907,6 +1095,62 @@ var CollectionService = class {
|
|
|
907
1095
|
await this.http.delete(`/api/v1/collections/${collectionId}`);
|
|
908
1096
|
return { success: true };
|
|
909
1097
|
}
|
|
1098
|
+
/**
|
|
1099
|
+
* Export collections to JSON format.
|
|
1100
|
+
* Returns collection schemas and rules for backup or migration.
|
|
1101
|
+
*
|
|
1102
|
+
* @param params Optional filter by collection IDs
|
|
1103
|
+
* @returns Complete export data structure with collections, schemas, and rules
|
|
1104
|
+
* @throws {AuthorizationError} If user is not a superadmin
|
|
1105
|
+
*
|
|
1106
|
+
* @example
|
|
1107
|
+
* // Export all collections
|
|
1108
|
+
* const exportData = await client.collections.export();
|
|
1109
|
+
*
|
|
1110
|
+
* @example
|
|
1111
|
+
* // Export specific collections
|
|
1112
|
+
* const exportData = await client.collections.export({
|
|
1113
|
+
* collection_ids: ['col-123', 'col-456']
|
|
1114
|
+
* });
|
|
1115
|
+
*/
|
|
1116
|
+
async export(params) {
|
|
1117
|
+
const queryParams = {};
|
|
1118
|
+
if (params?.collection_ids && params.collection_ids.length > 0) queryParams.collection_ids = params.collection_ids.join(",");
|
|
1119
|
+
return (await this.http.get("/api/v1/collections/export", { params: queryParams })).data;
|
|
1120
|
+
}
|
|
1121
|
+
/**
|
|
1122
|
+
* Import collections from JSON export.
|
|
1123
|
+
*
|
|
1124
|
+
* @param request Import request with data and conflict strategy
|
|
1125
|
+
* @returns Import result with per-collection status and migration IDs
|
|
1126
|
+
* @throws {ValidationError} If import data is invalid
|
|
1127
|
+
* @throws {ConflictError} If collection exists and strategy is 'error'
|
|
1128
|
+
* @throws {AuthorizationError} If user is not a superadmin
|
|
1129
|
+
*
|
|
1130
|
+
* @example
|
|
1131
|
+
* // Import with error strategy (fail on conflicts)
|
|
1132
|
+
* const result = await client.collections.import({
|
|
1133
|
+
* data: exportData,
|
|
1134
|
+
* strategy: 'error'
|
|
1135
|
+
* });
|
|
1136
|
+
*
|
|
1137
|
+
* @example
|
|
1138
|
+
* // Import with skip strategy (skip existing collections)
|
|
1139
|
+
* const result = await client.collections.import({
|
|
1140
|
+
* data: exportData,
|
|
1141
|
+
* strategy: 'skip'
|
|
1142
|
+
* });
|
|
1143
|
+
*
|
|
1144
|
+
* @example
|
|
1145
|
+
* // Import with update strategy (update existing collections)
|
|
1146
|
+
* const result = await client.collections.import({
|
|
1147
|
+
* data: exportData,
|
|
1148
|
+
* strategy: 'update'
|
|
1149
|
+
* });
|
|
1150
|
+
*/
|
|
1151
|
+
async import(request) {
|
|
1152
|
+
return (await this.http.post("/api/v1/collections/import", request)).data;
|
|
1153
|
+
}
|
|
910
1154
|
};
|
|
911
1155
|
|
|
912
1156
|
//#endregion
|
|
@@ -1090,7 +1334,14 @@ var RecordService = class {
|
|
|
1090
1334
|
if (params.sort !== void 0) formattedParams.sort = params.sort;
|
|
1091
1335
|
if (params.fields) formattedParams.fields = Array.isArray(params.fields) ? params.fields.join(",") : params.fields;
|
|
1092
1336
|
if (params.expand) formattedParams.expand = Array.isArray(params.expand) ? params.expand.join(",") : params.expand;
|
|
1093
|
-
if (params.filter)
|
|
1337
|
+
if (params.filter) {
|
|
1338
|
+
const filterStr = typeof params.filter === "string" ? params.filter : JSON.stringify(params.filter);
|
|
1339
|
+
const match = filterStr.match(/^\(?\s*(\w+)\s*=\s*(["']?)([^"'\)]+)\2\s*\)?$/);
|
|
1340
|
+
if (match) {
|
|
1341
|
+
const [, key, , value] = match;
|
|
1342
|
+
formattedParams[key] = value;
|
|
1343
|
+
} else formattedParams.filter = filterStr;
|
|
1344
|
+
}
|
|
1094
1345
|
}
|
|
1095
1346
|
return (await this.http.get(`/api/v1/records/${collection}`, { params: formattedParams })).data;
|
|
1096
1347
|
}
|
|
@@ -1276,33 +1527,34 @@ var ApiKeyService = class {
|
|
|
1276
1527
|
this.http = http;
|
|
1277
1528
|
}
|
|
1278
1529
|
/**
|
|
1279
|
-
* List all API keys
|
|
1280
|
-
*
|
|
1530
|
+
* List all API keys
|
|
1531
|
+
* GET /api/v1/admin/api-keys
|
|
1281
1532
|
*/
|
|
1282
|
-
async list() {
|
|
1283
|
-
return (await this.http.get(
|
|
1533
|
+
async list(params) {
|
|
1534
|
+
return (await this.http.get(API_KEY_BASE_PATH, { params })).data;
|
|
1284
1535
|
}
|
|
1285
1536
|
/**
|
|
1286
|
-
* Get
|
|
1287
|
-
*
|
|
1537
|
+
* Get specific API key
|
|
1538
|
+
* GET /api/v1/admin/api-keys/{id}
|
|
1288
1539
|
*/
|
|
1289
1540
|
async get(keyId) {
|
|
1290
|
-
return (await this.http.get(
|
|
1541
|
+
return (await this.http.get(`${API_KEY_BASE_PATH}/${encodeURIComponent(keyId)}`)).data;
|
|
1291
1542
|
}
|
|
1292
1543
|
/**
|
|
1293
|
-
* Create a new API key
|
|
1294
|
-
*
|
|
1544
|
+
* Create a new API key
|
|
1545
|
+
* POST /api/v1/admin/api-keys
|
|
1295
1546
|
*/
|
|
1296
1547
|
async create(data) {
|
|
1297
|
-
|
|
1548
|
+
const apiKey = (await this.http.post(API_KEY_BASE_PATH, data)).data;
|
|
1549
|
+
if (apiKey.key && !apiKey.masked_key) apiKey.masked_key = formatMaskedKey(apiKey.key);
|
|
1550
|
+
return apiKey;
|
|
1298
1551
|
}
|
|
1299
1552
|
/**
|
|
1300
|
-
* Revoke an
|
|
1301
|
-
*
|
|
1553
|
+
* Revoke an API key
|
|
1554
|
+
* DELETE /api/v1/admin/api-keys/{id}
|
|
1302
1555
|
*/
|
|
1303
1556
|
async revoke(keyId) {
|
|
1304
|
-
await this.http.delete(
|
|
1305
|
-
return { success: true };
|
|
1557
|
+
return (await this.http.delete(`${API_KEY_BASE_PATH}/${encodeURIComponent(keyId)}`)).data;
|
|
1306
1558
|
}
|
|
1307
1559
|
};
|
|
1308
1560
|
|
|
@@ -1325,7 +1577,25 @@ var AuditLogService = class {
|
|
|
1325
1577
|
return (await this.httpClient.get(`/api/v1/audit-logs/${logId}`)).data;
|
|
1326
1578
|
}
|
|
1327
1579
|
/**
|
|
1328
|
-
* Exports audit logs in the specified format.
|
|
1580
|
+
* Exports audit logs in the specified format (JSON, CSV, or PDF).
|
|
1581
|
+
*
|
|
1582
|
+
* @param params Optional filters (account_id, table_name, operation, date range, etc.)
|
|
1583
|
+
* @param format Export format: 'json', 'csv', or 'pdf' (default: 'json')
|
|
1584
|
+
* @returns Exported data as string (base64-encoded for PDF format)
|
|
1585
|
+
* @throws {AuthorizationError} If user is not a superadmin
|
|
1586
|
+
*
|
|
1587
|
+
* @example
|
|
1588
|
+
* // Export as JSON
|
|
1589
|
+
* const jsonData = await client.auditLogs.export({ table_name: 'users' }, 'json');
|
|
1590
|
+
*
|
|
1591
|
+
* @example
|
|
1592
|
+
* // Export as CSV
|
|
1593
|
+
* const csvData = await client.auditLogs.export({ table_name: 'users' }, 'csv');
|
|
1594
|
+
*
|
|
1595
|
+
* @example
|
|
1596
|
+
* // Export as PDF
|
|
1597
|
+
* const pdfBase64 = await client.auditLogs.export({ table_name: 'users' }, 'pdf');
|
|
1598
|
+
* // pdfBase64 is a base64-encoded PDF string
|
|
1329
1599
|
*/
|
|
1330
1600
|
async export(params, format = "json") {
|
|
1331
1601
|
return (await this.httpClient.get("/api/v1/audit-logs/export", { params: {
|
|
@@ -2278,6 +2548,7 @@ var SnackBaseClient = class {
|
|
|
2278
2548
|
this.http.addRequestInterceptor(contentTypeInterceptor);
|
|
2279
2549
|
this.http.addRequestInterceptor(createAuthInterceptor(() => this.authManager.token || void 0, this.config.apiKey));
|
|
2280
2550
|
this.http.addResponseInterceptor(errorNormalizationInterceptor);
|
|
2551
|
+
this.http.addErrorInterceptor(createAuthErrorInterceptor(this.config.onAuthError));
|
|
2281
2552
|
this.http.addErrorInterceptor(errorInterceptor);
|
|
2282
2553
|
}
|
|
2283
2554
|
/**
|
|
@@ -2299,6 +2570,36 @@ var SnackBaseClient = class {
|
|
|
2299
2570
|
return this.authManager.isAuthenticated;
|
|
2300
2571
|
}
|
|
2301
2572
|
/**
|
|
2573
|
+
* Check if current user is superadmin.
|
|
2574
|
+
*/
|
|
2575
|
+
get isSuperadmin() {
|
|
2576
|
+
return this.authManager.isSuperadmin();
|
|
2577
|
+
}
|
|
2578
|
+
/**
|
|
2579
|
+
* Check if current session uses API key authentication.
|
|
2580
|
+
*/
|
|
2581
|
+
get isApiKeySession() {
|
|
2582
|
+
return this.authManager.isApiKeySession();
|
|
2583
|
+
}
|
|
2584
|
+
/**
|
|
2585
|
+
* Check if current session uses personal token authentication.
|
|
2586
|
+
*/
|
|
2587
|
+
get isPersonalTokenSession() {
|
|
2588
|
+
return this.authManager.isPersonalTokenSession();
|
|
2589
|
+
}
|
|
2590
|
+
/**
|
|
2591
|
+
* Check if current session uses OAuth authentication.
|
|
2592
|
+
*/
|
|
2593
|
+
get isOAuthSession() {
|
|
2594
|
+
return this.authManager.isOAuthSession();
|
|
2595
|
+
}
|
|
2596
|
+
/**
|
|
2597
|
+
* Returns the current token type.
|
|
2598
|
+
*/
|
|
2599
|
+
get tokenType() {
|
|
2600
|
+
return this.authManager.tokenType;
|
|
2601
|
+
}
|
|
2602
|
+
/**
|
|
2302
2603
|
* Access to authentication methods.
|
|
2303
2604
|
*/
|
|
2304
2605
|
get auth() {
|
|
@@ -2513,4 +2814,4 @@ var SnackBaseClient = class {
|
|
|
2513
2814
|
};
|
|
2514
2815
|
|
|
2515
2816
|
//#endregion
|
|
2516
|
-
export { DEFAULT_CONFIG, QueryBuilder, SnackBaseClient, getAutoDetectedStorage };
|
|
2817
|
+
export { ApiKeyRestrictedError, AuthenticationError, AuthorizationError, ConflictError, DEFAULT_CONFIG, EmailVerificationRequiredError, NetworkError, NotFoundError, QueryBuilder, RateLimitError, ServerError, SnackBaseClient, SnackBaseError, TimeoutError, TokenType, ValidationError, getAutoDetectedStorage };
|
package/package.json
CHANGED
|
@@ -1,48 +1,43 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@snackbase/sdk",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Core SDK for SnackBase",
|
|
5
|
+
"main": "./dist/index.cjs",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.cts",
|
|
5
8
|
"exports": {
|
|
6
9
|
".": {
|
|
7
|
-
"import":
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
"import": {
|
|
11
|
+
"types": "./dist/index.d.mts",
|
|
12
|
+
"default": "./dist/index.mjs"
|
|
13
|
+
},
|
|
14
|
+
"require": {
|
|
15
|
+
"types": "./dist/index.d.cts",
|
|
16
|
+
"default": "./dist/index.cjs"
|
|
17
|
+
}
|
|
15
18
|
}
|
|
16
19
|
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist"
|
|
22
|
+
],
|
|
23
|
+
"keywords": [
|
|
24
|
+
"snackbase",
|
|
25
|
+
"sdk",
|
|
26
|
+
"database"
|
|
27
|
+
],
|
|
28
|
+
"author": "SnackBase Team",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@snackbase/tsconfig": "0.1.0"
|
|
35
|
+
},
|
|
17
36
|
"scripts": {
|
|
18
|
-
"dev": "tsdown src/index.ts
|
|
19
|
-
"build": "tsdown src/index.ts
|
|
37
|
+
"dev": "tsdown src/index.ts --format esm,cjs --watch",
|
|
38
|
+
"build": "tsdown src/index.ts --format esm,cjs --dts",
|
|
20
39
|
"test": "vitest run",
|
|
21
|
-
"test:watch": "vitest",
|
|
22
|
-
"test:integration": "vitest run --config vitest.integration.config.ts",
|
|
23
|
-
"test:coverage": "vitest run --coverage",
|
|
24
40
|
"typecheck": "tsc --noEmit",
|
|
25
|
-
"
|
|
26
|
-
"prepublishOnly": "npm run build && npm run test"
|
|
27
|
-
},
|
|
28
|
-
"peerDependencies": {
|
|
29
|
-
"react": "^18.0.0 || ^19.0.0"
|
|
30
|
-
},
|
|
31
|
-
"peerDependenciesMeta": {
|
|
32
|
-
"react": {
|
|
33
|
-
"optional": true
|
|
34
|
-
}
|
|
35
|
-
},
|
|
36
|
-
"devDependencies": {
|
|
37
|
-
"@testing-library/jest-dom": "^6.9.1",
|
|
38
|
-
"@testing-library/react": "^16.3.2",
|
|
39
|
-
"@vitejs/plugin-react": "^5.1.2",
|
|
40
|
-
"jsdom": "^27.4.0",
|
|
41
|
-
"react": "^18.2.0",
|
|
42
|
-
"react-dom": "^18.2.0",
|
|
43
|
-
"tsdown": "^0.20.0-beta.4",
|
|
44
|
-
"typescript": "^5.3.3",
|
|
45
|
-
"vite": "^7.3.1",
|
|
46
|
-
"vitest": "^1.2.1"
|
|
41
|
+
"clean": "rm -rf dist"
|
|
47
42
|
}
|
|
48
|
-
}
|
|
43
|
+
}
|