@koolbase/core 11.3.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.
@@ -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 });
@@ -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', 'email_taken');
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', 'user_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', 'session_expired');
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', 'unlock_token_invalid');
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', 'otp_rate_limit');
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', 'phone_taken');
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', 'sms_config_missing');
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', 'apple_not_configured');
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', 'invalid_apple_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.', 'apple_email_required');
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', 'google_not_configured');
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', 'invalid_google_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.', 'google_email_required');
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
  }
@@ -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.
@@ -154,6 +166,21 @@ export declare class KoolbaseAuth {
154
166
  private _doRefresh;
155
167
  logout(): Promise<boolean>;
156
168
  forgotPassword(email: string): Promise<void>;
169
+ /**
170
+ * Ask for a new verification email, with no session.
171
+ *
172
+ * For the state a project with verified contact required creates:
173
+ * registered, unverified, refused at login, and holding a link that may
174
+ * never have arrived or has since expired. `resendVerificationEmail()`
175
+ * cannot help there — it needs a session the user cannot get.
176
+ *
177
+ * Returns nothing, and throws only on a malformed request, because the
178
+ * server answers identically whether the address has an account, has none,
179
+ * or is already verified. Anything else would let anyone discover who has
180
+ * signed up. So there is no "we sent it" to report: show the same "check
181
+ * your email" either way.
182
+ */
183
+ resendVerificationEmailToAddress(email: string): Promise<void>;
157
184
  resetPassword(token: string, password: string): Promise<void>;
158
185
  /**
159
186
  * Complete email verification with a token from a verification link.
@@ -176,6 +203,92 @@ export declare class KoolbaseAuth {
176
203
  * taking an email address — otherwise this would be an open mail relay.
177
204
  */
178
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>;
179
292
  unlock(token: string): Promise<void>;
180
293
  get currentUser(): KoolbaseUser | null;
181
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) {
@@ -519,6 +535,27 @@ class KoolbaseAuth {
519
535
  });
520
536
  await this.checkResponse(res);
521
537
  }
538
+ /**
539
+ * Ask for a new verification email, with no session.
540
+ *
541
+ * For the state a project with verified contact required creates:
542
+ * registered, unverified, refused at login, and holding a link that may
543
+ * never have arrived or has since expired. `resendVerificationEmail()`
544
+ * cannot help there — it needs a session the user cannot get.
545
+ *
546
+ * Returns nothing, and throws only on a malformed request, because the
547
+ * server answers identically whether the address has an account, has none,
548
+ * or is already verified. Anything else would let anyone discover who has
549
+ * signed up. So there is no "we sent it" to report: show the same "check
550
+ * your email" either way.
551
+ */
552
+ async resendVerificationEmailToAddress(email) {
553
+ const res = await this.authRequest('/v1/sdk/auth/resend-verification/by-email', {
554
+ method: 'POST',
555
+ body: { email },
556
+ });
557
+ await this.checkResponse(res);
558
+ }
522
559
  async resetPassword(token, password) {
523
560
  const res = await this.authRequest('/v1/sdk/auth/password-reset/confirm', {
524
561
  method: 'POST',
@@ -565,6 +602,188 @@ class KoolbaseAuth {
565
602
  cooldownUntil: body.cooldown_until ? new Date(body.cooldown_until) : null,
566
603
  };
567
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
+ }
568
787
  async unlock(token) {
569
788
  const res = await this.authRequest('/v1/sdk/auth/unlock', {
570
789
  method: 'POST',
@@ -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.