@techfinityedge/koolbase-react-native 2.1.0 → 2.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/README.md +52 -0
- package/dist/auth.d.ts +31 -6
- package/dist/auth.js +125 -23
- package/dist/database-errors.d.ts +76 -0
- package/dist/database-errors.js +143 -0
- package/dist/database.js +4 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -162,6 +162,20 @@ await Koolbase.db.update('record-id', { title: 'Updated' });
|
|
|
162
162
|
await Koolbase.db.delete('record-id');
|
|
163
163
|
```
|
|
164
164
|
|
|
165
|
+
### Handling unique-constraint conflicts
|
|
166
|
+
|
|
167
|
+
A write that would violate a unique constraint throws `KoolbaseConflictError`:
|
|
168
|
+
|
|
169
|
+
\`\`\`ts
|
|
170
|
+
try {
|
|
171
|
+
await koolbase.db.upsert('users', { email }, { name });
|
|
172
|
+
} catch (e) {
|
|
173
|
+
if (e instanceof KoolbaseConflictError) {
|
|
174
|
+
showError('That email is already registered.');
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
\`\`\`
|
|
178
|
+
|
|
165
179
|
### Upsert
|
|
166
180
|
|
|
167
181
|
Insert a record, or update the existing one matching a filter.
|
|
@@ -388,6 +402,44 @@ await Koolbase.messaging.send({
|
|
|
388
402
|
|
|
389
403
|
---
|
|
390
404
|
|
|
405
|
+
## Error handling
|
|
406
|
+
|
|
407
|
+
Koolbase throws typed errors selected from the server's stable error `code`, so
|
|
408
|
+
handling doesn't depend on message text.
|
|
409
|
+
|
|
410
|
+
### Database errors
|
|
411
|
+
|
|
412
|
+
All data-layer failures extend `KoolbaseDataError` (which extends `Error`):
|
|
413
|
+
|
|
414
|
+
| Error | When |
|
|
415
|
+
|---|---|
|
|
416
|
+
| `KoolbaseConflictError` | A write violates a unique constraint (409). Exposes `.field`. |
|
|
417
|
+
| `KoolbaseNotFoundError` | The record or collection doesn't exist (404). |
|
|
418
|
+
| `KoolbaseValidationError` | The request was rejected as invalid (400). |
|
|
419
|
+
| `KoolbasePermissionError` | An access rule denied the operation (403). |
|
|
420
|
+
| `KoolbaseRateLimitError` | The caller is being rate-limited (429). |
|
|
421
|
+
|
|
422
|
+
```ts
|
|
423
|
+
import { KoolbaseConflictError, KoolbaseDataError } from '@techfinityedge/koolbase-react-native';
|
|
424
|
+
|
|
425
|
+
try {
|
|
426
|
+
await koolbase.db.upsert('users', { email }, { name });
|
|
427
|
+
} catch (e) {
|
|
428
|
+
if (e instanceof KoolbaseConflictError) {
|
|
429
|
+
showError(`That ${e.field ?? 'value'} is already taken.`);
|
|
430
|
+
} else if (e instanceof KoolbaseDataError) {
|
|
431
|
+
showError(e.message);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
> `query`, `get`, `upsert`, and `deleteWhere` throw these typed errors. `insert`,
|
|
437
|
+
> `update`, and `delete` are optimistic/offline-first — they queue and sync in
|
|
438
|
+
> the background, so their conflicts surface via the sync engine, not as a
|
|
439
|
+
> thrown error.
|
|
440
|
+
|
|
441
|
+
---
|
|
442
|
+
|
|
391
443
|
## What's included
|
|
392
444
|
|
|
393
445
|
- Authentication: email + password, Apple Sign-In, phone + OTP
|
package/dist/auth.d.ts
CHANGED
|
@@ -110,15 +110,18 @@ export declare class KoolbaseAuth {
|
|
|
110
110
|
*/
|
|
111
111
|
signInWithGoogle(params: SignInWithGoogleParams): Promise<KoolbaseSession>;
|
|
112
112
|
/**
|
|
113
|
-
* Parses a /v1/sdk/auth/oauth/google response.
|
|
114
|
-
*
|
|
115
|
-
*
|
|
113
|
+
* Parses a /v1/sdk/auth/oauth/google response. Code-first: the server
|
|
114
|
+
* emits unified OAuth codes (oauth_not_configured, invalid_oauth_token,
|
|
115
|
+
* oauth_email_required, oauth_email_conflict) for both providers; the
|
|
116
|
+
* provider distinction is made here so Google codes map to Google-specific
|
|
117
|
+
* errors. Status + message logic is retained as a fallback for older servers.
|
|
116
118
|
*/
|
|
117
119
|
private parseGoogleSessionResponse;
|
|
118
120
|
/**
|
|
119
|
-
* Parses a /v1/sdk/auth/oauth/apple response.
|
|
120
|
-
*
|
|
121
|
-
*
|
|
121
|
+
* Parses a /v1/sdk/auth/oauth/apple response. Code-first; the provider
|
|
122
|
+
* distinction is made here so the server's unified OAuth codes map to
|
|
123
|
+
* Apple-specific errors. Status + message logic is retained as a fallback
|
|
124
|
+
* for older servers.
|
|
122
125
|
*/
|
|
123
126
|
private parseAppleSessionResponse;
|
|
124
127
|
refresh(refreshToken?: string): Promise<KoolbaseSession>;
|
|
@@ -159,8 +162,30 @@ export declare class KoolbaseAuth {
|
|
|
159
162
|
private validatePhone;
|
|
160
163
|
private _ensureValidToken;
|
|
161
164
|
private mapUser;
|
|
165
|
+
/**
|
|
166
|
+
* Parse a session-returning response (login, register, refresh).
|
|
167
|
+
* Non-2xx is delegated to throwTypedError, which is code-first
|
|
168
|
+
* (reads body.code) with a status/message fallback. isRefresh only
|
|
169
|
+
* affects how a bare 401 (no code, older server) is interpreted.
|
|
170
|
+
*/
|
|
162
171
|
private parseSessionResponse;
|
|
163
172
|
private checkResponse;
|
|
173
|
+
/**
|
|
174
|
+
* Map a non-2xx credential/session response to a typed error.
|
|
175
|
+
*
|
|
176
|
+
* Code-first: the server now emits a stable `code` on every error
|
|
177
|
+
* (contract conformance), so we switch on body.code. The status +
|
|
178
|
+
* message logic is retained as a fallback for older servers or any
|
|
179
|
+
* response that arrives without a code. isRefresh only changes how a
|
|
180
|
+
* bare 401 is interpreted in the fallback path.
|
|
181
|
+
*/
|
|
164
182
|
private throwTypedError;
|
|
183
|
+
/**
|
|
184
|
+
* Parse a phone-auth response. Code-first, with a phone-specific twist:
|
|
185
|
+
* the server emits the generic `rate_limit` code for the phone endpoints
|
|
186
|
+
* (they share the default 429), but phone has a dedicated server-side
|
|
187
|
+
* rate-limiter, so we surface OtpRateLimitError rather than RateLimitError.
|
|
188
|
+
* Status + message logic is retained as a fallback for older servers.
|
|
189
|
+
*/
|
|
165
190
|
private parsePhoneResponse;
|
|
166
191
|
}
|
package/dist/auth.js
CHANGED
|
@@ -297,9 +297,11 @@ class KoolbaseAuth {
|
|
|
297
297
|
return session;
|
|
298
298
|
}
|
|
299
299
|
/**
|
|
300
|
-
* Parses a /v1/sdk/auth/oauth/google response.
|
|
301
|
-
*
|
|
302
|
-
*
|
|
300
|
+
* Parses a /v1/sdk/auth/oauth/google response. Code-first: the server
|
|
301
|
+
* emits unified OAuth codes (oauth_not_configured, invalid_oauth_token,
|
|
302
|
+
* oauth_email_required, oauth_email_conflict) for both providers; the
|
|
303
|
+
* provider distinction is made here so Google codes map to Google-specific
|
|
304
|
+
* errors. Status + message logic is retained as a fallback for older servers.
|
|
303
305
|
*/
|
|
304
306
|
async parseGoogleSessionResponse(res) {
|
|
305
307
|
if (res.status === 200) {
|
|
@@ -311,14 +313,31 @@ class KoolbaseAuth {
|
|
|
311
313
|
user: this.mapUser(data.user),
|
|
312
314
|
};
|
|
313
315
|
}
|
|
314
|
-
let
|
|
316
|
+
let body = {};
|
|
315
317
|
try {
|
|
316
|
-
|
|
317
|
-
errorMessage = data?.error ?? '';
|
|
318
|
+
body = await res.json();
|
|
318
319
|
}
|
|
319
320
|
catch {
|
|
320
321
|
// best-effort error message extraction
|
|
321
322
|
}
|
|
323
|
+
const code = body?.code ?? '';
|
|
324
|
+
const errorMessage = body?.error ?? '';
|
|
325
|
+
// ─── code-first ───
|
|
326
|
+
switch (code) {
|
|
327
|
+
case 'oauth_not_configured':
|
|
328
|
+
throw new auth_errors_1.GoogleSignInNotConfiguredError();
|
|
329
|
+
case 'invalid_oauth_token':
|
|
330
|
+
throw new auth_errors_1.InvalidGoogleTokenError();
|
|
331
|
+
case 'account_disabled':
|
|
332
|
+
throw new auth_errors_1.UserDisabledError();
|
|
333
|
+
case 'oauth_email_required':
|
|
334
|
+
throw new auth_errors_1.GoogleEmailRequiredError();
|
|
335
|
+
case 'oauth_email_conflict':
|
|
336
|
+
throw new auth_errors_1.OAuthEmailConflictError();
|
|
337
|
+
case 'rate_limit':
|
|
338
|
+
throw new auth_errors_1.RateLimitError(errorMessage || undefined);
|
|
339
|
+
}
|
|
340
|
+
// ─── status + message fallback (pre-code servers) ───
|
|
322
341
|
if (res.status === 400) {
|
|
323
342
|
if (errorMessage.includes('not configured')) {
|
|
324
343
|
throw new auth_errors_1.GoogleSignInNotConfiguredError();
|
|
@@ -339,9 +358,10 @@ class KoolbaseAuth {
|
|
|
339
358
|
throw new auth_errors_1.KoolbaseAuthError(`google sign-in failed: ${res.status} ${errorMessage}`, `google_signin_http_${res.status}`);
|
|
340
359
|
}
|
|
341
360
|
/**
|
|
342
|
-
* Parses a /v1/sdk/auth/oauth/apple response.
|
|
343
|
-
*
|
|
344
|
-
*
|
|
361
|
+
* Parses a /v1/sdk/auth/oauth/apple response. Code-first; the provider
|
|
362
|
+
* distinction is made here so the server's unified OAuth codes map to
|
|
363
|
+
* Apple-specific errors. Status + message logic is retained as a fallback
|
|
364
|
+
* for older servers.
|
|
345
365
|
*/
|
|
346
366
|
async parseAppleSessionResponse(res) {
|
|
347
367
|
if (res.status === 200) {
|
|
@@ -353,14 +373,31 @@ class KoolbaseAuth {
|
|
|
353
373
|
user: this.mapUser(data.user),
|
|
354
374
|
};
|
|
355
375
|
}
|
|
356
|
-
let
|
|
376
|
+
let body = {};
|
|
357
377
|
try {
|
|
358
|
-
|
|
359
|
-
errorMessage = data?.error ?? '';
|
|
378
|
+
body = await res.json();
|
|
360
379
|
}
|
|
361
380
|
catch {
|
|
362
381
|
// best-effort error message extraction
|
|
363
382
|
}
|
|
383
|
+
const code = body?.code ?? '';
|
|
384
|
+
const errorMessage = body?.error ?? '';
|
|
385
|
+
// ─── code-first ───
|
|
386
|
+
switch (code) {
|
|
387
|
+
case 'oauth_not_configured':
|
|
388
|
+
throw new auth_errors_1.AppleSignInNotConfiguredError();
|
|
389
|
+
case 'invalid_oauth_token':
|
|
390
|
+
throw new auth_errors_1.InvalidAppleTokenError();
|
|
391
|
+
case 'account_disabled':
|
|
392
|
+
throw new auth_errors_1.UserDisabledError();
|
|
393
|
+
case 'oauth_email_required':
|
|
394
|
+
throw new auth_errors_1.AppleEmailRequiredError();
|
|
395
|
+
case 'oauth_email_conflict':
|
|
396
|
+
throw new auth_errors_1.OAuthEmailConflictError();
|
|
397
|
+
case 'rate_limit':
|
|
398
|
+
throw new auth_errors_1.RateLimitError(errorMessage || undefined);
|
|
399
|
+
}
|
|
400
|
+
// ─── status + message fallback (pre-code servers) ───
|
|
364
401
|
if (res.status === 400) {
|
|
365
402
|
if (errorMessage.includes('not configured')) {
|
|
366
403
|
throw new auth_errors_1.AppleSignInNotConfiguredError();
|
|
@@ -592,16 +629,15 @@ class KoolbaseAuth {
|
|
|
592
629
|
createdAt: raw.created_at,
|
|
593
630
|
};
|
|
594
631
|
}
|
|
632
|
+
/**
|
|
633
|
+
* Parse a session-returning response (login, register, refresh).
|
|
634
|
+
* Non-2xx is delegated to throwTypedError, which is code-first
|
|
635
|
+
* (reads body.code) with a status/message fallback. isRefresh only
|
|
636
|
+
* affects how a bare 401 (no code, older server) is interpreted.
|
|
637
|
+
*/
|
|
595
638
|
async parseSessionResponse(res, isRefresh) {
|
|
596
|
-
if (res.status === 409)
|
|
597
|
-
throw new auth_errors_1.EmailAlreadyInUseError();
|
|
598
|
-
if (res.status === 401) {
|
|
599
|
-
throw isRefresh ? new auth_errors_1.SessionExpiredError() : new auth_errors_1.InvalidCredentialsError();
|
|
600
|
-
}
|
|
601
|
-
if (res.status === 403)
|
|
602
|
-
throw new auth_errors_1.UserDisabledError();
|
|
603
639
|
if (!res.ok)
|
|
604
|
-
await this.throwTypedError(res);
|
|
640
|
+
await this.throwTypedError(res, isRefresh); // never returns
|
|
605
641
|
const data = await res.json();
|
|
606
642
|
return {
|
|
607
643
|
accessToken: data.access_token,
|
|
@@ -615,7 +651,16 @@ class KoolbaseAuth {
|
|
|
615
651
|
return;
|
|
616
652
|
await this.throwTypedError(res);
|
|
617
653
|
}
|
|
618
|
-
|
|
654
|
+
/**
|
|
655
|
+
* Map a non-2xx credential/session response to a typed error.
|
|
656
|
+
*
|
|
657
|
+
* Code-first: the server now emits a stable `code` on every error
|
|
658
|
+
* (contract conformance), so we switch on body.code. The status +
|
|
659
|
+
* message logic is retained as a fallback for older servers or any
|
|
660
|
+
* response that arrives without a code. isRefresh only changes how a
|
|
661
|
+
* bare 401 is interpreted in the fallback path.
|
|
662
|
+
*/
|
|
663
|
+
async throwTypedError(res, isRefresh = false) {
|
|
619
664
|
let body = {};
|
|
620
665
|
try {
|
|
621
666
|
body = await res.json();
|
|
@@ -623,13 +668,43 @@ class KoolbaseAuth {
|
|
|
623
668
|
catch {
|
|
624
669
|
// ignore
|
|
625
670
|
}
|
|
671
|
+
const code = body.code ?? '';
|
|
626
672
|
const msg = body.error ?? '';
|
|
673
|
+
// ─── code-first ───
|
|
674
|
+
switch (code) {
|
|
675
|
+
case 'invalid_credentials':
|
|
676
|
+
throw new auth_errors_1.InvalidCredentialsError();
|
|
677
|
+
case 'email_in_use':
|
|
678
|
+
throw new auth_errors_1.EmailAlreadyInUseError();
|
|
679
|
+
case 'account_disabled':
|
|
680
|
+
throw new auth_errors_1.UserDisabledError();
|
|
681
|
+
case 'account_locked':
|
|
682
|
+
throw new auth_errors_1.AccountLockedError();
|
|
683
|
+
case 'invalid_refresh_token':
|
|
684
|
+
// Refresh token rejected — the session is unrecoverable; re-login.
|
|
685
|
+
throw new auth_errors_1.SessionExpiredError();
|
|
686
|
+
case 'token_revoked':
|
|
687
|
+
throw new auth_errors_1.TokenRevokedError();
|
|
688
|
+
case 'invalid_unlock_token':
|
|
689
|
+
throw new auth_errors_1.UnlockTokenInvalidError();
|
|
690
|
+
case 'rate_limit':
|
|
691
|
+
throw new auth_errors_1.RateLimitError(msg || undefined);
|
|
692
|
+
}
|
|
693
|
+
// ─── status fallback (pre-code servers) ───
|
|
694
|
+
if (res.status === 409)
|
|
695
|
+
throw new auth_errors_1.EmailAlreadyInUseError();
|
|
696
|
+
if (res.status === 401) {
|
|
697
|
+
throw isRefresh ? new auth_errors_1.SessionExpiredError() : new auth_errors_1.InvalidCredentialsError();
|
|
698
|
+
}
|
|
699
|
+
if (res.status === 403)
|
|
700
|
+
throw new auth_errors_1.UserDisabledError();
|
|
627
701
|
if (res.status === 429) {
|
|
628
702
|
if (msg.includes('account temporarily locked')) {
|
|
629
703
|
throw new auth_errors_1.AccountLockedError();
|
|
630
704
|
}
|
|
631
705
|
throw new auth_errors_1.RateLimitError(msg || undefined);
|
|
632
706
|
}
|
|
707
|
+
// ─── legacy message fallback ───
|
|
633
708
|
if (msg.includes('invalid or expired unlock token')) {
|
|
634
709
|
throw new auth_errors_1.UnlockTokenInvalidError();
|
|
635
710
|
}
|
|
@@ -638,8 +713,15 @@ class KoolbaseAuth {
|
|
|
638
713
|
msg.includes('session has been revoked')) {
|
|
639
714
|
throw new auth_errors_1.TokenRevokedError();
|
|
640
715
|
}
|
|
641
|
-
throw new auth_errors_1.KoolbaseAuthError(msg || `Request failed: ${res.status}`, `http_${res.status}`);
|
|
716
|
+
throw new auth_errors_1.KoolbaseAuthError(msg || `Request failed: ${res.status}`, code || `http_${res.status}`);
|
|
642
717
|
}
|
|
718
|
+
/**
|
|
719
|
+
* Parse a phone-auth response. Code-first, with a phone-specific twist:
|
|
720
|
+
* the server emits the generic `rate_limit` code for the phone endpoints
|
|
721
|
+
* (they share the default 429), but phone has a dedicated server-side
|
|
722
|
+
* rate-limiter, so we surface OtpRateLimitError rather than RateLimitError.
|
|
723
|
+
* Status + message logic is retained as a fallback for older servers.
|
|
724
|
+
*/
|
|
643
725
|
async parsePhoneResponse(res) {
|
|
644
726
|
let body = {};
|
|
645
727
|
try {
|
|
@@ -650,11 +732,31 @@ class KoolbaseAuth {
|
|
|
650
732
|
}
|
|
651
733
|
if (res.ok)
|
|
652
734
|
return body;
|
|
735
|
+
const code = body.code ?? '';
|
|
653
736
|
const msg = body.error ?? '';
|
|
737
|
+
// ─── code-first ───
|
|
738
|
+
switch (code) {
|
|
739
|
+
case 'invalid_phone':
|
|
740
|
+
throw new auth_errors_1.InvalidPhoneNumberError();
|
|
741
|
+
case 'otp_expired':
|
|
742
|
+
throw new auth_errors_1.OtpExpiredError();
|
|
743
|
+
case 'otp_invalid':
|
|
744
|
+
throw new auth_errors_1.OtpInvalidError();
|
|
745
|
+
case 'otp_max_attempts':
|
|
746
|
+
throw new auth_errors_1.OtpMaxAttemptsError();
|
|
747
|
+
case 'phone_in_use':
|
|
748
|
+
throw new auth_errors_1.PhoneAlreadyLinkedError();
|
|
749
|
+
case 'sms_not_configured':
|
|
750
|
+
throw new auth_errors_1.SmsConfigMissingError();
|
|
751
|
+
case 'rate_limit':
|
|
752
|
+
throw new auth_errors_1.OtpRateLimitError();
|
|
753
|
+
}
|
|
754
|
+
// ─── status fallback (pre-code servers) ───
|
|
654
755
|
if (res.status === 429)
|
|
655
756
|
throw new auth_errors_1.OtpRateLimitError();
|
|
656
757
|
if (res.status === 409)
|
|
657
758
|
throw new auth_errors_1.PhoneAlreadyLinkedError();
|
|
759
|
+
// ─── legacy message fallback ───
|
|
658
760
|
if (msg.includes('E.164'))
|
|
659
761
|
throw new auth_errors_1.InvalidPhoneNumberError();
|
|
660
762
|
if (msg.includes('OTP has expired'))
|
|
@@ -669,7 +771,7 @@ class KoolbaseAuth {
|
|
|
669
771
|
if (msg.includes('SMS provider not configured')) {
|
|
670
772
|
throw new auth_errors_1.SmsConfigMissingError();
|
|
671
773
|
}
|
|
672
|
-
throw new auth_errors_1.KoolbaseAuthError(msg || 'An unexpected error occurred');
|
|
774
|
+
throw new auth_errors_1.KoolbaseAuthError(msg || 'An unexpected error occurred', code || undefined);
|
|
673
775
|
}
|
|
674
776
|
}
|
|
675
777
|
exports.KoolbaseAuth = KoolbaseAuth;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base class for errors surfaced by the Koolbase data layer (database reads
|
|
3
|
+
* and writes). Every data error carries a `message` and, when the server
|
|
4
|
+
* provides one, its stable `code` (e.g. `not_found`, `validation_error`,
|
|
5
|
+
* `unique_violation`).
|
|
6
|
+
*
|
|
7
|
+
* Catch this to handle any data-layer failure generically, or catch a
|
|
8
|
+
* specific subclass to branch on the kind of failure.
|
|
9
|
+
*/
|
|
10
|
+
export declare class KoolbaseDataError extends Error {
|
|
11
|
+
code?: string;
|
|
12
|
+
constructor(message: string, code?: string);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Thrown when a write is rejected because the value would violate a
|
|
16
|
+
* collection's unique constraint — the server responds with 409 Conflict.
|
|
17
|
+
* Catch it to handle duplicates, e.g. an email or username already in use.
|
|
18
|
+
*
|
|
19
|
+
* `field` names the field that collided, when the server reports it
|
|
20
|
+
* (`details.field`) — useful when a collection has more than one unique
|
|
21
|
+
* constraint and you need to know which value clashed.
|
|
22
|
+
*
|
|
23
|
+
* Currently surfaced by `upsert` (the online-only write). `insert` and
|
|
24
|
+
* `update` are optimistic/offline-first: they accept the write locally and
|
|
25
|
+
* sync in the background, so a constraint conflict on those paths is a
|
|
26
|
+
* sync-time concern rather than a thrown error.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* try {
|
|
30
|
+
* await koolbase.db.upsert('users', { email }, { name });
|
|
31
|
+
* } catch (e) {
|
|
32
|
+
* if (e instanceof KoolbaseConflictError) {
|
|
33
|
+
* showError(`That ${e.field ?? 'value'} is already registered.`);
|
|
34
|
+
* }
|
|
35
|
+
* }
|
|
36
|
+
*/
|
|
37
|
+
export declare class KoolbaseConflictError extends KoolbaseDataError {
|
|
38
|
+
field?: string;
|
|
39
|
+
constructor(message?: string, field?: string);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Thrown when the requested record or collection does not exist — the server
|
|
43
|
+
* responds with 404 and code `not_found` / `record_not_found` /
|
|
44
|
+
* `collection_not_found`.
|
|
45
|
+
*/
|
|
46
|
+
export declare class KoolbaseNotFoundError extends KoolbaseDataError {
|
|
47
|
+
constructor(message?: string);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Thrown when the request is rejected as invalid — the server responds with
|
|
51
|
+
* 400 and code `validation_error`.
|
|
52
|
+
*/
|
|
53
|
+
export declare class KoolbaseValidationError extends KoolbaseDataError {
|
|
54
|
+
constructor(message?: string);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Thrown when the caller is authenticated but not allowed to perform the
|
|
58
|
+
* operation — the server responds with 403 and code `permission_denied`
|
|
59
|
+
* (typically a collection access rule rejecting the read/write).
|
|
60
|
+
*/
|
|
61
|
+
export declare class KoolbasePermissionError extends KoolbaseDataError {
|
|
62
|
+
constructor(message?: string);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Thrown when the server is rate-limiting the caller — 429 with code
|
|
66
|
+
* `rate_limit`. Back off and retry after a short delay.
|
|
67
|
+
*/
|
|
68
|
+
export declare class KoolbaseRateLimitError extends KoolbaseDataError {
|
|
69
|
+
constructor(message?: string);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Maps a non-2xx data-layer response to a typed {@link KoolbaseDataError},
|
|
73
|
+
* preferring the server's stable `code` and falling back to the HTTP status
|
|
74
|
+
* for older or uncoded responses. Always returns an error to throw.
|
|
75
|
+
*/
|
|
76
|
+
export declare function koolbaseDataError(status: number, body: any, fallbackMessage?: string): KoolbaseDataError;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.KoolbaseRateLimitError = exports.KoolbasePermissionError = exports.KoolbaseValidationError = exports.KoolbaseNotFoundError = exports.KoolbaseConflictError = exports.KoolbaseDataError = void 0;
|
|
4
|
+
exports.koolbaseDataError = koolbaseDataError;
|
|
5
|
+
/**
|
|
6
|
+
* Base class for errors surfaced by the Koolbase data layer (database reads
|
|
7
|
+
* and writes). Every data error carries a `message` and, when the server
|
|
8
|
+
* provides one, its stable `code` (e.g. `not_found`, `validation_error`,
|
|
9
|
+
* `unique_violation`).
|
|
10
|
+
*
|
|
11
|
+
* Catch this to handle any data-layer failure generically, or catch a
|
|
12
|
+
* specific subclass to branch on the kind of failure.
|
|
13
|
+
*/
|
|
14
|
+
class KoolbaseDataError extends Error {
|
|
15
|
+
constructor(message, code) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.code = code;
|
|
18
|
+
this.name = 'KoolbaseDataError';
|
|
19
|
+
Object.setPrototypeOf(this, KoolbaseDataError.prototype);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
exports.KoolbaseDataError = KoolbaseDataError;
|
|
23
|
+
/**
|
|
24
|
+
* Thrown when a write is rejected because the value would violate a
|
|
25
|
+
* collection's unique constraint — the server responds with 409 Conflict.
|
|
26
|
+
* Catch it to handle duplicates, e.g. an email or username already in use.
|
|
27
|
+
*
|
|
28
|
+
* `field` names the field that collided, when the server reports it
|
|
29
|
+
* (`details.field`) — useful when a collection has more than one unique
|
|
30
|
+
* constraint and you need to know which value clashed.
|
|
31
|
+
*
|
|
32
|
+
* Currently surfaced by `upsert` (the online-only write). `insert` and
|
|
33
|
+
* `update` are optimistic/offline-first: they accept the write locally and
|
|
34
|
+
* sync in the background, so a constraint conflict on those paths is a
|
|
35
|
+
* sync-time concern rather than a thrown error.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* try {
|
|
39
|
+
* await koolbase.db.upsert('users', { email }, { name });
|
|
40
|
+
* } catch (e) {
|
|
41
|
+
* if (e instanceof KoolbaseConflictError) {
|
|
42
|
+
* showError(`That ${e.field ?? 'value'} is already registered.`);
|
|
43
|
+
* }
|
|
44
|
+
* }
|
|
45
|
+
*/
|
|
46
|
+
class KoolbaseConflictError extends KoolbaseDataError {
|
|
47
|
+
constructor(message, field) {
|
|
48
|
+
super(message ?? 'Value violates a unique constraint', 'unique_violation');
|
|
49
|
+
this.field = field;
|
|
50
|
+
this.name = 'KoolbaseConflictError';
|
|
51
|
+
Object.setPrototypeOf(this, KoolbaseConflictError.prototype);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
exports.KoolbaseConflictError = KoolbaseConflictError;
|
|
55
|
+
/**
|
|
56
|
+
* Thrown when the requested record or collection does not exist — the server
|
|
57
|
+
* responds with 404 and code `not_found` / `record_not_found` /
|
|
58
|
+
* `collection_not_found`.
|
|
59
|
+
*/
|
|
60
|
+
class KoolbaseNotFoundError extends KoolbaseDataError {
|
|
61
|
+
constructor(message) {
|
|
62
|
+
super(message ?? 'The requested resource was not found', 'not_found');
|
|
63
|
+
this.name = 'KoolbaseNotFoundError';
|
|
64
|
+
Object.setPrototypeOf(this, KoolbaseNotFoundError.prototype);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
exports.KoolbaseNotFoundError = KoolbaseNotFoundError;
|
|
68
|
+
/**
|
|
69
|
+
* Thrown when the request is rejected as invalid — the server responds with
|
|
70
|
+
* 400 and code `validation_error`.
|
|
71
|
+
*/
|
|
72
|
+
class KoolbaseValidationError extends KoolbaseDataError {
|
|
73
|
+
constructor(message) {
|
|
74
|
+
super(message ?? 'The request was invalid', 'validation_error');
|
|
75
|
+
this.name = 'KoolbaseValidationError';
|
|
76
|
+
Object.setPrototypeOf(this, KoolbaseValidationError.prototype);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
exports.KoolbaseValidationError = KoolbaseValidationError;
|
|
80
|
+
/**
|
|
81
|
+
* Thrown when the caller is authenticated but not allowed to perform the
|
|
82
|
+
* operation — the server responds with 403 and code `permission_denied`
|
|
83
|
+
* (typically a collection access rule rejecting the read/write).
|
|
84
|
+
*/
|
|
85
|
+
class KoolbasePermissionError extends KoolbaseDataError {
|
|
86
|
+
constructor(message) {
|
|
87
|
+
super(message ?? 'You do not have permission to perform this action', 'permission_denied');
|
|
88
|
+
this.name = 'KoolbasePermissionError';
|
|
89
|
+
Object.setPrototypeOf(this, KoolbasePermissionError.prototype);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
exports.KoolbasePermissionError = KoolbasePermissionError;
|
|
93
|
+
/**
|
|
94
|
+
* Thrown when the server is rate-limiting the caller — 429 with code
|
|
95
|
+
* `rate_limit`. Back off and retry after a short delay.
|
|
96
|
+
*/
|
|
97
|
+
class KoolbaseRateLimitError extends KoolbaseDataError {
|
|
98
|
+
constructor(message) {
|
|
99
|
+
super(message ?? 'Too many requests, please slow down', 'rate_limit');
|
|
100
|
+
this.name = 'KoolbaseRateLimitError';
|
|
101
|
+
Object.setPrototypeOf(this, KoolbaseRateLimitError.prototype);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
exports.KoolbaseRateLimitError = KoolbaseRateLimitError;
|
|
105
|
+
/**
|
|
106
|
+
* Maps a non-2xx data-layer response to a typed {@link KoolbaseDataError},
|
|
107
|
+
* preferring the server's stable `code` and falling back to the HTTP status
|
|
108
|
+
* for older or uncoded responses. Always returns an error to throw.
|
|
109
|
+
*/
|
|
110
|
+
function koolbaseDataError(status, body, fallbackMessage = 'Request failed') {
|
|
111
|
+
const code = body?.code;
|
|
112
|
+
const message = body?.error ?? fallbackMessage;
|
|
113
|
+
const field = body?.details?.field;
|
|
114
|
+
// ─── code-first ───
|
|
115
|
+
switch (code) {
|
|
116
|
+
case 'unique_violation':
|
|
117
|
+
return new KoolbaseConflictError(message, field);
|
|
118
|
+
case 'not_found':
|
|
119
|
+
case 'record_not_found':
|
|
120
|
+
case 'collection_not_found':
|
|
121
|
+
return new KoolbaseNotFoundError(message);
|
|
122
|
+
case 'permission_denied':
|
|
123
|
+
return new KoolbasePermissionError(message);
|
|
124
|
+
case 'rate_limit':
|
|
125
|
+
return new KoolbaseRateLimitError(message);
|
|
126
|
+
case 'validation_error':
|
|
127
|
+
return new KoolbaseValidationError(message);
|
|
128
|
+
}
|
|
129
|
+
// ─── status fallback (pre-code servers) ───
|
|
130
|
+
switch (status) {
|
|
131
|
+
case 409:
|
|
132
|
+
return new KoolbaseConflictError(message);
|
|
133
|
+
case 404:
|
|
134
|
+
return new KoolbaseNotFoundError(message);
|
|
135
|
+
case 403:
|
|
136
|
+
return new KoolbasePermissionError(message);
|
|
137
|
+
case 429:
|
|
138
|
+
return new KoolbaseRateLimitError(message);
|
|
139
|
+
case 400:
|
|
140
|
+
return new KoolbaseValidationError(message);
|
|
141
|
+
}
|
|
142
|
+
return new KoolbaseDataError(message, code);
|
|
143
|
+
}
|
package/dist/database.js
CHANGED
|
@@ -4,6 +4,7 @@ exports.KoolbaseDatabase = void 0;
|
|
|
4
4
|
const cache_store_1 = require("./cache-store");
|
|
5
5
|
const sync_engine_1 = require("./sync-engine");
|
|
6
6
|
const record_1 = require("./record");
|
|
7
|
+
const database_errors_1 = require("./database-errors");
|
|
7
8
|
function generateId() {
|
|
8
9
|
return 'local_' + Math.random().toString(36).slice(2) + Date.now().toString(36);
|
|
9
10
|
}
|
|
@@ -30,7 +31,7 @@ class KoolbaseDatabase {
|
|
|
30
31
|
});
|
|
31
32
|
const data = await res.json();
|
|
32
33
|
if (!res.ok) {
|
|
33
|
-
throw
|
|
34
|
+
throw (0, database_errors_1.koolbaseDataError)(res.status, data, `Request failed: ${res.status}`);
|
|
34
35
|
}
|
|
35
36
|
return data;
|
|
36
37
|
}
|
|
@@ -121,7 +122,7 @@ class KoolbaseDatabase {
|
|
|
121
122
|
});
|
|
122
123
|
const body = await res.json();
|
|
123
124
|
if (!res.ok) {
|
|
124
|
-
throw
|
|
125
|
+
throw (0, database_errors_1.koolbaseDataError)(res.status, body, `Upsert failed: ${res.status}`);
|
|
125
126
|
}
|
|
126
127
|
const created = res.status === 201;
|
|
127
128
|
const record = (0, record_1.recordFromWire)(body);
|
|
@@ -150,7 +151,7 @@ class KoolbaseDatabase {
|
|
|
150
151
|
});
|
|
151
152
|
const body = await res.json();
|
|
152
153
|
if (!res.ok) {
|
|
153
|
-
throw
|
|
154
|
+
throw (0, database_errors_1.koolbaseDataError)(res.status, body, `Delete failed: ${res.status}`);
|
|
154
155
|
}
|
|
155
156
|
const userId = this.getUserId() ?? 'anonymous';
|
|
156
157
|
await (0, cache_store_1.invalidateCache)(userId, collection);
|
package/dist/index.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ import { KoolbaseStorage } from './storage';
|
|
|
18
18
|
import { KoolbaseConfig, VersionCheckResult } from './types';
|
|
19
19
|
export * from './types';
|
|
20
20
|
export * from './auth-errors';
|
|
21
|
+
export * from './database-errors';
|
|
21
22
|
export { KoolbaseAuth, KoolbaseDatabase, KoolbaseFlags, KoolbaseFunctions, KoolbaseRealtime, KoolbaseStorage };
|
|
22
23
|
export declare const Koolbase: {
|
|
23
24
|
initialize(config: KoolbaseConfig): Promise<void>;
|
package/dist/index.js
CHANGED
|
@@ -45,6 +45,7 @@ const storage_1 = require("./storage");
|
|
|
45
45
|
Object.defineProperty(exports, "KoolbaseStorage", { enumerable: true, get: function () { return storage_1.KoolbaseStorage; } });
|
|
46
46
|
__exportStar(require("./types"), exports);
|
|
47
47
|
__exportStar(require("./auth-errors"), exports);
|
|
48
|
+
__exportStar(require("./database-errors"), exports);
|
|
48
49
|
let _auth = null;
|
|
49
50
|
let _db = null;
|
|
50
51
|
let _storage = null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@techfinityedge/koolbase-react-native",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "React Native SDK for Koolbase \u2014 auth, database, storage, realtime, feature flags, and functions in one package.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|