@koolbase/core 12.0.0 → 12.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/auth-errors.d.ts +8 -0
- package/dist/cjs/auth-errors.js +14 -1
- package/dist/cjs/auth.d.ts +31 -6
- package/dist/cjs/auth.js +54 -10
- package/dist/cjs/database-errors.d.ts +46 -0
- package/dist/cjs/database-errors.js +71 -1
- package/dist/cjs/fiscal.d.ts +114 -0
- package/dist/cjs/fiscal.js +167 -0
- package/dist/cjs/index.d.ts +2 -0
- package/dist/cjs/index.js +7 -1
- package/dist/esm/auth-errors.d.ts +8 -0
- package/dist/esm/auth-errors.js +12 -0
- package/dist/esm/auth.d.ts +31 -6
- package/dist/esm/auth.js +55 -11
- package/dist/esm/database-errors.d.ts +46 -0
- package/dist/esm/database-errors.js +66 -0
- package/dist/esm/fiscal.d.ts +114 -0
- package/dist/esm/fiscal.js +159 -0
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +1 -0
- package/package.json +1 -1
|
@@ -101,6 +101,14 @@ export declare class NetworkError extends KoolbaseAuthError {
|
|
|
101
101
|
export declare class InvalidPhoneNumberError extends KoolbaseAuthError {
|
|
102
102
|
constructor();
|
|
103
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* The project has switched off signing in with an emailed code — 403
|
|
106
|
+
* `email_code_disabled`. Applies to every address alike, so it reveals
|
|
107
|
+
* nothing about which accounts exist.
|
|
108
|
+
*/
|
|
109
|
+
export declare class EmailCodeDisabledError extends KoolbaseAuthError {
|
|
110
|
+
constructor();
|
|
111
|
+
}
|
|
104
112
|
export declare class OtpExpiredError extends KoolbaseAuthError {
|
|
105
113
|
constructor();
|
|
106
114
|
}
|
package/dist/cjs/auth-errors.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.GoogleEmailRequiredError = exports.InvalidGoogleTokenError = exports.MalformedSessionResponseError = exports.ProviderIdentityAlreadyLinkedError = exports.IdentityNotFoundError = exports.InsufficientScopeError = exports.HideRequiresVerificationError = exports.LastCredentialError = exports.InsufficientAuthorityError = exports.SessionRequiredError = exports.UnsupportedOAuthProviderError = exports.OAuthOnlyAccountError = exports.AccountExistsError = exports.CurrentPasswordIncorrectError = exports.TokenAlreadyUsedError = exports.TokenExpiredError = exports.SignupsDisabledError = exports.ContactNotVerifiedError = exports.GoogleSignInNotConfiguredError = exports.OAuthEmailConflictError = exports.AppleEmailRequiredError = exports.InvalidAppleTokenError = exports.AppleSignInNotConfiguredError = exports.SmsConfigMissingError = exports.PhoneAlreadyLinkedError = exports.OtpRateLimitError = exports.OtpMaxAttemptsError = exports.OtpInvalidError = exports.OtpExpiredError = exports.InvalidPhoneNumberError = exports.NetworkError = exports.VerificationResendDailyCapError = exports.VerificationResendCooldownError = exports.RateLimitError = exports.UnlockTokenInvalidError = exports.AccountLockedError = exports.TokenRevokedError = exports.SessionExpiredError = exports.WeakPasswordError = exports.UserDisabledError = exports.EmailAlreadyInUseError = exports.InvalidCredentialsError = exports.KoolbaseAuthError = void 0;
|
|
3
|
+
exports.GoogleEmailRequiredError = exports.InvalidGoogleTokenError = exports.MalformedSessionResponseError = exports.ProviderIdentityAlreadyLinkedError = exports.IdentityNotFoundError = exports.InsufficientScopeError = exports.HideRequiresVerificationError = exports.LastCredentialError = exports.InsufficientAuthorityError = exports.SessionRequiredError = exports.UnsupportedOAuthProviderError = exports.OAuthOnlyAccountError = exports.AccountExistsError = exports.CurrentPasswordIncorrectError = exports.TokenAlreadyUsedError = exports.TokenExpiredError = exports.SignupsDisabledError = exports.ContactNotVerifiedError = exports.GoogleSignInNotConfiguredError = exports.OAuthEmailConflictError = exports.AppleEmailRequiredError = exports.InvalidAppleTokenError = exports.AppleSignInNotConfiguredError = exports.SmsConfigMissingError = exports.PhoneAlreadyLinkedError = exports.OtpRateLimitError = exports.OtpMaxAttemptsError = exports.OtpInvalidError = exports.OtpExpiredError = exports.EmailCodeDisabledError = exports.InvalidPhoneNumberError = exports.NetworkError = exports.VerificationResendDailyCapError = exports.VerificationResendCooldownError = exports.RateLimitError = exports.UnlockTokenInvalidError = exports.AccountLockedError = exports.TokenRevokedError = exports.SessionExpiredError = exports.WeakPasswordError = exports.UserDisabledError = exports.EmailAlreadyInUseError = exports.InvalidCredentialsError = exports.KoolbaseAuthError = void 0;
|
|
4
4
|
const errors_js_1 = require("./errors.js");
|
|
5
5
|
/**
|
|
6
6
|
* Base error type for all Koolbase auth errors. Catchable via
|
|
@@ -179,6 +179,19 @@ class InvalidPhoneNumberError extends KoolbaseAuthError {
|
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
181
|
exports.InvalidPhoneNumberError = InvalidPhoneNumberError;
|
|
182
|
+
/**
|
|
183
|
+
* The project has switched off signing in with an emailed code — 403
|
|
184
|
+
* `email_code_disabled`. Applies to every address alike, so it reveals
|
|
185
|
+
* nothing about which accounts exist.
|
|
186
|
+
*/
|
|
187
|
+
class EmailCodeDisabledError extends KoolbaseAuthError {
|
|
188
|
+
constructor() {
|
|
189
|
+
super('Signing in with an emailed code is switched off for this project', 'email_code_disabled');
|
|
190
|
+
this.name = 'EmailCodeDisabledError';
|
|
191
|
+
Object.setPrototypeOf(this, EmailCodeDisabledError.prototype);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
exports.EmailCodeDisabledError = EmailCodeDisabledError;
|
|
182
195
|
class OtpExpiredError extends KoolbaseAuthError {
|
|
183
196
|
constructor() {
|
|
184
197
|
super('OTP has expired, please request a new code', 'otp_expired');
|
package/dist/cjs/auth.d.ts
CHANGED
|
@@ -302,12 +302,10 @@ export declare class KoolbaseAuth {
|
|
|
302
302
|
validAccessToken(): Promise<string | null>;
|
|
303
303
|
setSession(session: KoolbaseSession | null): Promise<void>;
|
|
304
304
|
/**
|
|
305
|
-
* @deprecated
|
|
306
|
-
*
|
|
307
|
-
*
|
|
308
|
-
*
|
|
309
|
-
* server endpoints under /v1/sdk/auth/oauth/{apple,google,github}.
|
|
310
|
-
* Use email/password sign-in for now.
|
|
305
|
+
* @deprecated Never implemented; kept only so existing code compiles.
|
|
306
|
+
* Google and Apple sign-in are supported — use signInWithGoogle() or
|
|
307
|
+
* signInWithApple(). This used to say OAuth was not yet shipped and to
|
|
308
|
+
* use email and password instead, which stopped being true long ago.
|
|
311
309
|
*
|
|
312
310
|
* @throws Always throws KoolbaseAuthError('not_implemented').
|
|
313
311
|
*/
|
|
@@ -318,6 +316,33 @@ export declare class KoolbaseAuth {
|
|
|
318
316
|
name?: string;
|
|
319
317
|
avatarUrl?: string;
|
|
320
318
|
}): Promise<never>;
|
|
319
|
+
/**
|
|
320
|
+
* Emails a six-digit sign-in code.
|
|
321
|
+
*
|
|
322
|
+
* Resolves the same way whether or not the address has an account, so it
|
|
323
|
+
* cannot be used to check who is registered. A new address receives a code
|
|
324
|
+
* only while the project accepts sign-ups; a disabled account receives
|
|
325
|
+
* nothing.
|
|
326
|
+
*
|
|
327
|
+
* @throws EmailCodeDisabledError if the project has switched this off
|
|
328
|
+
* @throws RateLimitError after too many requests for one address
|
|
329
|
+
*/
|
|
330
|
+
requestEmailCode(params: {
|
|
331
|
+
email: string;
|
|
332
|
+
}): Promise<void>;
|
|
333
|
+
/**
|
|
334
|
+
* Signs in with a code from requestEmailCode(), storing the session exactly
|
|
335
|
+
* as login() does. An unknown address becomes an account only if the
|
|
336
|
+
* project still accepts sign-ups at this moment. Each code works once and
|
|
337
|
+
* allows three attempts.
|
|
338
|
+
*
|
|
339
|
+
* @throws OtpInvalidError, OtpExpiredError, OtpMaxAttemptsError
|
|
340
|
+
* @throws EmailCodeDisabledError if the project has switched this off
|
|
341
|
+
*/
|
|
342
|
+
signInWithEmailCode(params: {
|
|
343
|
+
email: string;
|
|
344
|
+
code: string;
|
|
345
|
+
}): Promise<KoolbaseSession>;
|
|
321
346
|
sendOtp(params: SendOtpParams): Promise<OtpSendResult>;
|
|
322
347
|
verifyOtp(params: VerifyOtpParams): Promise<PhoneVerifyResult>;
|
|
323
348
|
linkPhone(params: LinkPhoneParams): Promise<void>;
|
package/dist/cjs/auth.js
CHANGED
|
@@ -824,20 +824,54 @@ class KoolbaseAuth {
|
|
|
824
824
|
}
|
|
825
825
|
// ─── OAuth (DEPRECATED — see v1.10.0) ───────────────────────────────────
|
|
826
826
|
/**
|
|
827
|
-
* @deprecated
|
|
828
|
-
*
|
|
829
|
-
*
|
|
830
|
-
*
|
|
831
|
-
* server endpoints under /v1/sdk/auth/oauth/{apple,google,github}.
|
|
832
|
-
* Use email/password sign-in for now.
|
|
827
|
+
* @deprecated Never implemented; kept only so existing code compiles.
|
|
828
|
+
* Google and Apple sign-in are supported — use signInWithGoogle() or
|
|
829
|
+
* signInWithApple(). This used to say OAuth was not yet shipped and to
|
|
830
|
+
* use email and password instead, which stopped being true long ago.
|
|
833
831
|
*
|
|
834
832
|
* @throws Always throws KoolbaseAuthError('not_implemented').
|
|
835
833
|
*/
|
|
836
834
|
async oauthLogin(_params) {
|
|
837
|
-
throw new auth_errors_js_1.KoolbaseAuthError('
|
|
838
|
-
'
|
|
839
|
-
|
|
840
|
-
|
|
835
|
+
throw new auth_errors_js_1.KoolbaseAuthError('oauthLogin() was never implemented and is kept only so existing ' +
|
|
836
|
+
'code compiles. Use signInWithGoogle() or signInWithApple().', 'not_implemented');
|
|
837
|
+
}
|
|
838
|
+
// ─── Email code ─────────────────────────────────────────────────────────
|
|
839
|
+
/**
|
|
840
|
+
* Emails a six-digit sign-in code.
|
|
841
|
+
*
|
|
842
|
+
* Resolves the same way whether or not the address has an account, so it
|
|
843
|
+
* cannot be used to check who is registered. A new address receives a code
|
|
844
|
+
* only while the project accepts sign-ups; a disabled account receives
|
|
845
|
+
* nothing.
|
|
846
|
+
*
|
|
847
|
+
* @throws EmailCodeDisabledError if the project has switched this off
|
|
848
|
+
* @throws RateLimitError after too many requests for one address
|
|
849
|
+
*/
|
|
850
|
+
async requestEmailCode(params) {
|
|
851
|
+
const res = await this.authRequest('/v1/sdk/auth/email/code', {
|
|
852
|
+
method: 'POST',
|
|
853
|
+
body: { email: params.email },
|
|
854
|
+
});
|
|
855
|
+
if (!res.ok)
|
|
856
|
+
await this.throwTypedError(res, false);
|
|
857
|
+
}
|
|
858
|
+
/**
|
|
859
|
+
* Signs in with a code from requestEmailCode(), storing the session exactly
|
|
860
|
+
* as login() does. An unknown address becomes an account only if the
|
|
861
|
+
* project still accepts sign-ups at this moment. Each code works once and
|
|
862
|
+
* allows three attempts.
|
|
863
|
+
*
|
|
864
|
+
* @throws OtpInvalidError, OtpExpiredError, OtpMaxAttemptsError
|
|
865
|
+
* @throws EmailCodeDisabledError if the project has switched this off
|
|
866
|
+
*/
|
|
867
|
+
async signInWithEmailCode(params) {
|
|
868
|
+
const res = await this.authRequest('/v1/sdk/auth/email/code/verify', {
|
|
869
|
+
method: 'POST',
|
|
870
|
+
body: { email: params.email, code: params.code },
|
|
871
|
+
});
|
|
872
|
+
const session = await this.parseSessionResponse(res, false);
|
|
873
|
+
await this.setSessionInternal(session);
|
|
874
|
+
return session;
|
|
841
875
|
}
|
|
842
876
|
// ─── Phone OTP ──────────────────────────────────────────────────────────
|
|
843
877
|
async sendOtp(params) {
|
|
@@ -1030,6 +1064,16 @@ class KoolbaseAuth {
|
|
|
1030
1064
|
case 'contact_not_verified':
|
|
1031
1065
|
case 'email_not_verified':
|
|
1032
1066
|
throw new auth_errors_js_1.ContactNotVerifiedError(msg || undefined);
|
|
1067
|
+
// Sign-in codes. The otp_* codes are shared with phone sign-in; mapped
|
|
1068
|
+
// here, every path that returns them throws the class an app catches.
|
|
1069
|
+
case 'email_code_disabled':
|
|
1070
|
+
throw new auth_errors_js_1.EmailCodeDisabledError();
|
|
1071
|
+
case 'otp_expired':
|
|
1072
|
+
throw new auth_errors_js_1.OtpExpiredError();
|
|
1073
|
+
case 'otp_invalid':
|
|
1074
|
+
throw new auth_errors_js_1.OtpInvalidError();
|
|
1075
|
+
case 'otp_max_attempts':
|
|
1076
|
+
throw new auth_errors_js_1.OtpMaxAttemptsError();
|
|
1033
1077
|
// Registration
|
|
1034
1078
|
case 'signups_disabled':
|
|
1035
1079
|
throw new auth_errors_js_1.SignupsDisabledError(msg || undefined);
|
|
@@ -195,6 +195,52 @@ export declare class KoolbaseConflictError extends KoolbaseDataError {
|
|
|
195
195
|
field?: string;
|
|
196
196
|
constructor(message?: string, field?: string);
|
|
197
197
|
}
|
|
198
|
+
/**
|
|
199
|
+
* A reference field points at a record that does not exist, is deleted, or
|
|
200
|
+
* lives in another collection — the server responds 400 `reference_invalid`.
|
|
201
|
+
*
|
|
202
|
+
* Checked when the write commits, so it can surface from an insert, an update,
|
|
203
|
+
* an upsert or a batch. In a batch the whole transaction is refused: nothing
|
|
204
|
+
* in it was written.
|
|
205
|
+
*/
|
|
206
|
+
export declare class KoolbaseReferenceInvalidError extends KoolbaseDataError {
|
|
207
|
+
constructor(message?: string);
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* A record could not be deleted because live records still reference it, under
|
|
211
|
+
* a reference declared `on_delete: restrict` — 409 `reference_in_use`.
|
|
212
|
+
*
|
|
213
|
+
* Delete the referencing records first, or in the same batch: the check runs at
|
|
214
|
+
* commit, so one batch may delete a parent and its children in any order.
|
|
215
|
+
*/
|
|
216
|
+
export declare class KoolbaseReferenceInUseError extends KoolbaseDataError {
|
|
217
|
+
constructor(message?: string);
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* A reference could not be declared because existing records already point at
|
|
221
|
+
* records that do not exist — 409 `dangling_references`.
|
|
222
|
+
*
|
|
223
|
+
* `dangling` lists the offending records so they can be repaired; Koolbase
|
|
224
|
+
* never repairs them for you. It is capped at the first 50.
|
|
225
|
+
*/
|
|
226
|
+
export declare class KoolbaseDanglingReferencesError extends KoolbaseDataError {
|
|
227
|
+
dangling: Array<{
|
|
228
|
+
record_id: string;
|
|
229
|
+
value: string;
|
|
230
|
+
}>;
|
|
231
|
+
constructor(message?: string, dangling?: Array<{
|
|
232
|
+
record_id: string;
|
|
233
|
+
value: string;
|
|
234
|
+
}>);
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* A collection could not be deleted because another collection has a reference
|
|
238
|
+
* field pointing at it — 409 `collection_referenced`. Remove that reference
|
|
239
|
+
* first.
|
|
240
|
+
*/
|
|
241
|
+
export declare class KoolbaseCollectionReferencedError extends KoolbaseDataError {
|
|
242
|
+
constructor(message?: string);
|
|
243
|
+
}
|
|
198
244
|
/**
|
|
199
245
|
* Thrown when the requested record or collection does not exist — the server
|
|
200
246
|
* responds with 404 and code `not_found` / `record_not_found` /
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.KoolbaseVectorDimensionMismatchError = exports.KoolbaseRateLimitError = exports.KoolbasePermissionError = exports.KoolbaseInsufficientAuthorityError = exports.KoolbaseValidationError = exports.KoolbaseNotFoundError = exports.KoolbaseConflictError = exports.KoolbaseConstraintNotFoundError = exports.KoolbaseConstraintExistsError = exports.KoolbaseAmbiguousMatchError = exports.KoolbaseDuplicateValuesError = exports.KoolbaseBatchFailedError = exports.KoolbaseIdempotencyKeyReusedError = exports.KoolbaseRevisionMismatchError = exports.KoolbaseProviderInvalidError = exports.KoolbaseProviderNotConfiguredError = exports.KoolbaseInvalidEmbeddingConfigError = exports.KoolbaseFieldNotAutoEmbedError = exports.KoolbaseVectorFieldExistsError = exports.KoolbaseStateConflictError = exports.KoolbaseNoChangesError = exports.KoolbaseInvalidBodyError = exports.KoolbaseProjectInvalidError = exports.KoolbaseInvitationInvalidError = exports.KoolbaseSlugTakenError = exports.KoolbaseSeedError = exports.KoolbaseDataError = void 0;
|
|
3
|
+
exports.KoolbaseVectorDimensionMismatchError = exports.KoolbaseRateLimitError = exports.KoolbasePermissionError = exports.KoolbaseInsufficientAuthorityError = exports.KoolbaseValidationError = exports.KoolbaseNotFoundError = exports.KoolbaseCollectionReferencedError = exports.KoolbaseDanglingReferencesError = exports.KoolbaseReferenceInUseError = exports.KoolbaseReferenceInvalidError = exports.KoolbaseConflictError = exports.KoolbaseConstraintNotFoundError = exports.KoolbaseConstraintExistsError = exports.KoolbaseAmbiguousMatchError = exports.KoolbaseDuplicateValuesError = exports.KoolbaseBatchFailedError = exports.KoolbaseIdempotencyKeyReusedError = exports.KoolbaseRevisionMismatchError = exports.KoolbaseProviderInvalidError = exports.KoolbaseProviderNotConfiguredError = exports.KoolbaseInvalidEmbeddingConfigError = exports.KoolbaseFieldNotAutoEmbedError = exports.KoolbaseVectorFieldExistsError = exports.KoolbaseStateConflictError = exports.KoolbaseNoChangesError = exports.KoolbaseInvalidBodyError = exports.KoolbaseProjectInvalidError = exports.KoolbaseInvitationInvalidError = exports.KoolbaseSlugTakenError = exports.KoolbaseSeedError = exports.KoolbaseDataError = void 0;
|
|
4
4
|
exports.koolbaseDataError = koolbaseDataError;
|
|
5
5
|
const errors_js_1 = require("./errors.js");
|
|
6
6
|
/**
|
|
@@ -297,6 +297,66 @@ class KoolbaseConflictError extends KoolbaseDataError {
|
|
|
297
297
|
}
|
|
298
298
|
}
|
|
299
299
|
exports.KoolbaseConflictError = KoolbaseConflictError;
|
|
300
|
+
/**
|
|
301
|
+
* A reference field points at a record that does not exist, is deleted, or
|
|
302
|
+
* lives in another collection — the server responds 400 `reference_invalid`.
|
|
303
|
+
*
|
|
304
|
+
* Checked when the write commits, so it can surface from an insert, an update,
|
|
305
|
+
* an upsert or a batch. In a batch the whole transaction is refused: nothing
|
|
306
|
+
* in it was written.
|
|
307
|
+
*/
|
|
308
|
+
class KoolbaseReferenceInvalidError extends KoolbaseDataError {
|
|
309
|
+
constructor(message) {
|
|
310
|
+
super(message ?? 'A reference points at a record that does not exist', 'reference_invalid');
|
|
311
|
+
this.name = 'KoolbaseReferenceInvalidError';
|
|
312
|
+
Object.setPrototypeOf(this, KoolbaseReferenceInvalidError.prototype);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
exports.KoolbaseReferenceInvalidError = KoolbaseReferenceInvalidError;
|
|
316
|
+
/**
|
|
317
|
+
* A record could not be deleted because live records still reference it, under
|
|
318
|
+
* a reference declared `on_delete: restrict` — 409 `reference_in_use`.
|
|
319
|
+
*
|
|
320
|
+
* Delete the referencing records first, or in the same batch: the check runs at
|
|
321
|
+
* commit, so one batch may delete a parent and its children in any order.
|
|
322
|
+
*/
|
|
323
|
+
class KoolbaseReferenceInUseError extends KoolbaseDataError {
|
|
324
|
+
constructor(message) {
|
|
325
|
+
super(message ?? 'This record is still referenced by other records', 'reference_in_use');
|
|
326
|
+
this.name = 'KoolbaseReferenceInUseError';
|
|
327
|
+
Object.setPrototypeOf(this, KoolbaseReferenceInUseError.prototype);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
exports.KoolbaseReferenceInUseError = KoolbaseReferenceInUseError;
|
|
331
|
+
/**
|
|
332
|
+
* A reference could not be declared because existing records already point at
|
|
333
|
+
* records that do not exist — 409 `dangling_references`.
|
|
334
|
+
*
|
|
335
|
+
* `dangling` lists the offending records so they can be repaired; Koolbase
|
|
336
|
+
* never repairs them for you. It is capped at the first 50.
|
|
337
|
+
*/
|
|
338
|
+
class KoolbaseDanglingReferencesError extends KoolbaseDataError {
|
|
339
|
+
constructor(message, dangling) {
|
|
340
|
+
super(message ?? 'Existing records point at records that do not exist', 'dangling_references');
|
|
341
|
+
this.dangling = dangling ?? [];
|
|
342
|
+
this.name = 'KoolbaseDanglingReferencesError';
|
|
343
|
+
Object.setPrototypeOf(this, KoolbaseDanglingReferencesError.prototype);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
exports.KoolbaseDanglingReferencesError = KoolbaseDanglingReferencesError;
|
|
347
|
+
/**
|
|
348
|
+
* A collection could not be deleted because another collection has a reference
|
|
349
|
+
* field pointing at it — 409 `collection_referenced`. Remove that reference
|
|
350
|
+
* first.
|
|
351
|
+
*/
|
|
352
|
+
class KoolbaseCollectionReferencedError extends KoolbaseDataError {
|
|
353
|
+
constructor(message) {
|
|
354
|
+
super(message ?? 'Another collection references this one', 'collection_referenced');
|
|
355
|
+
this.name = 'KoolbaseCollectionReferencedError';
|
|
356
|
+
Object.setPrototypeOf(this, KoolbaseCollectionReferencedError.prototype);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
exports.KoolbaseCollectionReferencedError = KoolbaseCollectionReferencedError;
|
|
300
360
|
/**
|
|
301
361
|
* Thrown when the requested record or collection does not exist — the server
|
|
302
362
|
* responds with 404 and code `not_found` / `record_not_found` /
|
|
@@ -427,6 +487,16 @@ function koolbaseDataError(status, body, fallbackMessage = 'Request failed') {
|
|
|
427
487
|
switch (code) {
|
|
428
488
|
case 'unique_violation':
|
|
429
489
|
return attach(new KoolbaseConflictError(message, field));
|
|
490
|
+
case 'reference_invalid':
|
|
491
|
+
return attach(new KoolbaseReferenceInvalidError(message));
|
|
492
|
+
case 'reference_in_use':
|
|
493
|
+
return attach(new KoolbaseReferenceInUseError(message));
|
|
494
|
+
case 'dangling_references': {
|
|
495
|
+
const d = (body?.details ?? {});
|
|
496
|
+
return attach(new KoolbaseDanglingReferencesError(message, d.dangling));
|
|
497
|
+
}
|
|
498
|
+
case 'collection_referenced':
|
|
499
|
+
return attach(new KoolbaseCollectionReferencedError(message));
|
|
430
500
|
case 'plan_limit_reached': {
|
|
431
501
|
const d = (body?.details ?? {});
|
|
432
502
|
return new errors_js_1.KoolbasePlanLimitError(message, d.resource, d.limit, d.plan);
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { KoolbaseConfig } from './types.js';
|
|
2
|
+
import { KoolbaseError } from './errors.js';
|
|
3
|
+
/**
|
|
4
|
+
* The lifecycle status of a fiscal intent.
|
|
5
|
+
*
|
|
6
|
+
* A sale is durably recorded the moment `submit` returns; fiscal numbers and
|
|
7
|
+
* the authority's certification follow this state machine:
|
|
8
|
+
*
|
|
9
|
+
* - `queued` / `submitting` / `retrying` — sealed with allocated numbers;
|
|
10
|
+
* delivery to the authority is in progress.
|
|
11
|
+
* - `fiscalized` — the authority answered; for certifying document kinds the
|
|
12
|
+
* `certification` is present.
|
|
13
|
+
* - `blocked` — a precondition failed BEFORE any fiscal number was consumed
|
|
14
|
+
* (see `blockedReason`). Resubmitting the same `clientRef` after fixing the
|
|
15
|
+
* cause resumes it.
|
|
16
|
+
* - `attention` — the authority rejected the document, or its credential died
|
|
17
|
+
* after sealing. An operator resolves it; the register's chain waits behind
|
|
18
|
+
* it.
|
|
19
|
+
* - `voided` — terminally closed by an operator without fiscalization.
|
|
20
|
+
*
|
|
21
|
+
* `unknown` is the forward-compatible fallback: a status this SDK version has
|
|
22
|
+
* not heard of parses as unknown rather than throwing, so a server that grows
|
|
23
|
+
* a state does not break older clients.
|
|
24
|
+
*/
|
|
25
|
+
export type FiscalStatus = 'created' | 'blocked' | 'queued' | 'submitting' | 'retrying' | 'fiscalized' | 'attention' | 'voided' | 'unknown';
|
|
26
|
+
/**
|
|
27
|
+
* The state of one fiscal intent.
|
|
28
|
+
*
|
|
29
|
+
* Jurisdiction-neutral by design: this SDK never learns a jurisdiction's
|
|
30
|
+
* vocabulary. Payloads travel as objects shaped by the device's adapter, and
|
|
31
|
+
* `certification` comes back as the authority granted it — for Ghana that
|
|
32
|
+
* means `ysdcregsig`, `ysdcrecnum`, `qr_code` and so on, which the SDK
|
|
33
|
+
* passes through without naming.
|
|
34
|
+
*/
|
|
35
|
+
export interface FiscalIntentResult {
|
|
36
|
+
intentId: string;
|
|
37
|
+
status: FiscalStatus;
|
|
38
|
+
/** Why a `blocked` intent stopped, when the server said. */
|
|
39
|
+
blockedReason?: string;
|
|
40
|
+
/** Fiscal numbers allocated at sealing. */
|
|
41
|
+
numbers?: Record<string, number>;
|
|
42
|
+
/** When the intent was sealed and its numbers allocated. */
|
|
43
|
+
sealedAt?: string;
|
|
44
|
+
/**
|
|
45
|
+
* What the authority granted, once `status` is `fiscalized`. This is the
|
|
46
|
+
* receipt-rendering payload: for Ghana, the signature, receipt number and
|
|
47
|
+
* QR verification URL.
|
|
48
|
+
*/
|
|
49
|
+
certification?: Record<string, unknown>;
|
|
50
|
+
}
|
|
51
|
+
/** True once the authority has certified the document. */
|
|
52
|
+
export declare function isFiscalized(r: FiscalIntentResult): boolean;
|
|
53
|
+
/** True while delivery to the authority is still in progress. */
|
|
54
|
+
export declare function isPending(r: FiscalIntentResult): boolean;
|
|
55
|
+
/** A fiscal request that failed. Carries the HTTP status where there was one. */
|
|
56
|
+
export declare class KoolbaseFiscalError extends KoolbaseError {
|
|
57
|
+
readonly statusCode?: number;
|
|
58
|
+
constructor(message: string, statusCode?: number);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Classify a response. Status and body in, result or error out.
|
|
62
|
+
*
|
|
63
|
+
* Exported for testability, and because auth handling belongs with the
|
|
64
|
+
* session hook rather than here — the same split the Flutter SDK makes.
|
|
65
|
+
*/
|
|
66
|
+
export declare function decodeFiscalResponse(status: number, body: string): Promise<FiscalIntentResult>;
|
|
67
|
+
/**
|
|
68
|
+
* Koolbase Fiscal — submitting sales to a tax authority.
|
|
69
|
+
*
|
|
70
|
+
* Jurisdiction-neutral: the SDK moves payloads and reads back whatever the
|
|
71
|
+
* authority certified. Ghana's GRA E-VAT is the first jurisdiction; nothing
|
|
72
|
+
* here names it.
|
|
73
|
+
*/
|
|
74
|
+
export declare class KoolbaseFiscal {
|
|
75
|
+
private config;
|
|
76
|
+
private getToken;
|
|
77
|
+
private onSessionExpired?;
|
|
78
|
+
constructor(config: KoolbaseConfig, getToken: () => Promise<string | null>, onSessionExpired?: () => Promise<void>);
|
|
79
|
+
private buildHeaders;
|
|
80
|
+
private decode;
|
|
81
|
+
/**
|
|
82
|
+
* Submit a sale for fiscalization.
|
|
83
|
+
*
|
|
84
|
+
* The sale is durably recorded the moment this returns; fiscal numbers and
|
|
85
|
+
* the authority's certification follow asynchronously — poll `status()`
|
|
86
|
+
* with the same `clientRef` to watch it.
|
|
87
|
+
*
|
|
88
|
+
* **On timeout, do not resubmit blindly.** The sale may have fiscalized
|
|
89
|
+
* anyway: the request reached the server and the response was lost. Poll
|
|
90
|
+
* `status()` with the same `clientRef` instead, which is idempotent and
|
|
91
|
+
* tells you what actually happened.
|
|
92
|
+
*
|
|
93
|
+
* @param clientRef your own reference for this sale; the idempotency key
|
|
94
|
+
* and the handle you poll and render receipts by.
|
|
95
|
+
*/
|
|
96
|
+
submit(params: {
|
|
97
|
+
deviceId: string;
|
|
98
|
+
clientRef: string;
|
|
99
|
+
payload: Record<string, unknown>;
|
|
100
|
+
timeoutMs?: number;
|
|
101
|
+
}): Promise<FiscalIntentResult>;
|
|
102
|
+
/**
|
|
103
|
+
* Read an intent's current state by the reference it was submitted under.
|
|
104
|
+
*
|
|
105
|
+
* This is the receipt-rendering read: once the status is `fiscalized`, the
|
|
106
|
+
* `certification` carries what the receipt must show — for Ghana, the
|
|
107
|
+
* signature, receipt number and QR verification URL.
|
|
108
|
+
*/
|
|
109
|
+
status(params: {
|
|
110
|
+
deviceId: string;
|
|
111
|
+
clientRef: string;
|
|
112
|
+
timeoutMs?: number;
|
|
113
|
+
}): Promise<FiscalIntentResult>;
|
|
114
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.KoolbaseFiscal = exports.KoolbaseFiscalError = void 0;
|
|
4
|
+
exports.isFiscalized = isFiscalized;
|
|
5
|
+
exports.isPending = isPending;
|
|
6
|
+
exports.decodeFiscalResponse = decodeFiscalResponse;
|
|
7
|
+
const errors_js_1 = require("./errors.js");
|
|
8
|
+
const KNOWN_STATUSES = [
|
|
9
|
+
'created', 'blocked', 'queued', 'submitting',
|
|
10
|
+
'retrying', 'fiscalized', 'attention', 'voided',
|
|
11
|
+
];
|
|
12
|
+
function parseStatus(raw) {
|
|
13
|
+
return typeof raw === 'string' && KNOWN_STATUSES.includes(raw)
|
|
14
|
+
? raw
|
|
15
|
+
: 'unknown';
|
|
16
|
+
}
|
|
17
|
+
/** True once the authority has certified the document. */
|
|
18
|
+
function isFiscalized(r) {
|
|
19
|
+
return r.status === 'fiscalized';
|
|
20
|
+
}
|
|
21
|
+
/** True while delivery to the authority is still in progress. */
|
|
22
|
+
function isPending(r) {
|
|
23
|
+
return r.status === 'queued'
|
|
24
|
+
|| r.status === 'submitting'
|
|
25
|
+
|| r.status === 'retrying';
|
|
26
|
+
}
|
|
27
|
+
/** A fiscal request that failed. Carries the HTTP status where there was one. */
|
|
28
|
+
class KoolbaseFiscalError extends errors_js_1.KoolbaseError {
|
|
29
|
+
constructor(message, statusCode) {
|
|
30
|
+
super(message, 'fiscal_failed');
|
|
31
|
+
this.statusCode = statusCode;
|
|
32
|
+
this.name = 'KoolbaseFiscalError';
|
|
33
|
+
Object.setPrototypeOf(this, KoolbaseFiscalError.prototype);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
exports.KoolbaseFiscalError = KoolbaseFiscalError;
|
|
37
|
+
function resultFromJson(json) {
|
|
38
|
+
const numbers = json.numbers;
|
|
39
|
+
return {
|
|
40
|
+
intentId: String(json.intent_id ?? ''),
|
|
41
|
+
status: parseStatus(json.status),
|
|
42
|
+
blockedReason: json.blocked_reason,
|
|
43
|
+
numbers: numbers ?? undefined,
|
|
44
|
+
sealedAt: json.sealed_at,
|
|
45
|
+
certification: json.certification,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Classify a response. Status and body in, result or error out.
|
|
50
|
+
*
|
|
51
|
+
* Exported for testability, and because auth handling belongs with the
|
|
52
|
+
* session hook rather than here — the same split the Flutter SDK makes.
|
|
53
|
+
*/
|
|
54
|
+
async function decodeFiscalResponse(status, body) {
|
|
55
|
+
let parsed;
|
|
56
|
+
try {
|
|
57
|
+
parsed = JSON.parse(body);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
throw new KoolbaseFiscalError(`fiscal: unreadable response (${status})`, status);
|
|
61
|
+
}
|
|
62
|
+
if ((status >= 200 && status < 300) || status === 202) {
|
|
63
|
+
return resultFromJson(parsed);
|
|
64
|
+
}
|
|
65
|
+
throw new KoolbaseFiscalError(parsed.error ?? 'fiscal: request failed', status);
|
|
66
|
+
}
|
|
67
|
+
// ─── KoolbaseFiscal ──────────────────────────────────────────────────────────
|
|
68
|
+
/**
|
|
69
|
+
* Koolbase Fiscal — submitting sales to a tax authority.
|
|
70
|
+
*
|
|
71
|
+
* Jurisdiction-neutral: the SDK moves payloads and reads back whatever the
|
|
72
|
+
* authority certified. Ghana's GRA E-VAT is the first jurisdiction; nothing
|
|
73
|
+
* here names it.
|
|
74
|
+
*/
|
|
75
|
+
class KoolbaseFiscal {
|
|
76
|
+
constructor(config, getToken, onSessionExpired) {
|
|
77
|
+
this.config = config;
|
|
78
|
+
this.getToken = getToken;
|
|
79
|
+
this.onSessionExpired = onSessionExpired;
|
|
80
|
+
}
|
|
81
|
+
async buildHeaders() {
|
|
82
|
+
const token = await this.getToken();
|
|
83
|
+
return {
|
|
84
|
+
'Content-Type': 'application/json',
|
|
85
|
+
'x-api-key': this.config.publicKey,
|
|
86
|
+
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
async decode(res) {
|
|
90
|
+
if (res.status === 401 || res.status === 403) {
|
|
91
|
+
await this.onSessionExpired?.();
|
|
92
|
+
throw new errors_js_1.KoolbaseUnauthenticatedError(`fiscal: unauthenticated (${res.status})`);
|
|
93
|
+
}
|
|
94
|
+
return decodeFiscalResponse(res.status, await res.text());
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Submit a sale for fiscalization.
|
|
98
|
+
*
|
|
99
|
+
* The sale is durably recorded the moment this returns; fiscal numbers and
|
|
100
|
+
* the authority's certification follow asynchronously — poll `status()`
|
|
101
|
+
* with the same `clientRef` to watch it.
|
|
102
|
+
*
|
|
103
|
+
* **On timeout, do not resubmit blindly.** The sale may have fiscalized
|
|
104
|
+
* anyway: the request reached the server and the response was lost. Poll
|
|
105
|
+
* `status()` with the same `clientRef` instead, which is idempotent and
|
|
106
|
+
* tells you what actually happened.
|
|
107
|
+
*
|
|
108
|
+
* @param clientRef your own reference for this sale; the idempotency key
|
|
109
|
+
* and the handle you poll and render receipts by.
|
|
110
|
+
*/
|
|
111
|
+
async submit(params) {
|
|
112
|
+
const controller = new AbortController();
|
|
113
|
+
const timer = setTimeout(() => controller.abort(), params.timeoutMs ?? 30000);
|
|
114
|
+
try {
|
|
115
|
+
const res = await fetch(`${this.config.baseUrl}/v1/sdk/fiscal/submit`, {
|
|
116
|
+
method: 'POST',
|
|
117
|
+
headers: await this.buildHeaders(),
|
|
118
|
+
signal: controller.signal,
|
|
119
|
+
body: JSON.stringify({
|
|
120
|
+
device_id: params.deviceId,
|
|
121
|
+
client_ref: params.clientRef,
|
|
122
|
+
payload: params.payload,
|
|
123
|
+
}),
|
|
124
|
+
});
|
|
125
|
+
return await this.decode(res);
|
|
126
|
+
}
|
|
127
|
+
catch (e) {
|
|
128
|
+
if (e instanceof Error && e.name === 'AbortError') {
|
|
129
|
+
throw new KoolbaseFiscalError('fiscal submit timed out — the sale may still fiscalize; '
|
|
130
|
+
+ 'poll status with the same clientRef');
|
|
131
|
+
}
|
|
132
|
+
throw e;
|
|
133
|
+
}
|
|
134
|
+
finally {
|
|
135
|
+
clearTimeout(timer);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Read an intent's current state by the reference it was submitted under.
|
|
140
|
+
*
|
|
141
|
+
* This is the receipt-rendering read: once the status is `fiscalized`, the
|
|
142
|
+
* `certification` carries what the receipt must show — for Ghana, the
|
|
143
|
+
* signature, receipt number and QR verification URL.
|
|
144
|
+
*/
|
|
145
|
+
async status(params) {
|
|
146
|
+
const controller = new AbortController();
|
|
147
|
+
const timer = setTimeout(() => controller.abort(), params.timeoutMs ?? 15000);
|
|
148
|
+
try {
|
|
149
|
+
const q = new URLSearchParams({
|
|
150
|
+
device_id: params.deviceId,
|
|
151
|
+
client_ref: params.clientRef,
|
|
152
|
+
});
|
|
153
|
+
const res = await fetch(`${this.config.baseUrl}/v1/sdk/fiscal/status?${q.toString()}`, { method: 'GET', headers: await this.buildHeaders(), signal: controller.signal });
|
|
154
|
+
return await this.decode(res);
|
|
155
|
+
}
|
|
156
|
+
catch (e) {
|
|
157
|
+
if (e instanceof Error && e.name === 'AbortError') {
|
|
158
|
+
throw new KoolbaseFiscalError('fiscal status timed out');
|
|
159
|
+
}
|
|
160
|
+
throw e;
|
|
161
|
+
}
|
|
162
|
+
finally {
|
|
163
|
+
clearTimeout(timer);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
exports.KoolbaseFiscal = KoolbaseFiscal;
|
package/dist/cjs/index.d.ts
CHANGED
|
@@ -14,6 +14,8 @@ export { KoolbaseFunctions } from './functions.js';
|
|
|
14
14
|
export { KoolbaseRealtime } from './realtime.js';
|
|
15
15
|
export { KoolbaseStorage } from './storage.js';
|
|
16
16
|
export { KoolbaseAnalytics } from './analytics.js';
|
|
17
|
+
export { KoolbaseFiscal, KoolbaseFiscalError, decodeFiscalResponse, isFiscalized, isPending } from './fiscal.js';
|
|
18
|
+
export type { FiscalIntentResult, FiscalStatus } from './fiscal.js';
|
|
17
19
|
export { KoolbaseMessaging } from './messaging.js';
|
|
18
20
|
export type { RegisterTokenOptions } from './messaging.js';
|
|
19
21
|
export { getOrCreateDeviceId } from './device-id.js';
|
package/dist/cjs/index.js
CHANGED
|
@@ -19,7 +19,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
19
19
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
20
20
|
};
|
|
21
21
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
-
exports.memoryPlatform = exports.getPlatform = exports.setPlatform = exports.RestoreResult = exports.koolbaseSdkVersion = exports.getOrCreateDeviceId = exports.KoolbaseMessaging = exports.KoolbaseAnalytics = exports.KoolbaseStorage = exports.KoolbaseRealtime = exports.KoolbaseFunctions = exports.KoolbaseFlags = exports.KoolbaseDatabase = exports.KoolbaseAuth = void 0;
|
|
22
|
+
exports.memoryPlatform = exports.getPlatform = exports.setPlatform = exports.RestoreResult = exports.koolbaseSdkVersion = exports.getOrCreateDeviceId = exports.KoolbaseMessaging = exports.isPending = exports.isFiscalized = exports.decodeFiscalResponse = exports.KoolbaseFiscalError = exports.KoolbaseFiscal = exports.KoolbaseAnalytics = exports.KoolbaseStorage = exports.KoolbaseRealtime = exports.KoolbaseFunctions = exports.KoolbaseFlags = exports.KoolbaseDatabase = exports.KoolbaseAuth = void 0;
|
|
23
23
|
__exportStar(require("./types.js"), exports);
|
|
24
24
|
__exportStar(require("./errors.js"), exports);
|
|
25
25
|
__exportStar(require("./aggregate.js"), exports);
|
|
@@ -43,6 +43,12 @@ var storage_js_1 = require("./storage.js");
|
|
|
43
43
|
Object.defineProperty(exports, "KoolbaseStorage", { enumerable: true, get: function () { return storage_js_1.KoolbaseStorage; } });
|
|
44
44
|
var analytics_js_1 = require("./analytics.js");
|
|
45
45
|
Object.defineProperty(exports, "KoolbaseAnalytics", { enumerable: true, get: function () { return analytics_js_1.KoolbaseAnalytics; } });
|
|
46
|
+
var fiscal_js_1 = require("./fiscal.js");
|
|
47
|
+
Object.defineProperty(exports, "KoolbaseFiscal", { enumerable: true, get: function () { return fiscal_js_1.KoolbaseFiscal; } });
|
|
48
|
+
Object.defineProperty(exports, "KoolbaseFiscalError", { enumerable: true, get: function () { return fiscal_js_1.KoolbaseFiscalError; } });
|
|
49
|
+
Object.defineProperty(exports, "decodeFiscalResponse", { enumerable: true, get: function () { return fiscal_js_1.decodeFiscalResponse; } });
|
|
50
|
+
Object.defineProperty(exports, "isFiscalized", { enumerable: true, get: function () { return fiscal_js_1.isFiscalized; } });
|
|
51
|
+
Object.defineProperty(exports, "isPending", { enumerable: true, get: function () { return fiscal_js_1.isPending; } });
|
|
46
52
|
var messaging_js_1 = require("./messaging.js");
|
|
47
53
|
Object.defineProperty(exports, "KoolbaseMessaging", { enumerable: true, get: function () { return messaging_js_1.KoolbaseMessaging; } });
|
|
48
54
|
var device_id_js_1 = require("./device-id.js");
|
|
@@ -101,6 +101,14 @@ export declare class NetworkError extends KoolbaseAuthError {
|
|
|
101
101
|
export declare class InvalidPhoneNumberError extends KoolbaseAuthError {
|
|
102
102
|
constructor();
|
|
103
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* The project has switched off signing in with an emailed code — 403
|
|
106
|
+
* `email_code_disabled`. Applies to every address alike, so it reveals
|
|
107
|
+
* nothing about which accounts exist.
|
|
108
|
+
*/
|
|
109
|
+
export declare class EmailCodeDisabledError extends KoolbaseAuthError {
|
|
110
|
+
constructor();
|
|
111
|
+
}
|
|
104
112
|
export declare class OtpExpiredError extends KoolbaseAuthError {
|
|
105
113
|
constructor();
|
|
106
114
|
}
|
package/dist/esm/auth-errors.js
CHANGED
|
@@ -162,6 +162,18 @@ export class InvalidPhoneNumberError extends KoolbaseAuthError {
|
|
|
162
162
|
Object.setPrototypeOf(this, InvalidPhoneNumberError.prototype);
|
|
163
163
|
}
|
|
164
164
|
}
|
|
165
|
+
/**
|
|
166
|
+
* The project has switched off signing in with an emailed code — 403
|
|
167
|
+
* `email_code_disabled`. Applies to every address alike, so it reveals
|
|
168
|
+
* nothing about which accounts exist.
|
|
169
|
+
*/
|
|
170
|
+
export class EmailCodeDisabledError extends KoolbaseAuthError {
|
|
171
|
+
constructor() {
|
|
172
|
+
super('Signing in with an emailed code is switched off for this project', 'email_code_disabled');
|
|
173
|
+
this.name = 'EmailCodeDisabledError';
|
|
174
|
+
Object.setPrototypeOf(this, EmailCodeDisabledError.prototype);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
165
177
|
export class OtpExpiredError extends KoolbaseAuthError {
|
|
166
178
|
constructor() {
|
|
167
179
|
super('OTP has expired, please request a new code', 'otp_expired');
|
package/dist/esm/auth.d.ts
CHANGED
|
@@ -302,12 +302,10 @@ export declare class KoolbaseAuth {
|
|
|
302
302
|
validAccessToken(): Promise<string | null>;
|
|
303
303
|
setSession(session: KoolbaseSession | null): Promise<void>;
|
|
304
304
|
/**
|
|
305
|
-
* @deprecated
|
|
306
|
-
*
|
|
307
|
-
*
|
|
308
|
-
*
|
|
309
|
-
* server endpoints under /v1/sdk/auth/oauth/{apple,google,github}.
|
|
310
|
-
* Use email/password sign-in for now.
|
|
305
|
+
* @deprecated Never implemented; kept only so existing code compiles.
|
|
306
|
+
* Google and Apple sign-in are supported — use signInWithGoogle() or
|
|
307
|
+
* signInWithApple(). This used to say OAuth was not yet shipped and to
|
|
308
|
+
* use email and password instead, which stopped being true long ago.
|
|
311
309
|
*
|
|
312
310
|
* @throws Always throws KoolbaseAuthError('not_implemented').
|
|
313
311
|
*/
|
|
@@ -318,6 +316,33 @@ export declare class KoolbaseAuth {
|
|
|
318
316
|
name?: string;
|
|
319
317
|
avatarUrl?: string;
|
|
320
318
|
}): Promise<never>;
|
|
319
|
+
/**
|
|
320
|
+
* Emails a six-digit sign-in code.
|
|
321
|
+
*
|
|
322
|
+
* Resolves the same way whether or not the address has an account, so it
|
|
323
|
+
* cannot be used to check who is registered. A new address receives a code
|
|
324
|
+
* only while the project accepts sign-ups; a disabled account receives
|
|
325
|
+
* nothing.
|
|
326
|
+
*
|
|
327
|
+
* @throws EmailCodeDisabledError if the project has switched this off
|
|
328
|
+
* @throws RateLimitError after too many requests for one address
|
|
329
|
+
*/
|
|
330
|
+
requestEmailCode(params: {
|
|
331
|
+
email: string;
|
|
332
|
+
}): Promise<void>;
|
|
333
|
+
/**
|
|
334
|
+
* Signs in with a code from requestEmailCode(), storing the session exactly
|
|
335
|
+
* as login() does. An unknown address becomes an account only if the
|
|
336
|
+
* project still accepts sign-ups at this moment. Each code works once and
|
|
337
|
+
* allows three attempts.
|
|
338
|
+
*
|
|
339
|
+
* @throws OtpInvalidError, OtpExpiredError, OtpMaxAttemptsError
|
|
340
|
+
* @throws EmailCodeDisabledError if the project has switched this off
|
|
341
|
+
*/
|
|
342
|
+
signInWithEmailCode(params: {
|
|
343
|
+
email: string;
|
|
344
|
+
code: string;
|
|
345
|
+
}): Promise<KoolbaseSession>;
|
|
321
346
|
sendOtp(params: SendOtpParams): Promise<OtpSendResult>;
|
|
322
347
|
verifyOtp(params: VerifyOtpParams): Promise<PhoneVerifyResult>;
|
|
323
348
|
linkPhone(params: LinkPhoneParams): Promise<void>;
|
package/dist/esm/auth.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { RestoreResult, } from './types.js';
|
|
2
|
-
import { AccountLockedError, EmailAlreadyInUseError, InvalidCredentialsError, InvalidPhoneNumberError, KoolbaseAuthError, OtpExpiredError, OtpInvalidError, OtpMaxAttemptsError, OtpRateLimitError, PhoneAlreadyLinkedError, IdentityNotFoundError, InsufficientScopeError, MalformedSessionResponseError, ProviderIdentityAlreadyLinkedError, AccountExistsError, ContactNotVerifiedError, CurrentPasswordIncorrectError, HideRequiresVerificationError, InsufficientAuthorityError, LastCredentialError, OAuthOnlyAccountError, SessionRequiredError, SignupsDisabledError, TokenAlreadyUsedError, TokenExpiredError, UnsupportedOAuthProviderError, RateLimitError, VerificationResendCooldownError, VerificationResendDailyCapError, SessionExpiredError, SmsConfigMissingError, UnlockTokenInvalidError, UserDisabledError, WeakPasswordError, AppleEmailRequiredError, AppleSignInNotConfiguredError, InvalidAppleTokenError, OAuthEmailConflictError, GoogleEmailRequiredError, GoogleSignInNotConfiguredError, InvalidGoogleTokenError, } from './auth-errors.js';
|
|
2
|
+
import { AccountLockedError, EmailAlreadyInUseError, InvalidCredentialsError, InvalidPhoneNumberError, KoolbaseAuthError, OtpExpiredError, OtpInvalidError, OtpMaxAttemptsError, OtpRateLimitError, PhoneAlreadyLinkedError, IdentityNotFoundError, InsufficientScopeError, MalformedSessionResponseError, ProviderIdentityAlreadyLinkedError, AccountExistsError, ContactNotVerifiedError, CurrentPasswordIncorrectError, HideRequiresVerificationError, InsufficientAuthorityError, LastCredentialError, OAuthOnlyAccountError, SessionRequiredError, SignupsDisabledError, TokenAlreadyUsedError, TokenExpiredError, UnsupportedOAuthProviderError, RateLimitError, VerificationResendCooldownError, VerificationResendDailyCapError, SessionExpiredError, SmsConfigMissingError, UnlockTokenInvalidError, UserDisabledError, WeakPasswordError, AppleEmailRequiredError, AppleSignInNotConfiguredError, InvalidAppleTokenError, OAuthEmailConflictError, GoogleEmailRequiredError, GoogleSignInNotConfiguredError, InvalidGoogleTokenError, EmailCodeDisabledError, } from './auth-errors.js';
|
|
3
3
|
import { getPlatform } from './platform.js';
|
|
4
4
|
import { DeviceMetadata } from './device-metadata.js';
|
|
5
5
|
export class KoolbaseAuth {
|
|
@@ -821,20 +821,54 @@ export class KoolbaseAuth {
|
|
|
821
821
|
}
|
|
822
822
|
// ─── OAuth (DEPRECATED — see v1.10.0) ───────────────────────────────────
|
|
823
823
|
/**
|
|
824
|
-
* @deprecated
|
|
825
|
-
*
|
|
826
|
-
*
|
|
827
|
-
*
|
|
828
|
-
* server endpoints under /v1/sdk/auth/oauth/{apple,google,github}.
|
|
829
|
-
* Use email/password sign-in for now.
|
|
824
|
+
* @deprecated Never implemented; kept only so existing code compiles.
|
|
825
|
+
* Google and Apple sign-in are supported — use signInWithGoogle() or
|
|
826
|
+
* signInWithApple(). This used to say OAuth was not yet shipped and to
|
|
827
|
+
* use email and password instead, which stopped being true long ago.
|
|
830
828
|
*
|
|
831
829
|
* @throws Always throws KoolbaseAuthError('not_implemented').
|
|
832
830
|
*/
|
|
833
831
|
async oauthLogin(_params) {
|
|
834
|
-
throw new KoolbaseAuthError('
|
|
835
|
-
'
|
|
836
|
-
|
|
837
|
-
|
|
832
|
+
throw new KoolbaseAuthError('oauthLogin() was never implemented and is kept only so existing ' +
|
|
833
|
+
'code compiles. Use signInWithGoogle() or signInWithApple().', 'not_implemented');
|
|
834
|
+
}
|
|
835
|
+
// ─── Email code ─────────────────────────────────────────────────────────
|
|
836
|
+
/**
|
|
837
|
+
* Emails a six-digit sign-in code.
|
|
838
|
+
*
|
|
839
|
+
* Resolves the same way whether or not the address has an account, so it
|
|
840
|
+
* cannot be used to check who is registered. A new address receives a code
|
|
841
|
+
* only while the project accepts sign-ups; a disabled account receives
|
|
842
|
+
* nothing.
|
|
843
|
+
*
|
|
844
|
+
* @throws EmailCodeDisabledError if the project has switched this off
|
|
845
|
+
* @throws RateLimitError after too many requests for one address
|
|
846
|
+
*/
|
|
847
|
+
async requestEmailCode(params) {
|
|
848
|
+
const res = await this.authRequest('/v1/sdk/auth/email/code', {
|
|
849
|
+
method: 'POST',
|
|
850
|
+
body: { email: params.email },
|
|
851
|
+
});
|
|
852
|
+
if (!res.ok)
|
|
853
|
+
await this.throwTypedError(res, false);
|
|
854
|
+
}
|
|
855
|
+
/**
|
|
856
|
+
* Signs in with a code from requestEmailCode(), storing the session exactly
|
|
857
|
+
* as login() does. An unknown address becomes an account only if the
|
|
858
|
+
* project still accepts sign-ups at this moment. Each code works once and
|
|
859
|
+
* allows three attempts.
|
|
860
|
+
*
|
|
861
|
+
* @throws OtpInvalidError, OtpExpiredError, OtpMaxAttemptsError
|
|
862
|
+
* @throws EmailCodeDisabledError if the project has switched this off
|
|
863
|
+
*/
|
|
864
|
+
async signInWithEmailCode(params) {
|
|
865
|
+
const res = await this.authRequest('/v1/sdk/auth/email/code/verify', {
|
|
866
|
+
method: 'POST',
|
|
867
|
+
body: { email: params.email, code: params.code },
|
|
868
|
+
});
|
|
869
|
+
const session = await this.parseSessionResponse(res, false);
|
|
870
|
+
await this.setSessionInternal(session);
|
|
871
|
+
return session;
|
|
838
872
|
}
|
|
839
873
|
// ─── Phone OTP ──────────────────────────────────────────────────────────
|
|
840
874
|
async sendOtp(params) {
|
|
@@ -1027,6 +1061,16 @@ export class KoolbaseAuth {
|
|
|
1027
1061
|
case 'contact_not_verified':
|
|
1028
1062
|
case 'email_not_verified':
|
|
1029
1063
|
throw new ContactNotVerifiedError(msg || undefined);
|
|
1064
|
+
// Sign-in codes. The otp_* codes are shared with phone sign-in; mapped
|
|
1065
|
+
// here, every path that returns them throws the class an app catches.
|
|
1066
|
+
case 'email_code_disabled':
|
|
1067
|
+
throw new EmailCodeDisabledError();
|
|
1068
|
+
case 'otp_expired':
|
|
1069
|
+
throw new OtpExpiredError();
|
|
1070
|
+
case 'otp_invalid':
|
|
1071
|
+
throw new OtpInvalidError();
|
|
1072
|
+
case 'otp_max_attempts':
|
|
1073
|
+
throw new OtpMaxAttemptsError();
|
|
1030
1074
|
// Registration
|
|
1031
1075
|
case 'signups_disabled':
|
|
1032
1076
|
throw new SignupsDisabledError(msg || undefined);
|
|
@@ -195,6 +195,52 @@ export declare class KoolbaseConflictError extends KoolbaseDataError {
|
|
|
195
195
|
field?: string;
|
|
196
196
|
constructor(message?: string, field?: string);
|
|
197
197
|
}
|
|
198
|
+
/**
|
|
199
|
+
* A reference field points at a record that does not exist, is deleted, or
|
|
200
|
+
* lives in another collection — the server responds 400 `reference_invalid`.
|
|
201
|
+
*
|
|
202
|
+
* Checked when the write commits, so it can surface from an insert, an update,
|
|
203
|
+
* an upsert or a batch. In a batch the whole transaction is refused: nothing
|
|
204
|
+
* in it was written.
|
|
205
|
+
*/
|
|
206
|
+
export declare class KoolbaseReferenceInvalidError extends KoolbaseDataError {
|
|
207
|
+
constructor(message?: string);
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* A record could not be deleted because live records still reference it, under
|
|
211
|
+
* a reference declared `on_delete: restrict` — 409 `reference_in_use`.
|
|
212
|
+
*
|
|
213
|
+
* Delete the referencing records first, or in the same batch: the check runs at
|
|
214
|
+
* commit, so one batch may delete a parent and its children in any order.
|
|
215
|
+
*/
|
|
216
|
+
export declare class KoolbaseReferenceInUseError extends KoolbaseDataError {
|
|
217
|
+
constructor(message?: string);
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* A reference could not be declared because existing records already point at
|
|
221
|
+
* records that do not exist — 409 `dangling_references`.
|
|
222
|
+
*
|
|
223
|
+
* `dangling` lists the offending records so they can be repaired; Koolbase
|
|
224
|
+
* never repairs them for you. It is capped at the first 50.
|
|
225
|
+
*/
|
|
226
|
+
export declare class KoolbaseDanglingReferencesError extends KoolbaseDataError {
|
|
227
|
+
dangling: Array<{
|
|
228
|
+
record_id: string;
|
|
229
|
+
value: string;
|
|
230
|
+
}>;
|
|
231
|
+
constructor(message?: string, dangling?: Array<{
|
|
232
|
+
record_id: string;
|
|
233
|
+
value: string;
|
|
234
|
+
}>);
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* A collection could not be deleted because another collection has a reference
|
|
238
|
+
* field pointing at it — 409 `collection_referenced`. Remove that reference
|
|
239
|
+
* first.
|
|
240
|
+
*/
|
|
241
|
+
export declare class KoolbaseCollectionReferencedError extends KoolbaseDataError {
|
|
242
|
+
constructor(message?: string);
|
|
243
|
+
}
|
|
198
244
|
/**
|
|
199
245
|
* Thrown when the requested record or collection does not exist — the server
|
|
200
246
|
* responds with 404 and code `not_found` / `record_not_found` /
|
|
@@ -272,6 +272,62 @@ export class KoolbaseConflictError extends KoolbaseDataError {
|
|
|
272
272
|
Object.setPrototypeOf(this, KoolbaseConflictError.prototype);
|
|
273
273
|
}
|
|
274
274
|
}
|
|
275
|
+
/**
|
|
276
|
+
* A reference field points at a record that does not exist, is deleted, or
|
|
277
|
+
* lives in another collection — the server responds 400 `reference_invalid`.
|
|
278
|
+
*
|
|
279
|
+
* Checked when the write commits, so it can surface from an insert, an update,
|
|
280
|
+
* an upsert or a batch. In a batch the whole transaction is refused: nothing
|
|
281
|
+
* in it was written.
|
|
282
|
+
*/
|
|
283
|
+
export class KoolbaseReferenceInvalidError extends KoolbaseDataError {
|
|
284
|
+
constructor(message) {
|
|
285
|
+
super(message ?? 'A reference points at a record that does not exist', 'reference_invalid');
|
|
286
|
+
this.name = 'KoolbaseReferenceInvalidError';
|
|
287
|
+
Object.setPrototypeOf(this, KoolbaseReferenceInvalidError.prototype);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* A record could not be deleted because live records still reference it, under
|
|
292
|
+
* a reference declared `on_delete: restrict` — 409 `reference_in_use`.
|
|
293
|
+
*
|
|
294
|
+
* Delete the referencing records first, or in the same batch: the check runs at
|
|
295
|
+
* commit, so one batch may delete a parent and its children in any order.
|
|
296
|
+
*/
|
|
297
|
+
export class KoolbaseReferenceInUseError extends KoolbaseDataError {
|
|
298
|
+
constructor(message) {
|
|
299
|
+
super(message ?? 'This record is still referenced by other records', 'reference_in_use');
|
|
300
|
+
this.name = 'KoolbaseReferenceInUseError';
|
|
301
|
+
Object.setPrototypeOf(this, KoolbaseReferenceInUseError.prototype);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* A reference could not be declared because existing records already point at
|
|
306
|
+
* records that do not exist — 409 `dangling_references`.
|
|
307
|
+
*
|
|
308
|
+
* `dangling` lists the offending records so they can be repaired; Koolbase
|
|
309
|
+
* never repairs them for you. It is capped at the first 50.
|
|
310
|
+
*/
|
|
311
|
+
export class KoolbaseDanglingReferencesError extends KoolbaseDataError {
|
|
312
|
+
constructor(message, dangling) {
|
|
313
|
+
super(message ?? 'Existing records point at records that do not exist', 'dangling_references');
|
|
314
|
+
this.dangling = dangling ?? [];
|
|
315
|
+
this.name = 'KoolbaseDanglingReferencesError';
|
|
316
|
+
Object.setPrototypeOf(this, KoolbaseDanglingReferencesError.prototype);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* A collection could not be deleted because another collection has a reference
|
|
321
|
+
* field pointing at it — 409 `collection_referenced`. Remove that reference
|
|
322
|
+
* first.
|
|
323
|
+
*/
|
|
324
|
+
export class KoolbaseCollectionReferencedError extends KoolbaseDataError {
|
|
325
|
+
constructor(message) {
|
|
326
|
+
super(message ?? 'Another collection references this one', 'collection_referenced');
|
|
327
|
+
this.name = 'KoolbaseCollectionReferencedError';
|
|
328
|
+
Object.setPrototypeOf(this, KoolbaseCollectionReferencedError.prototype);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
275
331
|
/**
|
|
276
332
|
* Thrown when the requested record or collection does not exist — the server
|
|
277
333
|
* responds with 404 and code `not_found` / `record_not_found` /
|
|
@@ -396,6 +452,16 @@ export function koolbaseDataError(status, body, fallbackMessage = 'Request faile
|
|
|
396
452
|
switch (code) {
|
|
397
453
|
case 'unique_violation':
|
|
398
454
|
return attach(new KoolbaseConflictError(message, field));
|
|
455
|
+
case 'reference_invalid':
|
|
456
|
+
return attach(new KoolbaseReferenceInvalidError(message));
|
|
457
|
+
case 'reference_in_use':
|
|
458
|
+
return attach(new KoolbaseReferenceInUseError(message));
|
|
459
|
+
case 'dangling_references': {
|
|
460
|
+
const d = (body?.details ?? {});
|
|
461
|
+
return attach(new KoolbaseDanglingReferencesError(message, d.dangling));
|
|
462
|
+
}
|
|
463
|
+
case 'collection_referenced':
|
|
464
|
+
return attach(new KoolbaseCollectionReferencedError(message));
|
|
399
465
|
case 'plan_limit_reached': {
|
|
400
466
|
const d = (body?.details ?? {});
|
|
401
467
|
return new KoolbasePlanLimitError(message, d.resource, d.limit, d.plan);
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { KoolbaseConfig } from './types.js';
|
|
2
|
+
import { KoolbaseError } from './errors.js';
|
|
3
|
+
/**
|
|
4
|
+
* The lifecycle status of a fiscal intent.
|
|
5
|
+
*
|
|
6
|
+
* A sale is durably recorded the moment `submit` returns; fiscal numbers and
|
|
7
|
+
* the authority's certification follow this state machine:
|
|
8
|
+
*
|
|
9
|
+
* - `queued` / `submitting` / `retrying` — sealed with allocated numbers;
|
|
10
|
+
* delivery to the authority is in progress.
|
|
11
|
+
* - `fiscalized` — the authority answered; for certifying document kinds the
|
|
12
|
+
* `certification` is present.
|
|
13
|
+
* - `blocked` — a precondition failed BEFORE any fiscal number was consumed
|
|
14
|
+
* (see `blockedReason`). Resubmitting the same `clientRef` after fixing the
|
|
15
|
+
* cause resumes it.
|
|
16
|
+
* - `attention` — the authority rejected the document, or its credential died
|
|
17
|
+
* after sealing. An operator resolves it; the register's chain waits behind
|
|
18
|
+
* it.
|
|
19
|
+
* - `voided` — terminally closed by an operator without fiscalization.
|
|
20
|
+
*
|
|
21
|
+
* `unknown` is the forward-compatible fallback: a status this SDK version has
|
|
22
|
+
* not heard of parses as unknown rather than throwing, so a server that grows
|
|
23
|
+
* a state does not break older clients.
|
|
24
|
+
*/
|
|
25
|
+
export type FiscalStatus = 'created' | 'blocked' | 'queued' | 'submitting' | 'retrying' | 'fiscalized' | 'attention' | 'voided' | 'unknown';
|
|
26
|
+
/**
|
|
27
|
+
* The state of one fiscal intent.
|
|
28
|
+
*
|
|
29
|
+
* Jurisdiction-neutral by design: this SDK never learns a jurisdiction's
|
|
30
|
+
* vocabulary. Payloads travel as objects shaped by the device's adapter, and
|
|
31
|
+
* `certification` comes back as the authority granted it — for Ghana that
|
|
32
|
+
* means `ysdcregsig`, `ysdcrecnum`, `qr_code` and so on, which the SDK
|
|
33
|
+
* passes through without naming.
|
|
34
|
+
*/
|
|
35
|
+
export interface FiscalIntentResult {
|
|
36
|
+
intentId: string;
|
|
37
|
+
status: FiscalStatus;
|
|
38
|
+
/** Why a `blocked` intent stopped, when the server said. */
|
|
39
|
+
blockedReason?: string;
|
|
40
|
+
/** Fiscal numbers allocated at sealing. */
|
|
41
|
+
numbers?: Record<string, number>;
|
|
42
|
+
/** When the intent was sealed and its numbers allocated. */
|
|
43
|
+
sealedAt?: string;
|
|
44
|
+
/**
|
|
45
|
+
* What the authority granted, once `status` is `fiscalized`. This is the
|
|
46
|
+
* receipt-rendering payload: for Ghana, the signature, receipt number and
|
|
47
|
+
* QR verification URL.
|
|
48
|
+
*/
|
|
49
|
+
certification?: Record<string, unknown>;
|
|
50
|
+
}
|
|
51
|
+
/** True once the authority has certified the document. */
|
|
52
|
+
export declare function isFiscalized(r: FiscalIntentResult): boolean;
|
|
53
|
+
/** True while delivery to the authority is still in progress. */
|
|
54
|
+
export declare function isPending(r: FiscalIntentResult): boolean;
|
|
55
|
+
/** A fiscal request that failed. Carries the HTTP status where there was one. */
|
|
56
|
+
export declare class KoolbaseFiscalError extends KoolbaseError {
|
|
57
|
+
readonly statusCode?: number;
|
|
58
|
+
constructor(message: string, statusCode?: number);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Classify a response. Status and body in, result or error out.
|
|
62
|
+
*
|
|
63
|
+
* Exported for testability, and because auth handling belongs with the
|
|
64
|
+
* session hook rather than here — the same split the Flutter SDK makes.
|
|
65
|
+
*/
|
|
66
|
+
export declare function decodeFiscalResponse(status: number, body: string): Promise<FiscalIntentResult>;
|
|
67
|
+
/**
|
|
68
|
+
* Koolbase Fiscal — submitting sales to a tax authority.
|
|
69
|
+
*
|
|
70
|
+
* Jurisdiction-neutral: the SDK moves payloads and reads back whatever the
|
|
71
|
+
* authority certified. Ghana's GRA E-VAT is the first jurisdiction; nothing
|
|
72
|
+
* here names it.
|
|
73
|
+
*/
|
|
74
|
+
export declare class KoolbaseFiscal {
|
|
75
|
+
private config;
|
|
76
|
+
private getToken;
|
|
77
|
+
private onSessionExpired?;
|
|
78
|
+
constructor(config: KoolbaseConfig, getToken: () => Promise<string | null>, onSessionExpired?: () => Promise<void>);
|
|
79
|
+
private buildHeaders;
|
|
80
|
+
private decode;
|
|
81
|
+
/**
|
|
82
|
+
* Submit a sale for fiscalization.
|
|
83
|
+
*
|
|
84
|
+
* The sale is durably recorded the moment this returns; fiscal numbers and
|
|
85
|
+
* the authority's certification follow asynchronously — poll `status()`
|
|
86
|
+
* with the same `clientRef` to watch it.
|
|
87
|
+
*
|
|
88
|
+
* **On timeout, do not resubmit blindly.** The sale may have fiscalized
|
|
89
|
+
* anyway: the request reached the server and the response was lost. Poll
|
|
90
|
+
* `status()` with the same `clientRef` instead, which is idempotent and
|
|
91
|
+
* tells you what actually happened.
|
|
92
|
+
*
|
|
93
|
+
* @param clientRef your own reference for this sale; the idempotency key
|
|
94
|
+
* and the handle you poll and render receipts by.
|
|
95
|
+
*/
|
|
96
|
+
submit(params: {
|
|
97
|
+
deviceId: string;
|
|
98
|
+
clientRef: string;
|
|
99
|
+
payload: Record<string, unknown>;
|
|
100
|
+
timeoutMs?: number;
|
|
101
|
+
}): Promise<FiscalIntentResult>;
|
|
102
|
+
/**
|
|
103
|
+
* Read an intent's current state by the reference it was submitted under.
|
|
104
|
+
*
|
|
105
|
+
* This is the receipt-rendering read: once the status is `fiscalized`, the
|
|
106
|
+
* `certification` carries what the receipt must show — for Ghana, the
|
|
107
|
+
* signature, receipt number and QR verification URL.
|
|
108
|
+
*/
|
|
109
|
+
status(params: {
|
|
110
|
+
deviceId: string;
|
|
111
|
+
clientRef: string;
|
|
112
|
+
timeoutMs?: number;
|
|
113
|
+
}): Promise<FiscalIntentResult>;
|
|
114
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { KoolbaseError, KoolbaseUnauthenticatedError } from './errors.js';
|
|
2
|
+
const KNOWN_STATUSES = [
|
|
3
|
+
'created', 'blocked', 'queued', 'submitting',
|
|
4
|
+
'retrying', 'fiscalized', 'attention', 'voided',
|
|
5
|
+
];
|
|
6
|
+
function parseStatus(raw) {
|
|
7
|
+
return typeof raw === 'string' && KNOWN_STATUSES.includes(raw)
|
|
8
|
+
? raw
|
|
9
|
+
: 'unknown';
|
|
10
|
+
}
|
|
11
|
+
/** True once the authority has certified the document. */
|
|
12
|
+
export function isFiscalized(r) {
|
|
13
|
+
return r.status === 'fiscalized';
|
|
14
|
+
}
|
|
15
|
+
/** True while delivery to the authority is still in progress. */
|
|
16
|
+
export function isPending(r) {
|
|
17
|
+
return r.status === 'queued'
|
|
18
|
+
|| r.status === 'submitting'
|
|
19
|
+
|| r.status === 'retrying';
|
|
20
|
+
}
|
|
21
|
+
/** A fiscal request that failed. Carries the HTTP status where there was one. */
|
|
22
|
+
export class KoolbaseFiscalError extends KoolbaseError {
|
|
23
|
+
constructor(message, statusCode) {
|
|
24
|
+
super(message, 'fiscal_failed');
|
|
25
|
+
this.statusCode = statusCode;
|
|
26
|
+
this.name = 'KoolbaseFiscalError';
|
|
27
|
+
Object.setPrototypeOf(this, KoolbaseFiscalError.prototype);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function resultFromJson(json) {
|
|
31
|
+
const numbers = json.numbers;
|
|
32
|
+
return {
|
|
33
|
+
intentId: String(json.intent_id ?? ''),
|
|
34
|
+
status: parseStatus(json.status),
|
|
35
|
+
blockedReason: json.blocked_reason,
|
|
36
|
+
numbers: numbers ?? undefined,
|
|
37
|
+
sealedAt: json.sealed_at,
|
|
38
|
+
certification: json.certification,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Classify a response. Status and body in, result or error out.
|
|
43
|
+
*
|
|
44
|
+
* Exported for testability, and because auth handling belongs with the
|
|
45
|
+
* session hook rather than here — the same split the Flutter SDK makes.
|
|
46
|
+
*/
|
|
47
|
+
export async function decodeFiscalResponse(status, body) {
|
|
48
|
+
let parsed;
|
|
49
|
+
try {
|
|
50
|
+
parsed = JSON.parse(body);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
throw new KoolbaseFiscalError(`fiscal: unreadable response (${status})`, status);
|
|
54
|
+
}
|
|
55
|
+
if ((status >= 200 && status < 300) || status === 202) {
|
|
56
|
+
return resultFromJson(parsed);
|
|
57
|
+
}
|
|
58
|
+
throw new KoolbaseFiscalError(parsed.error ?? 'fiscal: request failed', status);
|
|
59
|
+
}
|
|
60
|
+
// ─── KoolbaseFiscal ──────────────────────────────────────────────────────────
|
|
61
|
+
/**
|
|
62
|
+
* Koolbase Fiscal — submitting sales to a tax authority.
|
|
63
|
+
*
|
|
64
|
+
* Jurisdiction-neutral: the SDK moves payloads and reads back whatever the
|
|
65
|
+
* authority certified. Ghana's GRA E-VAT is the first jurisdiction; nothing
|
|
66
|
+
* here names it.
|
|
67
|
+
*/
|
|
68
|
+
export class KoolbaseFiscal {
|
|
69
|
+
constructor(config, getToken, onSessionExpired) {
|
|
70
|
+
this.config = config;
|
|
71
|
+
this.getToken = getToken;
|
|
72
|
+
this.onSessionExpired = onSessionExpired;
|
|
73
|
+
}
|
|
74
|
+
async buildHeaders() {
|
|
75
|
+
const token = await this.getToken();
|
|
76
|
+
return {
|
|
77
|
+
'Content-Type': 'application/json',
|
|
78
|
+
'x-api-key': this.config.publicKey,
|
|
79
|
+
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
async decode(res) {
|
|
83
|
+
if (res.status === 401 || res.status === 403) {
|
|
84
|
+
await this.onSessionExpired?.();
|
|
85
|
+
throw new KoolbaseUnauthenticatedError(`fiscal: unauthenticated (${res.status})`);
|
|
86
|
+
}
|
|
87
|
+
return decodeFiscalResponse(res.status, await res.text());
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Submit a sale for fiscalization.
|
|
91
|
+
*
|
|
92
|
+
* The sale is durably recorded the moment this returns; fiscal numbers and
|
|
93
|
+
* the authority's certification follow asynchronously — poll `status()`
|
|
94
|
+
* with the same `clientRef` to watch it.
|
|
95
|
+
*
|
|
96
|
+
* **On timeout, do not resubmit blindly.** The sale may have fiscalized
|
|
97
|
+
* anyway: the request reached the server and the response was lost. Poll
|
|
98
|
+
* `status()` with the same `clientRef` instead, which is idempotent and
|
|
99
|
+
* tells you what actually happened.
|
|
100
|
+
*
|
|
101
|
+
* @param clientRef your own reference for this sale; the idempotency key
|
|
102
|
+
* and the handle you poll and render receipts by.
|
|
103
|
+
*/
|
|
104
|
+
async submit(params) {
|
|
105
|
+
const controller = new AbortController();
|
|
106
|
+
const timer = setTimeout(() => controller.abort(), params.timeoutMs ?? 30000);
|
|
107
|
+
try {
|
|
108
|
+
const res = await fetch(`${this.config.baseUrl}/v1/sdk/fiscal/submit`, {
|
|
109
|
+
method: 'POST',
|
|
110
|
+
headers: await this.buildHeaders(),
|
|
111
|
+
signal: controller.signal,
|
|
112
|
+
body: JSON.stringify({
|
|
113
|
+
device_id: params.deviceId,
|
|
114
|
+
client_ref: params.clientRef,
|
|
115
|
+
payload: params.payload,
|
|
116
|
+
}),
|
|
117
|
+
});
|
|
118
|
+
return await this.decode(res);
|
|
119
|
+
}
|
|
120
|
+
catch (e) {
|
|
121
|
+
if (e instanceof Error && e.name === 'AbortError') {
|
|
122
|
+
throw new KoolbaseFiscalError('fiscal submit timed out — the sale may still fiscalize; '
|
|
123
|
+
+ 'poll status with the same clientRef');
|
|
124
|
+
}
|
|
125
|
+
throw e;
|
|
126
|
+
}
|
|
127
|
+
finally {
|
|
128
|
+
clearTimeout(timer);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Read an intent's current state by the reference it was submitted under.
|
|
133
|
+
*
|
|
134
|
+
* This is the receipt-rendering read: once the status is `fiscalized`, the
|
|
135
|
+
* `certification` carries what the receipt must show — for Ghana, the
|
|
136
|
+
* signature, receipt number and QR verification URL.
|
|
137
|
+
*/
|
|
138
|
+
async status(params) {
|
|
139
|
+
const controller = new AbortController();
|
|
140
|
+
const timer = setTimeout(() => controller.abort(), params.timeoutMs ?? 15000);
|
|
141
|
+
try {
|
|
142
|
+
const q = new URLSearchParams({
|
|
143
|
+
device_id: params.deviceId,
|
|
144
|
+
client_ref: params.clientRef,
|
|
145
|
+
});
|
|
146
|
+
const res = await fetch(`${this.config.baseUrl}/v1/sdk/fiscal/status?${q.toString()}`, { method: 'GET', headers: await this.buildHeaders(), signal: controller.signal });
|
|
147
|
+
return await this.decode(res);
|
|
148
|
+
}
|
|
149
|
+
catch (e) {
|
|
150
|
+
if (e instanceof Error && e.name === 'AbortError') {
|
|
151
|
+
throw new KoolbaseFiscalError('fiscal status timed out');
|
|
152
|
+
}
|
|
153
|
+
throw e;
|
|
154
|
+
}
|
|
155
|
+
finally {
|
|
156
|
+
clearTimeout(timer);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -14,6 +14,8 @@ export { KoolbaseFunctions } from './functions.js';
|
|
|
14
14
|
export { KoolbaseRealtime } from './realtime.js';
|
|
15
15
|
export { KoolbaseStorage } from './storage.js';
|
|
16
16
|
export { KoolbaseAnalytics } from './analytics.js';
|
|
17
|
+
export { KoolbaseFiscal, KoolbaseFiscalError, decodeFiscalResponse, isFiscalized, isPending } from './fiscal.js';
|
|
18
|
+
export type { FiscalIntentResult, FiscalStatus } from './fiscal.js';
|
|
17
19
|
export { KoolbaseMessaging } from './messaging.js';
|
|
18
20
|
export type { RegisterTokenOptions } from './messaging.js';
|
|
19
21
|
export { getOrCreateDeviceId } from './device-id.js';
|
package/dist/esm/index.js
CHANGED
|
@@ -19,6 +19,7 @@ export { KoolbaseFunctions } from './functions.js';
|
|
|
19
19
|
export { KoolbaseRealtime } from './realtime.js';
|
|
20
20
|
export { KoolbaseStorage } from './storage.js';
|
|
21
21
|
export { KoolbaseAnalytics } from './analytics.js';
|
|
22
|
+
export { KoolbaseFiscal, KoolbaseFiscalError, decodeFiscalResponse, isFiscalized, isPending } from './fiscal.js';
|
|
22
23
|
export { KoolbaseMessaging } from './messaging.js';
|
|
23
24
|
export { getOrCreateDeviceId } from './device-id.js';
|
|
24
25
|
export { koolbaseSdkVersion } from './device-metadata.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@koolbase/core",
|
|
3
|
-
"version": "12.
|
|
3
|
+
"version": "12.1.0",
|
|
4
4
|
"description": "Koolbase SDK core \u2014 shared behaviour behind @koolbase/react-native and @koolbase/js. Install one of those, not this.",
|
|
5
5
|
"main": "./dist/cjs/index.js",
|
|
6
6
|
"types": "./dist/esm/index.d.ts",
|