@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
package/dist/esm/auth.js
CHANGED
|
@@ -130,6 +130,22 @@ export class KoolbaseAuth {
|
|
|
130
130
|
return this.authRequest(path, { ...options, includeAuth: true });
|
|
131
131
|
}
|
|
132
132
|
// ─── Internal session lifecycle ─────────────────────────────────────────
|
|
133
|
+
/**
|
|
134
|
+
* Replace the user inside the current session and persist it.
|
|
135
|
+
*
|
|
136
|
+
* A profile update or a fresh fetch changes the user, not the tokens.
|
|
137
|
+
* Without this the new values live only in the returned object: the cached
|
|
138
|
+
* `currentUser` stays stale, anything bound to `onAuthStateChange` never
|
|
139
|
+
* hears about the change, and a reload restores the old name.
|
|
140
|
+
*
|
|
141
|
+
* No session means nobody is signed in, and there is nothing to update —
|
|
142
|
+
* the caller's own request would have failed first.
|
|
143
|
+
*/
|
|
144
|
+
async updateUserAndPersist(user) {
|
|
145
|
+
if (!this.session)
|
|
146
|
+
return;
|
|
147
|
+
await this.setSessionInternal({ ...this.session, user });
|
|
148
|
+
}
|
|
133
149
|
async setSessionInternal(session) {
|
|
134
150
|
this.session = session;
|
|
135
151
|
if (this.storage) {
|
|
@@ -583,6 +599,188 @@ export class KoolbaseAuth {
|
|
|
583
599
|
cooldownUntil: body.cooldown_until ? new Date(body.cooldown_until) : null,
|
|
584
600
|
};
|
|
585
601
|
}
|
|
602
|
+
/**
|
|
603
|
+
* The signed-in user, fetched fresh from the server.
|
|
604
|
+
*
|
|
605
|
+
* `currentUser` returns what was cached at sign-in; this asks. Use it when
|
|
606
|
+
* something may have changed the user server-side — a verification
|
|
607
|
+
* completing, a profile updated from another device — and persist the
|
|
608
|
+
* result so the cached copy stops being stale.
|
|
609
|
+
*/
|
|
610
|
+
async getCurrentUser() {
|
|
611
|
+
const res = await this.authRequest('/v1/sdk/auth/me', {
|
|
612
|
+
method: 'GET',
|
|
613
|
+
includeAuth: true,
|
|
614
|
+
});
|
|
615
|
+
await this.checkResponse(res);
|
|
616
|
+
const user = this.mapUser(await res.json());
|
|
617
|
+
await this.updateUserAndPersist(user);
|
|
618
|
+
return user;
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Update the signed-in user's profile.
|
|
622
|
+
*
|
|
623
|
+
* Only the fields given are sent, so passing one leaves the other
|
|
624
|
+
* untouched. The updated user is persisted, so `currentUser` reflects it
|
|
625
|
+
* without a refetch.
|
|
626
|
+
*/
|
|
627
|
+
async updateProfile(params) {
|
|
628
|
+
const body = {};
|
|
629
|
+
if (params.fullName !== undefined)
|
|
630
|
+
body.full_name = params.fullName;
|
|
631
|
+
if (params.avatarUrl !== undefined)
|
|
632
|
+
body.avatar_url = params.avatarUrl;
|
|
633
|
+
const res = await this.authRequest('/v1/sdk/auth/me', {
|
|
634
|
+
method: 'PATCH',
|
|
635
|
+
body,
|
|
636
|
+
includeAuth: true,
|
|
637
|
+
});
|
|
638
|
+
await this.checkResponse(res);
|
|
639
|
+
const user = this.mapUser(await res.json());
|
|
640
|
+
await this.updateUserAndPersist(user);
|
|
641
|
+
return user;
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Change the signed-in user's password.
|
|
645
|
+
*
|
|
646
|
+
* The current password is required: a stolen session should not be enough
|
|
647
|
+
* to lock the real owner out. A wrong one throws
|
|
648
|
+
* CurrentPasswordIncorrectError — the server returns `invalid_password`,
|
|
649
|
+
* which reads like a rejected new password and means the opposite.
|
|
650
|
+
*
|
|
651
|
+
* An account that signed up through Google or Apple has no password to
|
|
652
|
+
* change and gets the same error, deliberately, so a caller cannot probe
|
|
653
|
+
* which sign-in methods an account has.
|
|
654
|
+
*/
|
|
655
|
+
async changePassword(params) {
|
|
656
|
+
if (params.newPassword.length < 8)
|
|
657
|
+
throw new WeakPasswordError();
|
|
658
|
+
const res = await this.authRequest('/v1/sdk/auth/me/password', {
|
|
659
|
+
method: 'PATCH',
|
|
660
|
+
body: {
|
|
661
|
+
current_password: params.currentPassword,
|
|
662
|
+
new_password: params.newPassword,
|
|
663
|
+
},
|
|
664
|
+
includeAuth: true,
|
|
665
|
+
});
|
|
666
|
+
await this.checkResponse(res);
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* Delete the signed-in user's account, and sign out.
|
|
670
|
+
*
|
|
671
|
+
* Deletes the caller only — there is no user id to pass, which is the
|
|
672
|
+
* security property. The server removes the auth record and every session
|
|
673
|
+
* on every device, then fires an `auth.user.deleted` trigger.
|
|
674
|
+
*
|
|
675
|
+
* It does NOT delete the account's records in your collections. Koolbase
|
|
676
|
+
* cannot know whether a row should be deleted, anonymised, or kept for a
|
|
677
|
+
* statutory retention period. Subscribe a Function to `auth.user.deleted`
|
|
678
|
+
* and clean up there — it runs server-side, so a tab closing mid-flow
|
|
679
|
+
* cannot strand anything.
|
|
680
|
+
*/
|
|
681
|
+
async deleteAccount() {
|
|
682
|
+
const res = await this.authRequest('/v1/sdk/auth/me', {
|
|
683
|
+
method: 'DELETE',
|
|
684
|
+
includeAuth: true,
|
|
685
|
+
});
|
|
686
|
+
await this.checkResponse(res);
|
|
687
|
+
await this.clearSessionInternal();
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* Every session the signed-in user has — a "where you're signed in" list.
|
|
691
|
+
*
|
|
692
|
+
* Newest first is not guaranteed; sort by createdAt if the order matters.
|
|
693
|
+
* The entry with `isCurrent` is this device.
|
|
694
|
+
*/
|
|
695
|
+
async listSessions() {
|
|
696
|
+
const res = await this.authRequest('/v1/sdk/auth/sessions', {
|
|
697
|
+
method: 'GET',
|
|
698
|
+
includeAuth: true,
|
|
699
|
+
});
|
|
700
|
+
await this.checkResponse(res);
|
|
701
|
+
const body = (await res.json());
|
|
702
|
+
return (body.sessions ?? []).map((s) => {
|
|
703
|
+
const r = s;
|
|
704
|
+
return {
|
|
705
|
+
id: String(r.id),
|
|
706
|
+
ip: r.ip,
|
|
707
|
+
userAgent: r.user_agent,
|
|
708
|
+
deviceLabel: r.device_label,
|
|
709
|
+
createdAt: String(r.created_at),
|
|
710
|
+
expiresAt: String(r.expires_at),
|
|
711
|
+
isCurrent: Boolean(r.is_current),
|
|
712
|
+
};
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* Sign out one session by id.
|
|
717
|
+
*
|
|
718
|
+
* Revoking the current one ends this session too — the server does not
|
|
719
|
+
* refuse it, and the local session is cleared here so the app does not keep
|
|
720
|
+
* a token the server has dropped. Use logout() when that is what you mean.
|
|
721
|
+
*/
|
|
722
|
+
async revokeSession(sessionId) {
|
|
723
|
+
const res = await this.authRequest(`/v1/sdk/auth/sessions/${encodeURIComponent(sessionId)}/revoke`, { method: 'POST', includeAuth: true });
|
|
724
|
+
await this.checkResponse(res);
|
|
725
|
+
// Revoking your own session leaves a token the server has dropped. The
|
|
726
|
+
// SDK cannot tell from the response which one it was, and re-listing to
|
|
727
|
+
// find out would cost a request on every revoke — so it asks the only
|
|
728
|
+
// party that already knows: the next request. If it was this session,
|
|
729
|
+
// that request 401s and the normal refresh path clears it.
|
|
730
|
+
//
|
|
731
|
+
// A UI listing sessions knows which row is current from isCurrent, and
|
|
732
|
+
// should call logout() rather than revokeSession() for that one, which
|
|
733
|
+
// is both clearer to the user and one fewer round trip.
|
|
734
|
+
}
|
|
735
|
+
/**
|
|
736
|
+
* Sign out every OTHER session, keeping this one.
|
|
737
|
+
*
|
|
738
|
+
* What "sign out my other devices" means after a lost phone. Returns how
|
|
739
|
+
* many were ended, so an app can say so rather than guess.
|
|
740
|
+
*/
|
|
741
|
+
async revokeAllOtherSessions() {
|
|
742
|
+
const res = await this.authRequest('/v1/sdk/auth/sessions/revoke-all', {
|
|
743
|
+
method: 'POST',
|
|
744
|
+
includeAuth: true,
|
|
745
|
+
});
|
|
746
|
+
await this.checkResponse(res);
|
|
747
|
+
const body = (await res.json().catch(() => ({})));
|
|
748
|
+
return body.revoked_count ?? 0;
|
|
749
|
+
}
|
|
750
|
+
/**
|
|
751
|
+
* What has happened to this account — sign-ins, failures, lockouts,
|
|
752
|
+
* password changes.
|
|
753
|
+
*
|
|
754
|
+
* For a "recent security activity" screen. The server sanitizes each
|
|
755
|
+
* event, so this carries what that event type is allowed to say and
|
|
756
|
+
* nothing more.
|
|
757
|
+
*
|
|
758
|
+
* @param limit up to 200; the server caps it there.
|
|
759
|
+
*/
|
|
760
|
+
async auditLog(options) {
|
|
761
|
+
const params = new URLSearchParams();
|
|
762
|
+
if (options?.limit !== undefined)
|
|
763
|
+
params.set('limit', String(options.limit));
|
|
764
|
+
if (options?.offset !== undefined)
|
|
765
|
+
params.set('offset', String(options.offset));
|
|
766
|
+
const qs = params.toString();
|
|
767
|
+
const res = await this.authRequest(`/v1/sdk/auth/audit${qs ? `?${qs}` : ''}`, { method: 'GET', includeAuth: true });
|
|
768
|
+
await this.checkResponse(res);
|
|
769
|
+
const body = (await res.json());
|
|
770
|
+
return {
|
|
771
|
+
events: (body.events ?? []).map((e) => ({
|
|
772
|
+
id: String(e.id),
|
|
773
|
+
eventType: String(e.event_type),
|
|
774
|
+
occurredAt: String(e.occurred_at),
|
|
775
|
+
ip: e.ip,
|
|
776
|
+
userAgent: e.user_agent,
|
|
777
|
+
eventData: e.event_data ?? {},
|
|
778
|
+
})),
|
|
779
|
+
total: Number(body.total ?? 0),
|
|
780
|
+
limit: Number(body.limit ?? 0),
|
|
781
|
+
offset: Number(body.offset ?? 0),
|
|
782
|
+
};
|
|
783
|
+
}
|
|
586
784
|
async unlock(token) {
|
|
587
785
|
const res = await this.authRequest('/v1/sdk/auth/unlock', {
|
|
588
786
|
method: 'POST',
|
package/dist/esm/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.
|
package/dist/esm/database.js
CHANGED
|
@@ -421,6 +421,70 @@ export class KoolbaseDatabase {
|
|
|
421
421
|
const { pending } = await readOfflineState(userId);
|
|
422
422
|
return pending.map(toPendingWrite);
|
|
423
423
|
}
|
|
424
|
+
/**
|
|
425
|
+
* Count and total over a whole collection, with the read rule applied
|
|
426
|
+
* inside the query.
|
|
427
|
+
*
|
|
428
|
+
* Not a paged query you add up yourself: the server aggregates the entire
|
|
429
|
+
* authorized set. And never a bare number — collections are schemaless, so
|
|
430
|
+
* a sum that quietly skipped three malformed rows would be a wrong number
|
|
431
|
+
* that looks right. Check `accounting` for what contributed.
|
|
432
|
+
*
|
|
433
|
+
* ```ts
|
|
434
|
+
* const r = await db.aggregate({
|
|
435
|
+
* collection: 'orders',
|
|
436
|
+
* groupBy: { field: 'created_at', bucket: 'month', timezone: 'Africa/Accra' },
|
|
437
|
+
* measures: [{ aggregate: 'sum', field: 'total', as: 'revenue' }],
|
|
438
|
+
* });
|
|
439
|
+
* ```
|
|
440
|
+
*
|
|
441
|
+
* A calendar bucket requires a timezone and the types enforce it: midnight
|
|
442
|
+
* means nothing without one, and Accra and UTC disagree about which day a
|
|
443
|
+
* 23:30 sale belongs to.
|
|
444
|
+
*/
|
|
445
|
+
async aggregate(request) {
|
|
446
|
+
const raw = await this.request('POST', '/v1/sdk/db/aggregate', {
|
|
447
|
+
collection: request.collection,
|
|
448
|
+
...(request.where?.length ? { where: request.where } : {}),
|
|
449
|
+
...(request.groupBy
|
|
450
|
+
? {
|
|
451
|
+
group_by: {
|
|
452
|
+
field: request.groupBy.field,
|
|
453
|
+
...(request.groupBy.bucket
|
|
454
|
+
? {
|
|
455
|
+
bucket: request.groupBy.bucket,
|
|
456
|
+
timezone: request.groupBy.timezone,
|
|
457
|
+
}
|
|
458
|
+
: {}),
|
|
459
|
+
},
|
|
460
|
+
}
|
|
461
|
+
: {}),
|
|
462
|
+
measures: request.measures.map((m) => ({
|
|
463
|
+
aggregate: m.aggregate,
|
|
464
|
+
...(m.field ? { field: m.field } : {}),
|
|
465
|
+
as: m.as,
|
|
466
|
+
})),
|
|
467
|
+
});
|
|
468
|
+
const accounting = {};
|
|
469
|
+
for (const [name, a] of Object.entries(raw.accounting ?? {})) {
|
|
470
|
+
accounting[name] = {
|
|
471
|
+
counted: Number(a.counted ?? 0),
|
|
472
|
+
skipped: Number(a.skipped ?? 0),
|
|
473
|
+
skippedReason: a.skipped_reason,
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
return {
|
|
477
|
+
groups: (raw.groups ?? []).map((g) => ({
|
|
478
|
+
category: String(g.category ?? ''),
|
|
479
|
+
values: g.values ?? {},
|
|
480
|
+
})),
|
|
481
|
+
matched: Number(raw.matched ?? 0),
|
|
482
|
+
accounting,
|
|
483
|
+
// Explicitly boolean: a missing field must read as false, not
|
|
484
|
+
// undefined, or `if (!r.tooManyGroups)` passes on a refused request.
|
|
485
|
+
tooManyGroups: raw.too_many_groups === true,
|
|
486
|
+
};
|
|
487
|
+
}
|
|
424
488
|
async conflicts() {
|
|
425
489
|
const userId = this.requireUserId('the conflict list');
|
|
426
490
|
const { conflicts } = await readOfflineState(userId);
|
|
@@ -49,7 +49,13 @@ export class DeviceMetadata {
|
|
|
49
49
|
if (this.cached)
|
|
50
50
|
return this.cached;
|
|
51
51
|
const platform = getPlatform().info.os;
|
|
52
|
-
|
|
52
|
+
// 'unknown' rather than empty, matching appVersion above. A browser
|
|
53
|
+
// outside the handful browserVersion() recognises — Brave, Opera,
|
|
54
|
+
// Samsung Internet, an in-app webview — reports nothing, and an empty
|
|
55
|
+
// header stored against a session is indistinguishable from an SDK that
|
|
56
|
+
// never reported itself at all. 'unknown' says the SDK spoke and could
|
|
57
|
+
// not identify the host, which is a different and more useful fact.
|
|
58
|
+
const platformVersion = getPlatform().info.version || 'unknown';
|
|
53
59
|
const deviceLabel = await this.getOrCreateDeviceLabel();
|
|
54
60
|
// No User-Agent: a browser forbids setting it, and the same header set
|
|
55
61
|
// now goes out from both hosts. The platform and version below carry
|
package/dist/esm/index.d.ts
CHANGED
package/dist/esm/index.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// code installs the platform package, never this one directly.
|
|
6
6
|
export * from './types.js';
|
|
7
7
|
export * from './errors.js';
|
|
8
|
+
export * from './aggregate.js';
|
|
8
9
|
export * from './conflict.js';
|
|
9
10
|
export * from './pending-write.js';
|
|
10
11
|
export * from './function-errors.js';
|
package/dist/esm/types.d.ts
CHANGED
|
@@ -88,6 +88,64 @@ export type SignUpResult = {
|
|
|
88
88
|
user: KoolbaseUser;
|
|
89
89
|
session: null;
|
|
90
90
|
};
|
|
91
|
+
/**
|
|
92
|
+
* One active session — somewhere the user is signed in.
|
|
93
|
+
*
|
|
94
|
+
* Token hashes are never included; the server excludes them deliberately.
|
|
95
|
+
*/
|
|
96
|
+
export interface KoolbaseSessionInfo {
|
|
97
|
+
id: string;
|
|
98
|
+
/** Where it signed in from, when the server captured it. */
|
|
99
|
+
ip?: string;
|
|
100
|
+
/** What signed in. A browser sets this; native SDKs send their own. */
|
|
101
|
+
userAgent?: string;
|
|
102
|
+
/** A per-install label, where the SDK supplied one. */
|
|
103
|
+
deviceLabel?: string;
|
|
104
|
+
createdAt: string;
|
|
105
|
+
expiresAt: string;
|
|
106
|
+
/**
|
|
107
|
+
* True for the session making the request.
|
|
108
|
+
*
|
|
109
|
+
* A device list needs this: without it a user cannot tell which row is the
|
|
110
|
+
* phone in their hand, and "sign out everywhere else" has nothing to
|
|
111
|
+
* exclude.
|
|
112
|
+
*/
|
|
113
|
+
isCurrent: boolean;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* One thing that happened to this account.
|
|
117
|
+
*
|
|
118
|
+
* The server sanitizes these: `eventData` carries only the fields allowed
|
|
119
|
+
* for that event type, so a lockout can say which address was attempted
|
|
120
|
+
* while a successful login carries nothing extra.
|
|
121
|
+
*/
|
|
122
|
+
export interface KoolbaseAuditEvent {
|
|
123
|
+
id: string;
|
|
124
|
+
/**
|
|
125
|
+
* What happened. One of: auth.login.success, auth.login.failed,
|
|
126
|
+
* auth.account.locked, auth.account.unlocked, auth.session.created,
|
|
127
|
+
* auth.session.refreshed, auth.session.revoked, auth.password.changed,
|
|
128
|
+
* auth.user.verified.
|
|
129
|
+
*
|
|
130
|
+
* A string rather than a union: the server may add one, and an app that
|
|
131
|
+
* stops compiling because it did would be worse than one that shows an
|
|
132
|
+
* event it does not recognise.
|
|
133
|
+
*/
|
|
134
|
+
eventType: string;
|
|
135
|
+
occurredAt: string;
|
|
136
|
+
ip?: string;
|
|
137
|
+
userAgent?: string;
|
|
138
|
+
/** Whatever that event type is allowed to carry. Often empty. */
|
|
139
|
+
eventData: Record<string, unknown>;
|
|
140
|
+
}
|
|
141
|
+
/** A page of audit events, and how many there are in total. */
|
|
142
|
+
export interface KoolbaseAuditPage {
|
|
143
|
+
events: KoolbaseAuditEvent[];
|
|
144
|
+
/** Across all pages, so an app knows whether to offer "show more". */
|
|
145
|
+
total: number;
|
|
146
|
+
limit: number;
|
|
147
|
+
offset: number;
|
|
148
|
+
}
|
|
91
149
|
export interface ResendVerificationResult {
|
|
92
150
|
alreadyVerified: boolean;
|
|
93
151
|
/** When the link in the email stops working. Null when nothing was sent. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@koolbase/core",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "12.0.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",
|