@koolbase/core 11.4.0 → 12.0.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/aggregate.d.ts +115 -0
- package/dist/cjs/aggregate.js +26 -0
- package/dist/cjs/auth-errors.js +13 -13
- package/dist/cjs/auth.d.ts +99 -1
- package/dist/cjs/auth.js +198 -0
- package/dist/cjs/database.d.ts +23 -0
- package/dist/cjs/database.js +64 -0
- package/dist/cjs/device-metadata.js +7 -1
- package/dist/cjs/index.d.ts +1 -0
- package/dist/cjs/index.js +1 -0
- package/dist/cjs/types.d.ts +58 -0
- package/dist/esm/aggregate.d.ts +115 -0
- package/dist/esm/aggregate.js +25 -0
- package/dist/esm/auth-errors.js +13 -13
- package/dist/esm/auth.d.ts +99 -1
- package/dist/esm/auth.js +198 -0
- package/dist/esm/database.d.ts +23 -0
- package/dist/esm/database.js +64 -0
- package/dist/esm/device-metadata.js +7 -1
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/types.d.ts +58 -0
- package/package.json +1 -1
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Asking "how many" and "how much" over a collection, correctly.
|
|
3
|
+
*
|
|
4
|
+
* Correct means the whole authorized set — never the first page — with the
|
|
5
|
+
* caller's read rule applied inside the query, and never a bare number:
|
|
6
|
+
* collections are schemaless, so a total that quietly excluded three
|
|
7
|
+
* malformed rows would be a wrong number that looks right. Every result
|
|
8
|
+
* carries its accounting.
|
|
9
|
+
*
|
|
10
|
+
* ```ts
|
|
11
|
+
* const r = await Koolbase.db.aggregate({
|
|
12
|
+
* collection: 'orders',
|
|
13
|
+
* groupBy: { field: 'created_at', bucket: 'month', timezone: 'Africa/Accra' },
|
|
14
|
+
* measures: [{ aggregate: 'sum', field: 'total', as: 'revenue' }],
|
|
15
|
+
* });
|
|
16
|
+
*
|
|
17
|
+
* for (const g of r.groups) {
|
|
18
|
+
* console.log(g.category, g.values.revenue);
|
|
19
|
+
* }
|
|
20
|
+
* if (r.accounting.revenue.skipped > 0) {
|
|
21
|
+
* // some orders had a total that was not a number — say so
|
|
22
|
+
* }
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
/** A record filter, AND-ed on top of the read rule. */
|
|
26
|
+
export interface KoolbaseAggregateFilter {
|
|
27
|
+
field: string;
|
|
28
|
+
op: 'eq' | 'neq' | 'in' | 'gt' | 'gte' | 'lt' | 'lte';
|
|
29
|
+
value: unknown;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The category axis: a field's value, or a calendar bucket of a timestamp
|
|
33
|
+
* field.
|
|
34
|
+
*
|
|
35
|
+
* A bucket REQUIRES a timezone, and the union makes omitting it a type
|
|
36
|
+
* error. "Midnight" means nothing without one — Africa/Accra and UTC
|
|
37
|
+
* disagree about which day a 23:30 sale belongs to — and the API refuses a
|
|
38
|
+
* bucket that arrives without one.
|
|
39
|
+
*/
|
|
40
|
+
export type KoolbaseGroupBy = {
|
|
41
|
+
field: string;
|
|
42
|
+
bucket?: never;
|
|
43
|
+
timezone?: never;
|
|
44
|
+
} | {
|
|
45
|
+
field: string;
|
|
46
|
+
bucket: 'day' | 'week' | 'month' | 'year';
|
|
47
|
+
timezone: string;
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* One number per group.
|
|
51
|
+
*
|
|
52
|
+
* `count` counts RECORDS, so it takes no field and skips nothing. The others
|
|
53
|
+
* read a field, and a record whose value is missing or not a number is
|
|
54
|
+
* skipped and reported in the result's accounting.
|
|
55
|
+
*/
|
|
56
|
+
export type KoolbaseMeasure = {
|
|
57
|
+
aggregate: 'count';
|
|
58
|
+
field?: never;
|
|
59
|
+
as: string;
|
|
60
|
+
} | {
|
|
61
|
+
aggregate: 'sum' | 'avg' | 'min' | 'max';
|
|
62
|
+
field: string;
|
|
63
|
+
as: string;
|
|
64
|
+
};
|
|
65
|
+
/** One aggregation request. */
|
|
66
|
+
export interface KoolbaseAggregateRequest {
|
|
67
|
+
collection: string;
|
|
68
|
+
/** AND-ed record filters on top of the read rule. */
|
|
69
|
+
where?: KoolbaseAggregateFilter[];
|
|
70
|
+
/** Absent means one group: the measures over everything. */
|
|
71
|
+
groupBy?: KoolbaseGroupBy;
|
|
72
|
+
/** At least one. */
|
|
73
|
+
measures: KoolbaseMeasure[];
|
|
74
|
+
}
|
|
75
|
+
/** One category and its numbers. */
|
|
76
|
+
export interface KoolbaseAggregateGroup {
|
|
77
|
+
/** Empty string for the single group when there was no groupBy. */
|
|
78
|
+
category: string;
|
|
79
|
+
/** Keyed by each measure's `as`. Null where a group had nothing to measure. */
|
|
80
|
+
values: Record<string, number | null>;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* How a measure arrived at its number.
|
|
84
|
+
*
|
|
85
|
+
* `skipped` is not an error. It is what you need to decide whether the
|
|
86
|
+
* number means what it appears to mean — a revenue total over 900 of 903
|
|
87
|
+
* orders is a different fact from one over all of them, and only this says
|
|
88
|
+
* which you are looking at.
|
|
89
|
+
*/
|
|
90
|
+
export interface KoolbaseAggregateAccounting {
|
|
91
|
+
counted: number;
|
|
92
|
+
skipped: number;
|
|
93
|
+
/** Present when skipped > 0. */
|
|
94
|
+
skippedReason?: 'missing' | 'not_numeric' | 'mixed';
|
|
95
|
+
}
|
|
96
|
+
/** The whole answer, including what could not be counted. */
|
|
97
|
+
export interface KoolbaseAggregateResult {
|
|
98
|
+
/**
|
|
99
|
+
* One per category, sorted — or exactly one with an empty category when
|
|
100
|
+
* there was no groupBy.
|
|
101
|
+
*/
|
|
102
|
+
groups: KoolbaseAggregateGroup[];
|
|
103
|
+
/** Records that matched the read rule and filters: the denominator. */
|
|
104
|
+
matched: number;
|
|
105
|
+
/** Per measure name, keyed by its `as`. */
|
|
106
|
+
accounting: Record<string, KoolbaseAggregateAccounting>;
|
|
107
|
+
/**
|
|
108
|
+
* True when the groupBy produced more categories than allowed and the
|
|
109
|
+
* whole request was refused.
|
|
110
|
+
*
|
|
111
|
+
* `groups` is EMPTY when this is set: a partial set drawn as the whole
|
|
112
|
+
* would be exactly the wrong number this API exists to prevent.
|
|
113
|
+
*/
|
|
114
|
+
tooManyGroups: boolean;
|
|
115
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Asking "how many" and "how much" over a collection, correctly.
|
|
4
|
+
*
|
|
5
|
+
* Correct means the whole authorized set — never the first page — with the
|
|
6
|
+
* caller's read rule applied inside the query, and never a bare number:
|
|
7
|
+
* collections are schemaless, so a total that quietly excluded three
|
|
8
|
+
* malformed rows would be a wrong number that looks right. Every result
|
|
9
|
+
* carries its accounting.
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* const r = await Koolbase.db.aggregate({
|
|
13
|
+
* collection: 'orders',
|
|
14
|
+
* groupBy: { field: 'created_at', bucket: 'month', timezone: 'Africa/Accra' },
|
|
15
|
+
* measures: [{ aggregate: 'sum', field: 'total', as: 'revenue' }],
|
|
16
|
+
* });
|
|
17
|
+
*
|
|
18
|
+
* for (const g of r.groups) {
|
|
19
|
+
* console.log(g.category, g.values.revenue);
|
|
20
|
+
* }
|
|
21
|
+
* if (r.accounting.revenue.skipped > 0) {
|
|
22
|
+
* // some orders had a total that was not a number — say so
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
package/dist/cjs/auth-errors.js
CHANGED
|
@@ -26,7 +26,7 @@ class InvalidCredentialsError extends KoolbaseAuthError {
|
|
|
26
26
|
exports.InvalidCredentialsError = InvalidCredentialsError;
|
|
27
27
|
class EmailAlreadyInUseError extends KoolbaseAuthError {
|
|
28
28
|
constructor() {
|
|
29
|
-
super('Email is already in use', '
|
|
29
|
+
super('Email is already in use', 'email_in_use');
|
|
30
30
|
this.name = 'EmailAlreadyInUseError';
|
|
31
31
|
Object.setPrototypeOf(this, EmailAlreadyInUseError.prototype);
|
|
32
32
|
}
|
|
@@ -34,7 +34,7 @@ class EmailAlreadyInUseError extends KoolbaseAuthError {
|
|
|
34
34
|
exports.EmailAlreadyInUseError = EmailAlreadyInUseError;
|
|
35
35
|
class UserDisabledError extends KoolbaseAuthError {
|
|
36
36
|
constructor() {
|
|
37
|
-
super('This account has been disabled', '
|
|
37
|
+
super('This account has been disabled', 'account_disabled');
|
|
38
38
|
this.name = 'UserDisabledError';
|
|
39
39
|
Object.setPrototypeOf(this, UserDisabledError.prototype);
|
|
40
40
|
}
|
|
@@ -57,7 +57,7 @@ exports.WeakPasswordError = WeakPasswordError;
|
|
|
57
57
|
// ─── Session lifecycle ─────────────────────────────────────────────────────
|
|
58
58
|
class SessionExpiredError extends KoolbaseAuthError {
|
|
59
59
|
constructor() {
|
|
60
|
-
super('Session expired, please log in again', '
|
|
60
|
+
super('Session expired, please log in again', 'invalid_refresh_token');
|
|
61
61
|
this.name = 'SessionExpiredError';
|
|
62
62
|
Object.setPrototypeOf(this, SessionExpiredError.prototype);
|
|
63
63
|
}
|
|
@@ -106,7 +106,7 @@ exports.AccountLockedError = AccountLockedError;
|
|
|
106
106
|
*/
|
|
107
107
|
class UnlockTokenInvalidError extends KoolbaseAuthError {
|
|
108
108
|
constructor() {
|
|
109
|
-
super('Unlock link is invalid or has expired', '
|
|
109
|
+
super('Unlock link is invalid or has expired', 'invalid_unlock_token');
|
|
110
110
|
this.name = 'UnlockTokenInvalidError';
|
|
111
111
|
Object.setPrototypeOf(this, UnlockTokenInvalidError.prototype);
|
|
112
112
|
}
|
|
@@ -205,7 +205,7 @@ class OtpMaxAttemptsError extends KoolbaseAuthError {
|
|
|
205
205
|
exports.OtpMaxAttemptsError = OtpMaxAttemptsError;
|
|
206
206
|
class OtpRateLimitError extends KoolbaseAuthError {
|
|
207
207
|
constructor() {
|
|
208
|
-
super('Too many OTP requests, please wait before trying again', '
|
|
208
|
+
super('Too many OTP requests, please wait before trying again', 'rate_limit');
|
|
209
209
|
this.name = 'OtpRateLimitError';
|
|
210
210
|
Object.setPrototypeOf(this, OtpRateLimitError.prototype);
|
|
211
211
|
}
|
|
@@ -213,7 +213,7 @@ class OtpRateLimitError extends KoolbaseAuthError {
|
|
|
213
213
|
exports.OtpRateLimitError = OtpRateLimitError;
|
|
214
214
|
class PhoneAlreadyLinkedError extends KoolbaseAuthError {
|
|
215
215
|
constructor() {
|
|
216
|
-
super('Phone number is already associated with another account', '
|
|
216
|
+
super('Phone number is already associated with another account', 'phone_in_use');
|
|
217
217
|
this.name = 'PhoneAlreadyLinkedError';
|
|
218
218
|
Object.setPrototypeOf(this, PhoneAlreadyLinkedError.prototype);
|
|
219
219
|
}
|
|
@@ -221,7 +221,7 @@ class PhoneAlreadyLinkedError extends KoolbaseAuthError {
|
|
|
221
221
|
exports.PhoneAlreadyLinkedError = PhoneAlreadyLinkedError;
|
|
222
222
|
class SmsConfigMissingError extends KoolbaseAuthError {
|
|
223
223
|
constructor() {
|
|
224
|
-
super('SMS provider not configured for this project', '
|
|
224
|
+
super('SMS provider not configured for this project', 'sms_not_configured');
|
|
225
225
|
this.name = 'SmsConfigMissingError';
|
|
226
226
|
Object.setPrototypeOf(this, SmsConfigMissingError.prototype);
|
|
227
227
|
}
|
|
@@ -229,7 +229,7 @@ class SmsConfigMissingError extends KoolbaseAuthError {
|
|
|
229
229
|
exports.SmsConfigMissingError = SmsConfigMissingError;
|
|
230
230
|
class AppleSignInNotConfiguredError extends KoolbaseAuthError {
|
|
231
231
|
constructor() {
|
|
232
|
-
super('Apple Sign-In is not configured for this environment', '
|
|
232
|
+
super('Apple Sign-In is not configured for this environment', 'oauth_not_configured');
|
|
233
233
|
this.name = 'AppleSignInNotConfiguredError';
|
|
234
234
|
Object.setPrototypeOf(this, AppleSignInNotConfiguredError.prototype);
|
|
235
235
|
}
|
|
@@ -237,7 +237,7 @@ class AppleSignInNotConfiguredError extends KoolbaseAuthError {
|
|
|
237
237
|
exports.AppleSignInNotConfiguredError = AppleSignInNotConfiguredError;
|
|
238
238
|
class InvalidAppleTokenError extends KoolbaseAuthError {
|
|
239
239
|
constructor() {
|
|
240
|
-
super('Invalid Apple identity token', '
|
|
240
|
+
super('Invalid Apple identity token', 'invalid_oauth_token');
|
|
241
241
|
this.name = 'InvalidAppleTokenError';
|
|
242
242
|
Object.setPrototypeOf(this, InvalidAppleTokenError.prototype);
|
|
243
243
|
}
|
|
@@ -245,7 +245,7 @@ class InvalidAppleTokenError extends KoolbaseAuthError {
|
|
|
245
245
|
exports.InvalidAppleTokenError = InvalidAppleTokenError;
|
|
246
246
|
class AppleEmailRequiredError extends KoolbaseAuthError {
|
|
247
247
|
constructor() {
|
|
248
|
-
super('Apple did not return email for this sign-in. Revoke this app in iOS Settings → Apple ID and retry.', '
|
|
248
|
+
super('Apple did not return email for this sign-in. Revoke this app in iOS Settings → Apple ID and retry.', 'oauth_email_required');
|
|
249
249
|
this.name = 'AppleEmailRequiredError';
|
|
250
250
|
Object.setPrototypeOf(this, AppleEmailRequiredError.prototype);
|
|
251
251
|
}
|
|
@@ -261,7 +261,7 @@ class OAuthEmailConflictError extends KoolbaseAuthError {
|
|
|
261
261
|
exports.OAuthEmailConflictError = OAuthEmailConflictError;
|
|
262
262
|
class GoogleSignInNotConfiguredError extends KoolbaseAuthError {
|
|
263
263
|
constructor() {
|
|
264
|
-
super('Google Sign-In is not configured for this environment', '
|
|
264
|
+
super('Google Sign-In is not configured for this environment', 'oauth_not_configured');
|
|
265
265
|
this.name = 'GoogleSignInNotConfiguredError';
|
|
266
266
|
Object.setPrototypeOf(this, GoogleSignInNotConfiguredError.prototype);
|
|
267
267
|
}
|
|
@@ -458,7 +458,7 @@ class MalformedSessionResponseError extends KoolbaseAuthError {
|
|
|
458
458
|
exports.MalformedSessionResponseError = MalformedSessionResponseError;
|
|
459
459
|
class InvalidGoogleTokenError extends KoolbaseAuthError {
|
|
460
460
|
constructor() {
|
|
461
|
-
super('Invalid Google identity token', '
|
|
461
|
+
super('Invalid Google identity token', 'invalid_oauth_token');
|
|
462
462
|
this.name = 'InvalidGoogleTokenError';
|
|
463
463
|
Object.setPrototypeOf(this, InvalidGoogleTokenError.prototype);
|
|
464
464
|
}
|
|
@@ -466,7 +466,7 @@ class InvalidGoogleTokenError extends KoolbaseAuthError {
|
|
|
466
466
|
exports.InvalidGoogleTokenError = InvalidGoogleTokenError;
|
|
467
467
|
class GoogleEmailRequiredError extends KoolbaseAuthError {
|
|
468
468
|
constructor() {
|
|
469
|
-
super('Google did not return email for this sign-in. Ensure the email scope is requested in the native flow.', '
|
|
469
|
+
super('Google did not return email for this sign-in. Ensure the email scope is requested in the native flow.', 'oauth_email_required');
|
|
470
470
|
this.name = 'GoogleEmailRequiredError';
|
|
471
471
|
Object.setPrototypeOf(this, GoogleEmailRequiredError.prototype);
|
|
472
472
|
}
|
package/dist/cjs/auth.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AuthStateListener, KoolbaseConfig, KoolbaseSession, KoolbaseUser, LinkPhoneParams, LoginParams, OtpSendResult, PhoneVerifyResult, RegisterParams, RestoreResult, ResendVerificationResult, SendOtpParams, SignUpResult, SignInWithAppleParams, VerifyOtpParams } from './types.js';
|
|
1
|
+
import { AuthStateListener, KoolbaseConfig, KoolbaseSession, KoolbaseUser, LinkPhoneParams, LoginParams, OtpSendResult, PhoneVerifyResult, RegisterParams, RestoreResult, KoolbaseAuditPage, KoolbaseSessionInfo, ResendVerificationResult, SendOtpParams, SignUpResult, SignInWithAppleParams, VerifyOtpParams } from './types.js';
|
|
2
2
|
import type { SignInWithGoogleParams } from './types.js';
|
|
3
3
|
export declare class KoolbaseAuth {
|
|
4
4
|
private config;
|
|
@@ -55,6 +55,18 @@ export declare class KoolbaseAuth {
|
|
|
55
55
|
* delegates to {@link authRequest} with includeAuth=true.
|
|
56
56
|
*/
|
|
57
57
|
private authedRequest;
|
|
58
|
+
/**
|
|
59
|
+
* Replace the user inside the current session and persist it.
|
|
60
|
+
*
|
|
61
|
+
* A profile update or a fresh fetch changes the user, not the tokens.
|
|
62
|
+
* Without this the new values live only in the returned object: the cached
|
|
63
|
+
* `currentUser` stays stale, anything bound to `onAuthStateChange` never
|
|
64
|
+
* hears about the change, and a reload restores the old name.
|
|
65
|
+
*
|
|
66
|
+
* No session means nobody is signed in, and there is nothing to update —
|
|
67
|
+
* the caller's own request would have failed first.
|
|
68
|
+
*/
|
|
69
|
+
private updateUserAndPersist;
|
|
58
70
|
private setSessionInternal;
|
|
59
71
|
/**
|
|
60
72
|
* Discards the stored session without contacting the server.
|
|
@@ -191,6 +203,92 @@ export declare class KoolbaseAuth {
|
|
|
191
203
|
* taking an email address — otherwise this would be an open mail relay.
|
|
192
204
|
*/
|
|
193
205
|
resendVerificationEmail(): Promise<ResendVerificationResult>;
|
|
206
|
+
/**
|
|
207
|
+
* The signed-in user, fetched fresh from the server.
|
|
208
|
+
*
|
|
209
|
+
* `currentUser` returns what was cached at sign-in; this asks. Use it when
|
|
210
|
+
* something may have changed the user server-side — a verification
|
|
211
|
+
* completing, a profile updated from another device — and persist the
|
|
212
|
+
* result so the cached copy stops being stale.
|
|
213
|
+
*/
|
|
214
|
+
getCurrentUser(): Promise<KoolbaseUser>;
|
|
215
|
+
/**
|
|
216
|
+
* Update the signed-in user's profile.
|
|
217
|
+
*
|
|
218
|
+
* Only the fields given are sent, so passing one leaves the other
|
|
219
|
+
* untouched. The updated user is persisted, so `currentUser` reflects it
|
|
220
|
+
* without a refetch.
|
|
221
|
+
*/
|
|
222
|
+
updateProfile(params: {
|
|
223
|
+
fullName?: string;
|
|
224
|
+
avatarUrl?: string;
|
|
225
|
+
}): Promise<KoolbaseUser>;
|
|
226
|
+
/**
|
|
227
|
+
* Change the signed-in user's password.
|
|
228
|
+
*
|
|
229
|
+
* The current password is required: a stolen session should not be enough
|
|
230
|
+
* to lock the real owner out. A wrong one throws
|
|
231
|
+
* CurrentPasswordIncorrectError — the server returns `invalid_password`,
|
|
232
|
+
* which reads like a rejected new password and means the opposite.
|
|
233
|
+
*
|
|
234
|
+
* An account that signed up through Google or Apple has no password to
|
|
235
|
+
* change and gets the same error, deliberately, so a caller cannot probe
|
|
236
|
+
* which sign-in methods an account has.
|
|
237
|
+
*/
|
|
238
|
+
changePassword(params: {
|
|
239
|
+
currentPassword: string;
|
|
240
|
+
newPassword: string;
|
|
241
|
+
}): Promise<void>;
|
|
242
|
+
/**
|
|
243
|
+
* Delete the signed-in user's account, and sign out.
|
|
244
|
+
*
|
|
245
|
+
* Deletes the caller only — there is no user id to pass, which is the
|
|
246
|
+
* security property. The server removes the auth record and every session
|
|
247
|
+
* on every device, then fires an `auth.user.deleted` trigger.
|
|
248
|
+
*
|
|
249
|
+
* It does NOT delete the account's records in your collections. Koolbase
|
|
250
|
+
* cannot know whether a row should be deleted, anonymised, or kept for a
|
|
251
|
+
* statutory retention period. Subscribe a Function to `auth.user.deleted`
|
|
252
|
+
* and clean up there — it runs server-side, so a tab closing mid-flow
|
|
253
|
+
* cannot strand anything.
|
|
254
|
+
*/
|
|
255
|
+
deleteAccount(): Promise<void>;
|
|
256
|
+
/**
|
|
257
|
+
* Every session the signed-in user has — a "where you're signed in" list.
|
|
258
|
+
*
|
|
259
|
+
* Newest first is not guaranteed; sort by createdAt if the order matters.
|
|
260
|
+
* The entry with `isCurrent` is this device.
|
|
261
|
+
*/
|
|
262
|
+
listSessions(): Promise<KoolbaseSessionInfo[]>;
|
|
263
|
+
/**
|
|
264
|
+
* Sign out one session by id.
|
|
265
|
+
*
|
|
266
|
+
* Revoking the current one ends this session too — the server does not
|
|
267
|
+
* refuse it, and the local session is cleared here so the app does not keep
|
|
268
|
+
* a token the server has dropped. Use logout() when that is what you mean.
|
|
269
|
+
*/
|
|
270
|
+
revokeSession(sessionId: string): Promise<void>;
|
|
271
|
+
/**
|
|
272
|
+
* Sign out every OTHER session, keeping this one.
|
|
273
|
+
*
|
|
274
|
+
* What "sign out my other devices" means after a lost phone. Returns how
|
|
275
|
+
* many were ended, so an app can say so rather than guess.
|
|
276
|
+
*/
|
|
277
|
+
revokeAllOtherSessions(): Promise<number>;
|
|
278
|
+
/**
|
|
279
|
+
* What has happened to this account — sign-ins, failures, lockouts,
|
|
280
|
+
* password changes.
|
|
281
|
+
*
|
|
282
|
+
* For a "recent security activity" screen. The server sanitizes each
|
|
283
|
+
* event, so this carries what that event type is allowed to say and
|
|
284
|
+
* nothing more.
|
|
285
|
+
*
|
|
286
|
+
* @param limit up to 200; the server caps it there.
|
|
287
|
+
*/
|
|
288
|
+
auditLog(options?: {
|
|
289
|
+
limit?: number;
|
|
290
|
+
offset?: number;
|
|
291
|
+
}): Promise<KoolbaseAuditPage>;
|
|
194
292
|
unlock(token: string): Promise<void>;
|
|
195
293
|
get currentUser(): KoolbaseUser | null;
|
|
196
294
|
get accessToken(): string | null;
|
package/dist/cjs/auth.js
CHANGED
|
@@ -133,6 +133,22 @@ class KoolbaseAuth {
|
|
|
133
133
|
return this.authRequest(path, { ...options, includeAuth: true });
|
|
134
134
|
}
|
|
135
135
|
// ─── Internal session lifecycle ─────────────────────────────────────────
|
|
136
|
+
/**
|
|
137
|
+
* Replace the user inside the current session and persist it.
|
|
138
|
+
*
|
|
139
|
+
* A profile update or a fresh fetch changes the user, not the tokens.
|
|
140
|
+
* Without this the new values live only in the returned object: the cached
|
|
141
|
+
* `currentUser` stays stale, anything bound to `onAuthStateChange` never
|
|
142
|
+
* hears about the change, and a reload restores the old name.
|
|
143
|
+
*
|
|
144
|
+
* No session means nobody is signed in, and there is nothing to update —
|
|
145
|
+
* the caller's own request would have failed first.
|
|
146
|
+
*/
|
|
147
|
+
async updateUserAndPersist(user) {
|
|
148
|
+
if (!this.session)
|
|
149
|
+
return;
|
|
150
|
+
await this.setSessionInternal({ ...this.session, user });
|
|
151
|
+
}
|
|
136
152
|
async setSessionInternal(session) {
|
|
137
153
|
this.session = session;
|
|
138
154
|
if (this.storage) {
|
|
@@ -586,6 +602,188 @@ class KoolbaseAuth {
|
|
|
586
602
|
cooldownUntil: body.cooldown_until ? new Date(body.cooldown_until) : null,
|
|
587
603
|
};
|
|
588
604
|
}
|
|
605
|
+
/**
|
|
606
|
+
* The signed-in user, fetched fresh from the server.
|
|
607
|
+
*
|
|
608
|
+
* `currentUser` returns what was cached at sign-in; this asks. Use it when
|
|
609
|
+
* something may have changed the user server-side — a verification
|
|
610
|
+
* completing, a profile updated from another device — and persist the
|
|
611
|
+
* result so the cached copy stops being stale.
|
|
612
|
+
*/
|
|
613
|
+
async getCurrentUser() {
|
|
614
|
+
const res = await this.authRequest('/v1/sdk/auth/me', {
|
|
615
|
+
method: 'GET',
|
|
616
|
+
includeAuth: true,
|
|
617
|
+
});
|
|
618
|
+
await this.checkResponse(res);
|
|
619
|
+
const user = this.mapUser(await res.json());
|
|
620
|
+
await this.updateUserAndPersist(user);
|
|
621
|
+
return user;
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* Update the signed-in user's profile.
|
|
625
|
+
*
|
|
626
|
+
* Only the fields given are sent, so passing one leaves the other
|
|
627
|
+
* untouched. The updated user is persisted, so `currentUser` reflects it
|
|
628
|
+
* without a refetch.
|
|
629
|
+
*/
|
|
630
|
+
async updateProfile(params) {
|
|
631
|
+
const body = {};
|
|
632
|
+
if (params.fullName !== undefined)
|
|
633
|
+
body.full_name = params.fullName;
|
|
634
|
+
if (params.avatarUrl !== undefined)
|
|
635
|
+
body.avatar_url = params.avatarUrl;
|
|
636
|
+
const res = await this.authRequest('/v1/sdk/auth/me', {
|
|
637
|
+
method: 'PATCH',
|
|
638
|
+
body,
|
|
639
|
+
includeAuth: true,
|
|
640
|
+
});
|
|
641
|
+
await this.checkResponse(res);
|
|
642
|
+
const user = this.mapUser(await res.json());
|
|
643
|
+
await this.updateUserAndPersist(user);
|
|
644
|
+
return user;
|
|
645
|
+
}
|
|
646
|
+
/**
|
|
647
|
+
* Change the signed-in user's password.
|
|
648
|
+
*
|
|
649
|
+
* The current password is required: a stolen session should not be enough
|
|
650
|
+
* to lock the real owner out. A wrong one throws
|
|
651
|
+
* CurrentPasswordIncorrectError — the server returns `invalid_password`,
|
|
652
|
+
* which reads like a rejected new password and means the opposite.
|
|
653
|
+
*
|
|
654
|
+
* An account that signed up through Google or Apple has no password to
|
|
655
|
+
* change and gets the same error, deliberately, so a caller cannot probe
|
|
656
|
+
* which sign-in methods an account has.
|
|
657
|
+
*/
|
|
658
|
+
async changePassword(params) {
|
|
659
|
+
if (params.newPassword.length < 8)
|
|
660
|
+
throw new auth_errors_js_1.WeakPasswordError();
|
|
661
|
+
const res = await this.authRequest('/v1/sdk/auth/me/password', {
|
|
662
|
+
method: 'PATCH',
|
|
663
|
+
body: {
|
|
664
|
+
current_password: params.currentPassword,
|
|
665
|
+
new_password: params.newPassword,
|
|
666
|
+
},
|
|
667
|
+
includeAuth: true,
|
|
668
|
+
});
|
|
669
|
+
await this.checkResponse(res);
|
|
670
|
+
}
|
|
671
|
+
/**
|
|
672
|
+
* Delete the signed-in user's account, and sign out.
|
|
673
|
+
*
|
|
674
|
+
* Deletes the caller only — there is no user id to pass, which is the
|
|
675
|
+
* security property. The server removes the auth record and every session
|
|
676
|
+
* on every device, then fires an `auth.user.deleted` trigger.
|
|
677
|
+
*
|
|
678
|
+
* It does NOT delete the account's records in your collections. Koolbase
|
|
679
|
+
* cannot know whether a row should be deleted, anonymised, or kept for a
|
|
680
|
+
* statutory retention period. Subscribe a Function to `auth.user.deleted`
|
|
681
|
+
* and clean up there — it runs server-side, so a tab closing mid-flow
|
|
682
|
+
* cannot strand anything.
|
|
683
|
+
*/
|
|
684
|
+
async deleteAccount() {
|
|
685
|
+
const res = await this.authRequest('/v1/sdk/auth/me', {
|
|
686
|
+
method: 'DELETE',
|
|
687
|
+
includeAuth: true,
|
|
688
|
+
});
|
|
689
|
+
await this.checkResponse(res);
|
|
690
|
+
await this.clearSessionInternal();
|
|
691
|
+
}
|
|
692
|
+
/**
|
|
693
|
+
* Every session the signed-in user has — a "where you're signed in" list.
|
|
694
|
+
*
|
|
695
|
+
* Newest first is not guaranteed; sort by createdAt if the order matters.
|
|
696
|
+
* The entry with `isCurrent` is this device.
|
|
697
|
+
*/
|
|
698
|
+
async listSessions() {
|
|
699
|
+
const res = await this.authRequest('/v1/sdk/auth/sessions', {
|
|
700
|
+
method: 'GET',
|
|
701
|
+
includeAuth: true,
|
|
702
|
+
});
|
|
703
|
+
await this.checkResponse(res);
|
|
704
|
+
const body = (await res.json());
|
|
705
|
+
return (body.sessions ?? []).map((s) => {
|
|
706
|
+
const r = s;
|
|
707
|
+
return {
|
|
708
|
+
id: String(r.id),
|
|
709
|
+
ip: r.ip,
|
|
710
|
+
userAgent: r.user_agent,
|
|
711
|
+
deviceLabel: r.device_label,
|
|
712
|
+
createdAt: String(r.created_at),
|
|
713
|
+
expiresAt: String(r.expires_at),
|
|
714
|
+
isCurrent: Boolean(r.is_current),
|
|
715
|
+
};
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
/**
|
|
719
|
+
* Sign out one session by id.
|
|
720
|
+
*
|
|
721
|
+
* Revoking the current one ends this session too — the server does not
|
|
722
|
+
* refuse it, and the local session is cleared here so the app does not keep
|
|
723
|
+
* a token the server has dropped. Use logout() when that is what you mean.
|
|
724
|
+
*/
|
|
725
|
+
async revokeSession(sessionId) {
|
|
726
|
+
const res = await this.authRequest(`/v1/sdk/auth/sessions/${encodeURIComponent(sessionId)}/revoke`, { method: 'POST', includeAuth: true });
|
|
727
|
+
await this.checkResponse(res);
|
|
728
|
+
// Revoking your own session leaves a token the server has dropped. The
|
|
729
|
+
// SDK cannot tell from the response which one it was, and re-listing to
|
|
730
|
+
// find out would cost a request on every revoke — so it asks the only
|
|
731
|
+
// party that already knows: the next request. If it was this session,
|
|
732
|
+
// that request 401s and the normal refresh path clears it.
|
|
733
|
+
//
|
|
734
|
+
// A UI listing sessions knows which row is current from isCurrent, and
|
|
735
|
+
// should call logout() rather than revokeSession() for that one, which
|
|
736
|
+
// is both clearer to the user and one fewer round trip.
|
|
737
|
+
}
|
|
738
|
+
/**
|
|
739
|
+
* Sign out every OTHER session, keeping this one.
|
|
740
|
+
*
|
|
741
|
+
* What "sign out my other devices" means after a lost phone. Returns how
|
|
742
|
+
* many were ended, so an app can say so rather than guess.
|
|
743
|
+
*/
|
|
744
|
+
async revokeAllOtherSessions() {
|
|
745
|
+
const res = await this.authRequest('/v1/sdk/auth/sessions/revoke-all', {
|
|
746
|
+
method: 'POST',
|
|
747
|
+
includeAuth: true,
|
|
748
|
+
});
|
|
749
|
+
await this.checkResponse(res);
|
|
750
|
+
const body = (await res.json().catch(() => ({})));
|
|
751
|
+
return body.revoked_count ?? 0;
|
|
752
|
+
}
|
|
753
|
+
/**
|
|
754
|
+
* What has happened to this account — sign-ins, failures, lockouts,
|
|
755
|
+
* password changes.
|
|
756
|
+
*
|
|
757
|
+
* For a "recent security activity" screen. The server sanitizes each
|
|
758
|
+
* event, so this carries what that event type is allowed to say and
|
|
759
|
+
* nothing more.
|
|
760
|
+
*
|
|
761
|
+
* @param limit up to 200; the server caps it there.
|
|
762
|
+
*/
|
|
763
|
+
async auditLog(options) {
|
|
764
|
+
const params = new URLSearchParams();
|
|
765
|
+
if (options?.limit !== undefined)
|
|
766
|
+
params.set('limit', String(options.limit));
|
|
767
|
+
if (options?.offset !== undefined)
|
|
768
|
+
params.set('offset', String(options.offset));
|
|
769
|
+
const qs = params.toString();
|
|
770
|
+
const res = await this.authRequest(`/v1/sdk/auth/audit${qs ? `?${qs}` : ''}`, { method: 'GET', includeAuth: true });
|
|
771
|
+
await this.checkResponse(res);
|
|
772
|
+
const body = (await res.json());
|
|
773
|
+
return {
|
|
774
|
+
events: (body.events ?? []).map((e) => ({
|
|
775
|
+
id: String(e.id),
|
|
776
|
+
eventType: String(e.event_type),
|
|
777
|
+
occurredAt: String(e.occurred_at),
|
|
778
|
+
ip: e.ip,
|
|
779
|
+
userAgent: e.user_agent,
|
|
780
|
+
eventData: e.event_data ?? {},
|
|
781
|
+
})),
|
|
782
|
+
total: Number(body.total ?? 0),
|
|
783
|
+
limit: Number(body.limit ?? 0),
|
|
784
|
+
offset: Number(body.offset ?? 0),
|
|
785
|
+
};
|
|
786
|
+
}
|
|
589
787
|
async unlock(token) {
|
|
590
788
|
const res = await this.authRequest('/v1/sdk/auth/unlock', {
|
|
591
789
|
method: 'POST',
|
package/dist/cjs/database.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { KoolbaseAggregateRequest, KoolbaseAggregateResult } from './aggregate.js';
|
|
1
2
|
import { KoolbaseConflict } from './conflict.js';
|
|
2
3
|
import { PendingWrite } from './pending-write.js';
|
|
3
4
|
import { KoolbaseConfig, KoolbaseRecord, QueryOptions, QueryResult, UpsertResult, BatchOp, BatchResult, KoolbaseVector, SemanticSearchResult, SearchMode } from './types.js';
|
|
@@ -127,6 +128,28 @@ export declare class KoolbaseDatabase {
|
|
|
127
128
|
* moment matters. Snapshot, not a live handle; per-user.
|
|
128
129
|
*/
|
|
129
130
|
pendingWrites(): Promise<PendingWrite[]>;
|
|
131
|
+
/**
|
|
132
|
+
* Count and total over a whole collection, with the read rule applied
|
|
133
|
+
* inside the query.
|
|
134
|
+
*
|
|
135
|
+
* Not a paged query you add up yourself: the server aggregates the entire
|
|
136
|
+
* authorized set. And never a bare number — collections are schemaless, so
|
|
137
|
+
* a sum that quietly skipped three malformed rows would be a wrong number
|
|
138
|
+
* that looks right. Check `accounting` for what contributed.
|
|
139
|
+
*
|
|
140
|
+
* ```ts
|
|
141
|
+
* const r = await db.aggregate({
|
|
142
|
+
* collection: 'orders',
|
|
143
|
+
* groupBy: { field: 'created_at', bucket: 'month', timezone: 'Africa/Accra' },
|
|
144
|
+
* measures: [{ aggregate: 'sum', field: 'total', as: 'revenue' }],
|
|
145
|
+
* });
|
|
146
|
+
* ```
|
|
147
|
+
*
|
|
148
|
+
* A calendar bucket requires a timezone and the types enforce it: midnight
|
|
149
|
+
* means nothing without one, and Accra and UTC disagree about which day a
|
|
150
|
+
* 23:30 sale belongs to.
|
|
151
|
+
*/
|
|
152
|
+
aggregate(request: KoolbaseAggregateRequest): Promise<KoolbaseAggregateResult>;
|
|
130
153
|
conflicts(): Promise<KoolbaseConflict[]>;
|
|
131
154
|
/**
|
|
132
155
|
* Resolves by id, reloading the stored conflict first.
|