@kerne/react 0.1.1 → 0.1.2

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/index.d.cts CHANGED
@@ -3,8 +3,10 @@ import React, { ReactNode, Component, ErrorInfo } from 'react';
3
3
  import { KerneConfig, JoinWaitlistParams, ValidateTokenResponse, ValidateCodeResponse } from '@kerne/server';
4
4
  export { JoinWaitlistParams, ValidateCodeResponse, ValidateTokenResponse, WaitlistEntry } from '@kerne/server';
5
5
  import * as _kerne_types from '@kerne/types';
6
- import { User, AuthResponse, EntitlementCheck, SubscriptionWithPlan, PublicPlan, Entitlements, UsageRecord, Subscription } from '@kerne/types';
7
- export { AuthResponse, EntitlementCheck, Entitlements, PublicPlan, Subscription, SubscriptionWithPlan, UsageRecord, User } from '@kerne/types';
6
+ import { User, AuthResponse, ActivationContext, AuthConfig, EntitlementCheck, SubscriptionWithPlan, PublicPlan, Entitlements, UsageRecord } from '@kerne/types';
7
+ export { ActivationContext, AuthConfig, AuthResponse, EntitlementCheck, Entitlements, PublicPlan, Subscription, SubscriptionWithPlan, UsageRecord, User } from '@kerne/types';
8
+ import { D as DeepPartial, K as KerneLocalization } from './i18n-Qketn1RF.cjs';
9
+ export { d as defaultLocalization } from './i18n-Qketn1RF.cjs';
8
10
 
9
11
  interface KerneReactConfig extends Omit<KerneConfig, 'secretKey'> {
10
12
  storage?: Storage;
@@ -16,7 +18,7 @@ interface KerneReactConfig extends Omit<KerneConfig, 'secretKey'> {
16
18
  refreshBuffer?: number;
17
19
  }
18
20
 
19
- type Listener$1 = () => void;
21
+ type Listener = () => void;
20
22
  declare class KerneClient {
21
23
  private kerne;
22
24
  private storage;
@@ -31,16 +33,43 @@ declare class KerneClient {
31
33
  private _refreshToken;
32
34
  private _expiresAt;
33
35
  private _isLoading;
36
+ private _authConfigPromise;
34
37
  readonly baseUrl: string;
35
38
  readonly appId: string;
36
39
  constructor(config: KerneReactConfig);
37
- subscribe(listener: Listener$1): () => void;
40
+ subscribe(listener: Listener): () => void;
38
41
  private notify;
39
42
  get user(): User | null;
40
43
  get token(): string | null;
41
44
  get isAuthenticated(): boolean;
42
45
  get isLoading(): boolean;
43
46
  private loadSession;
47
+ private getHandoffCodeFromUrl;
48
+ /**
49
+ * Strips only `kerne_handoff` from the URL, leaving the rest of the
50
+ * path/query (e.g. a `return_to` the host app added) untouched - and
51
+ * without adding a history entry, so back-navigation doesn't resurrect it.
52
+ */
53
+ private stripHandoffParamFromUrl;
54
+ /**
55
+ * Same shape as `refreshOnLoad`: a bootstrap-only path invoked once from
56
+ * `loadSession()` when a `kerne_handoff` code is present in the URL,
57
+ * entirely invisible to the host app - no code on their end required.
58
+ *
59
+ * Works no matter which page the browser lands on: `redirectUrl` is a hard
60
+ * navigation (`window.location.href`), so the destination reloads the SDK
61
+ * fresh and `loadSession()` reads the code straight off that page's own
62
+ * URL - there is no dependency on a specific "callback route", as long as
63
+ * `KerneProvider` wraps whatever the host renders there (normally true when
64
+ * it is mounted once at the app root).
65
+ *
66
+ * One retry on a transient failure (network blip, 5xx) - worth it because
67
+ * the code was never reached by the server in that case, so it is still
68
+ * unspent. A 4xx (invalid/expired/already-consumed) is not retried: the
69
+ * code is gone, retrying only delays the "logged out" outcome.
70
+ */
71
+ private exchangeHandoffOnLoad;
72
+ private refreshOnLoad;
44
73
  /**
45
74
  * `AuthResponse.user` is the minimal `{id, email, role}` claim set the
46
75
  * token endpoint returns, not the full profile (`User`) - so this always
@@ -76,10 +105,45 @@ declare class KerneClient {
76
105
  currentPassword: string;
77
106
  newPassword: string;
78
107
  }): Promise<void>;
108
+ /** Soft-deletes (deactivates) the caller's own account, then clears the local session. */
109
+ requestAccountDeletion(): Promise<void>;
79
110
  /** Forgot-password flow (logged out) - sends the reset email. */
80
- requestPasswordReset(email: string): Promise<void>;
111
+ requestPasswordReset(email: string, callbackUrl?: string): Promise<void>;
81
112
  /** Confirms a password reset with the token from the email. */
82
113
  confirmPasswordReset(token: string, password: string): Promise<void>;
114
+ /**
115
+ * Sends a magic-link email (logged out). Anti-enumeration: always resolves,
116
+ * regardless of whether the email is registered.
117
+ */
118
+ startPasswordless(email: string, callbackUrl?: string, invitation?: {
119
+ invitationToken?: string;
120
+ invitationCode?: string;
121
+ }): Promise<void>;
122
+ /** Completes a magic-link login using the `token` query param from the emailed link. */
123
+ loginWithMagicLink(token: string): Promise<AuthResponse>;
124
+ /** Read-only: which UI to render for an activation token, without consuming it. */
125
+ getActivationContext(token: string): Promise<ActivationContext>;
126
+ /** Completes activation (sets a password or confirms magic-link mode), then logs the user in. */
127
+ completeActivation(token: string, password?: string): Promise<AuthResponse>;
128
+ /**
129
+ * Mints a handoff code for the current session and redirects the browser
130
+ * to `url` with it attached (`?kerne_handoff=...`) - never the real
131
+ * token/refresh_token, which would otherwise leak into server logs,
132
+ * browser history, and third-party `Referer` headers on the landing page.
133
+ */
134
+ redirectWithSession(url: string): Promise<void>;
135
+ /**
136
+ * The tenant's public auth config - which sign-in methods to render, whether
137
+ * registration is open, whether to show Kerne branding.
138
+ *
139
+ * Deduped on the in-flight promise rather than the resolved value: the
140
+ * prebuilt forms all read this on mount, and two of them mounted together
141
+ * (a login screen with a register link prefetching) would otherwise fire two
142
+ * identical requests before either resolved. Cached for the client's
143
+ * lifetime - this config changes on the order of "the owner edited their
144
+ * settings", not per render.
145
+ */
146
+ getAuthConfig(): Promise<AuthConfig>;
83
147
  /**
84
148
  * Errors are not swallowed here (unlike the old implementation, which
85
149
  * caught everything and returned `false`) - a 401/500 must not be
@@ -115,7 +179,7 @@ declare class KerneClient {
115
179
  validateInvitationToken(token: string): Promise<ValidateTokenResponse>;
116
180
  /** Validate an invitation code before showing the registration form. */
117
181
  validateInvitationCode(code: string): Promise<ValidateCodeResponse>;
118
- sendVerificationEmail(verificationType?: 'code' | 'link'): Promise<void>;
182
+ sendVerificationEmail(verificationType?: 'code' | 'link', callbackUrl?: string): Promise<void>;
119
183
  verifyEmailWithCode(code: string): Promise<{
120
184
  success: boolean;
121
185
  needsTokenRefresh: boolean;
@@ -133,8 +197,16 @@ declare class KerneClient {
133
197
  declare const KerneContext: React.Context<KerneClient | null>;
134
198
  interface KerneProviderProps extends KerneReactConfig {
135
199
  children: React.ReactNode;
200
+ /**
201
+ * Overrides for the prebuilt components' copy, including the error-code
202
+ * messages. Set once here rather than per component - the strings are shared,
203
+ * and a per-component prop would mean re-passing the same dictionary to every
204
+ * screen. Merged group-by-group over the English defaults, so a partial
205
+ * override keeps everything it does not mention.
206
+ */
207
+ localization?: DeepPartial<KerneLocalization>;
136
208
  }
137
- declare function KerneProvider({ children, ...config }: KerneProviderProps): react_jsx_runtime.JSX.Element;
209
+ declare function KerneProvider({ children, localization, ...config }: KerneProviderProps): react_jsx_runtime.JSX.Element;
138
210
 
139
211
  /**
140
212
  * Access the Kerne client instance
@@ -149,7 +221,7 @@ declare function useClient(): KerneClient;
149
221
  * ```
150
222
  */
151
223
  declare function useAuth(): {
152
- user: User | null;
224
+ user: _kerne_types.User | null;
153
225
  token: string | null;
154
226
  isAuthenticated: boolean;
155
227
  isLoading: boolean;
@@ -165,20 +237,26 @@ declare function useAuth(): {
165
237
  invitationCode?: string;
166
238
  }) => Promise<_kerne_types.AuthResponse>;
167
239
  logout: () => void;
168
- refreshUser: () => Promise<User | null>;
240
+ refreshUser: () => Promise<_kerne_types.User | null>;
169
241
  refreshToken: () => Promise<_kerne_types.AuthResponse | null>;
170
242
  updateProfile: (params: {
171
243
  first_name?: string;
172
244
  last_name?: string;
173
245
  avatar?: string;
174
- }) => Promise<User>;
246
+ }) => Promise<_kerne_types.User>;
175
247
  updatePassword: (params: {
176
248
  currentPassword: string;
177
249
  newPassword: string;
178
250
  }) => Promise<void>;
179
- requestPasswordReset: (email: string) => Promise<void>;
251
+ requestAccountDeletion: () => Promise<void>;
252
+ requestPasswordReset: (email: string, callbackUrl?: string) => Promise<void>;
180
253
  confirmPasswordReset: (token: string, password: string) => Promise<void>;
181
- sendVerificationEmail: (verificationType?: "code" | "link") => Promise<void>;
254
+ startPasswordless: (email: string, callbackUrl?: string, invitation?: {
255
+ invitationToken?: string;
256
+ invitationCode?: string;
257
+ }) => Promise<void>;
258
+ loginWithMagicLink: (token: string) => Promise<_kerne_types.AuthResponse>;
259
+ sendVerificationEmail: (verificationType?: "code" | "link", callbackUrl?: string) => Promise<void>;
182
260
  verifyEmailWithCode: (code: string) => Promise<{
183
261
  success: boolean;
184
262
  needsTokenRefresh: boolean;
@@ -192,6 +270,9 @@ declare function useAuth(): {
192
270
  needsVerification: boolean;
193
271
  email: string;
194
272
  }>;
273
+ getActivationContext: (token: string) => Promise<_kerne_types.ActivationContext>;
274
+ completeActivation: (token: string, password?: string) => Promise<_kerne_types.AuthResponse>;
275
+ redirectWithSession: (url: string) => Promise<void>;
195
276
  };
196
277
  /**
197
278
  * Checkout hook - starts a Stripe/Polar checkout session for a plan price.
@@ -226,30 +307,33 @@ declare function usePortal(): {
226
307
  openPortal: (returnUrl?: string) => Promise<void>;
227
308
  };
228
309
  /**
229
- * Hook for a plain allowed/denied entitlement check.
310
+ * Whether the current scope has access to one feature - the single entry
311
+ * point for a live, per-feature decision. Deliberately not named after what
312
+ * it checks TODAY: this gates entitlements for now, but the same shape is
313
+ * meant to answer for a future access-rule engine (custom RBAC, feature
314
+ * flags) without a rename - "access" is the word that survives that.
315
+ * `details` (not `entitlement`) follows the same logic on the return value.
230
316
  *
231
- * @example
232
- * ```tsx
233
- * const { allowed, isLoading } = useAllows('advanced_analytics');
234
- * ```
235
- */
236
- declare function useAllows(featureKey: string, requested?: number): {
237
- allowed: boolean;
238
- isLoading: boolean;
239
- error: Error | null;
240
- };
241
- /**
242
- * Hook for the full entitlement detail (limit/used/remaining/overage) -
243
- * use `useAllows` when a plain boolean is all you need.
317
+ * Not `useCheck` either - too close to `useCheckout` to skim safely, and
318
+ * unrelated to it in every other way. Not folded into `useEntitlements()`
319
+ * (the cached list, for display): the two hit different endpoints with
320
+ * different consistency guarantees, and merging them would make the network
321
+ * behavior branch silently on whether a key was passed.
322
+ *
323
+ * Replaces the former `useAllows`/`useCheck` pair, which were two names for
324
+ * one request: `client.allows()` is literally `check().allowed`, hitting the
325
+ * same endpoint and discarding the rest.
244
326
  *
245
327
  * @example
246
328
  * ```tsx
247
- * const { check, isLoading } = useCheck('api_calls');
248
- * // check?.remaining, check?.limit, check?.overage_behavior
329
+ * const { allowed } = useAccess('advanced_analytics');
330
+ * const { details } = useAccess('api_calls');
331
+ * // details?.remaining, details?.limit, details?.overage_behavior
249
332
  * ```
250
333
  */
251
- declare function useCheck(featureKey: string, requested?: number): {
252
- check: EntitlementCheck | null;
334
+ declare function useAccess(featureKey: string, requested?: number): {
335
+ details: EntitlementCheck | null;
336
+ allowed: boolean;
253
337
  isLoading: boolean;
254
338
  error: Error | null;
255
339
  };
@@ -263,8 +347,8 @@ declare function useCheck(featureKey: string, requested?: number): {
263
347
  */
264
348
  declare function useSubscription(productSlug?: string): {
265
349
  isActive: boolean;
266
- planSlug: any;
267
- subscription: any | null;
350
+ planSlug: string | undefined;
351
+ subscription: SubscriptionWithPlan | null;
268
352
  isLoading: boolean;
269
353
  error: Error | null;
270
354
  };
@@ -282,11 +366,27 @@ declare function usePlans(productIdOrSlug?: string): {
282
366
  isLoading: boolean;
283
367
  error: Error | null;
284
368
  };
369
+ /**
370
+ * The tenant's public auth config - which sign-in methods are enabled, whether
371
+ * registration is open, whether to show Kerne branding. Drives what the
372
+ * prebuilt forms render, so a tenant that turns magic link on gets the button
373
+ * without shipping any code.
374
+ *
375
+ * @example
376
+ * ```tsx
377
+ * const { config, isLoading } = useAuthConfig();
378
+ * ```
379
+ */
380
+ declare function useAuthConfig(): {
381
+ config: AuthConfig | null;
382
+ isLoading: boolean;
383
+ error: Error | null;
384
+ };
285
385
  /**
286
386
  * Every entitlement for the current user in one call - for a "your plan"
287
- * screen. Use `useAllows`/`useCheck` instead for a single feature - this
288
- * response never carries `allowed` (see docs: the list is cached, a stale
289
- * verdict would be unsafe).
387
+ * screen. Use `useAccess` instead for a single feature - this response
388
+ * never carries `allowed` (see docs: the list is cached, a stale verdict
389
+ * would be unsafe).
290
390
  *
291
391
  * @example
292
392
  * ```tsx
@@ -353,13 +453,13 @@ declare function useInvitation(params: {
353
453
  * ```
354
454
  */
355
455
  declare function useUser(): {
356
- user: User | null;
456
+ user: _kerne_types.User | null;
357
457
  isLoading: boolean;
358
458
  update: (params: {
359
459
  first_name?: string;
360
460
  last_name?: string;
361
461
  avatar?: string;
362
- }) => Promise<User>;
462
+ }) => Promise<_kerne_types.User>;
363
463
  fullName: string | null;
364
464
  initials: string | null;
365
465
  email: string | null;
@@ -431,8 +531,16 @@ interface SubscriptionGuardProps {
431
531
  declare function HasSubscription({ children, fallback }: SubscriptionGuardProps): ReactNode;
432
532
  interface EntitlementGuardProps {
433
533
  children: ReactNode;
434
- /** Capability key to check */
435
- key: string;
534
+ /**
535
+ * Capability key to check.
536
+ *
537
+ * NOT named `key`: React reserves that prop for reconciliation and strips it
538
+ * before the component sees it, so the previous `key="..."` signature could
539
+ * only ever read `undefined` - the check then failed and the guard rendered
540
+ * its fallback forever. Same name as `useAccess(featureKey)` and
541
+ * `UpgradePrompt`, so the whole entitlement surface reads alike.
542
+ */
543
+ featureKey: string;
436
544
  /** Minimum value required (for limit features) */
437
545
  minimum?: number;
438
546
  /** Fallback content when entitlement is not met */
@@ -445,18 +553,18 @@ interface EntitlementGuardProps {
445
553
  *
446
554
  * @example
447
555
  * ```tsx
448
- * <Allows key="advanced_analytics" fallback={<UpgradePrompt feature="analytics" />}>
556
+ * <Allows featureKey="advanced_analytics" fallback={<UpgradePrompt featureKey="advanced_analytics" />}>
449
557
  * <AnalyticsDashboard />
450
558
  * </Allows>
451
559
  * ```
452
560
  */
453
- declare function Allows({ children, key, minimum, fallback, }: EntitlementGuardProps): ReactNode;
561
+ declare function Allows({ children, featureKey, minimum, fallback, }: EntitlementGuardProps): ReactNode;
454
562
  interface ProtectedProps {
455
563
  children: ReactNode;
456
564
  /** Require authentication */
457
565
  auth?: boolean;
458
- /** Require specific capability key */
459
- key?: string;
566
+ /** Require a specific capability. Not `key` - React reserves that prop name. */
567
+ featureKey?: string;
460
568
  /** Fallback for unauthenticated */
461
569
  authFallback?: ReactNode;
462
570
  /** Fallback for missing feature */
@@ -467,12 +575,12 @@ interface ProtectedProps {
467
575
  *
468
576
  * @example
469
577
  * ```tsx
470
- * <Protected auth key="advanced_analytics" authFallback={<Login />} billingFallback={<Upgrade />}>
578
+ * <Protected auth featureKey="advanced_analytics" authFallback={<Login />} billingFallback={<Upgrade />}>
471
579
  * <ProFeature />
472
580
  * </Protected>
473
581
  * ```
474
582
  */
475
- declare function Protected({ children, auth, key, authFallback, billingFallback, }: ProtectedProps): ReactNode;
583
+ declare function Protected({ children, auth, featureKey, authFallback, billingFallback, }: ProtectedProps): ReactNode;
476
584
 
477
585
  /**
478
586
  * Kerne Error Boundary
@@ -533,35 +641,4 @@ declare function useKerneError(): {
533
641
  clearError: () => void;
534
642
  };
535
643
 
536
- /**
537
- * KerneStore - External store for React 18+ useSyncExternalStore
538
- *
539
- * Optimized for minimal re-renders:
540
- * - Subscribe to specific slices of state
541
- * - Only notifies when subscribed values change
542
- */
543
-
544
- interface KerneState {
545
- user: User | null;
546
- isAuthenticated: boolean;
547
- isLoading: boolean;
548
- subscription: Subscription | null;
549
- error: Error | null;
550
- }
551
- type Listener = () => void;
552
- type Selector<T> = (state: KerneState) => T;
553
- declare class KerneStore {
554
- private state;
555
- private listeners;
556
- constructor(initialState?: Partial<KerneState>);
557
- getState(): KerneState;
558
- getSnapshot(): KerneState;
559
- getServerSnapshot(): KerneState;
560
- setState(partial: Partial<KerneState>): void;
561
- private hasChanged;
562
- subscribe(listener: Listener): () => void;
563
- private notify;
564
- select<T>(selector: Selector<T>): T;
565
- }
566
-
567
- export { Allows, AuthLoading, Authenticated, HasSubscription, KerneClient, KerneContext, type KerneError, KerneErrorBoundary, KerneProvider, type KerneProviderProps, type KerneReactConfig, type KerneState, KerneStore, Protected, Unauthenticated, useAllows, useAuth, useCheck, useCheckout, useClient, useEntitlements, useInvitation, useKerneError, usePlans, usePortal, useSubscription, useUsage, useUser, useWaitlist };
644
+ export { Allows, AuthLoading, Authenticated, HasSubscription, KerneClient, KerneContext, type KerneError, KerneErrorBoundary, KerneLocalization, KerneProvider, type KerneProviderProps, type KerneReactConfig, Protected, Unauthenticated, useAccess, useAuth, useAuthConfig, useCheckout, useClient, useEntitlements, useInvitation, useKerneError, usePlans, usePortal, useSubscription, useUsage, useUser, useWaitlist };