@kerne/react 0.1.0 → 0.1.1

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.ts CHANGED
@@ -1,87 +1,567 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import * as _kerne_server from '@kerne/server';
3
- import { KerneConfig, Kerne } from '@kerne/server';
4
- import React from 'react';
5
- import { User, AuthResponse } from '@kerne/types';
2
+ import React, { ReactNode, Component, ErrorInfo } from 'react';
3
+ import { KerneConfig, JoinWaitlistParams, ValidateTokenResponse, ValidateCodeResponse } from '@kerne/server';
4
+ export { JoinWaitlistParams, ValidateCodeResponse, ValidateTokenResponse, WaitlistEntry } from '@kerne/server';
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
8
 
7
9
  interface KerneReactConfig extends Omit<KerneConfig, 'secretKey'> {
8
10
  storage?: Storage;
9
11
  storageKey?: string;
10
12
  onAuthChange?: (user: User | null) => void;
13
+ /** Auto-refresh session before expiry (default: true) */
14
+ autoRefresh?: boolean;
15
+ /** Refresh buffer in seconds before expiry (default: 60) */
16
+ refreshBuffer?: number;
11
17
  }
18
+
19
+ type Listener$1 = () => void;
12
20
  declare class KerneClient {
13
21
  private kerne;
14
22
  private storage;
15
23
  private storageKey;
16
24
  private onAuthChange?;
17
- private currentUser;
18
- private currentToken;
25
+ private autoRefresh;
26
+ private refreshBuffer;
27
+ private refreshTimer;
28
+ private listeners;
29
+ private _user;
30
+ private _token;
31
+ private _refreshToken;
32
+ private _expiresAt;
33
+ private _isLoading;
34
+ readonly baseUrl: string;
35
+ readonly appId: string;
19
36
  constructor(config: KerneReactConfig);
20
- private loadSession;
21
- private saveSession;
22
- private clearSession;
37
+ subscribe(listener: Listener$1): () => void;
38
+ private notify;
23
39
  get user(): User | null;
24
40
  get token(): string | null;
25
41
  get isAuthenticated(): boolean;
26
- get client(): Kerne;
27
- get projects(): _kerne_server.ProjectsResource;
42
+ get isLoading(): boolean;
43
+ private loadSession;
44
+ /**
45
+ * `AuthResponse.user` is the minimal `{id, email, role}` claim set the
46
+ * token endpoint returns, not the full profile (`User`) - so this always
47
+ * follows up with `users.me()` rather than assigning it directly (that
48
+ * used to leave `_user` looking like a `User` while actually missing
49
+ * `email_verified`/`first_name`/`avatar`/etc).
50
+ */
51
+ private saveSession;
52
+ private clearSession;
53
+ private scheduleRefresh;
54
+ private cancelRefresh;
55
+ private silentRefresh;
28
56
  register(params: {
29
57
  email: string;
30
58
  password: string;
31
59
  name?: string;
60
+ invitationToken?: string;
61
+ invitationCode?: string;
32
62
  }): Promise<AuthResponse>;
33
63
  login(params: {
34
64
  email: string;
35
65
  password: string;
36
66
  }): Promise<AuthResponse>;
67
+ /** Manually trigger a token refresh - normally handled automatically by `autoRefresh`. */
37
68
  refreshToken(): Promise<AuthResponse | null>;
38
69
  logout(): void;
39
70
  refreshUser(): Promise<User | null>;
40
- checkEntitlement(featureKey: string, requested?: number): Promise<boolean>;
41
- createCheckout(planId: string, options?: {
71
+ updateProfile(params: {
72
+ first_name?: string;
73
+ last_name?: string;
74
+ }): Promise<User>;
75
+ updatePassword(params: {
76
+ currentPassword: string;
77
+ newPassword: string;
78
+ }): Promise<void>;
79
+ /** Forgot-password flow (logged out) - sends the reset email. */
80
+ requestPasswordReset(email: string): Promise<void>;
81
+ /** Confirms a password reset with the token from the email. */
82
+ confirmPasswordReset(token: string, password: string): Promise<void>;
83
+ /**
84
+ * Errors are not swallowed here (unlike the old implementation, which
85
+ * caught everything and returned `false`) - a 401/500 must not be
86
+ * indistinguishable from a real denial, that's exactly what let the
87
+ * `has_access`/`allowed` mismatch below ship unnoticed. Callers that want
88
+ * a fail-closed boolean regardless of the reason (e.g. `<Allows>`)
89
+ * catch around this themselves.
90
+ */
91
+ allows(featureKey: string, requested?: number): Promise<boolean>;
92
+ /** Full entitlement detail (limit/used/remaining/overage) - use `allows()` for a plain boolean. */
93
+ check(featureKey: string, requested?: number): Promise<EntitlementCheck>;
94
+ createCheckout(planPriceId: string, options?: {
42
95
  successUrl?: string;
43
96
  cancelUrl?: string;
44
97
  }): Promise<string>;
45
- openCheckout(planId: string, options?: {
98
+ openCheckout(planPriceId: string, options?: {
46
99
  successUrl?: string;
47
100
  cancelUrl?: string;
48
101
  }): Promise<void>;
49
102
  createPortal(returnUrl?: string): Promise<string>;
50
103
  openPortal(returnUrl?: string): Promise<void>;
104
+ getSubscription(productSlug?: string): Promise<SubscriptionWithPlan | null>;
105
+ /** Public pricing data - omit `productIdOrSlug` for the tenant's default product. */
106
+ getPlans(productIdOrSlug?: string): Promise<PublicPlan[]>;
107
+ /** Every entitlement for the current user in one call - for a "your plan" screen. */
108
+ getEntitlements(): Promise<Entitlements>;
109
+ /** Usage for the current user - all QUOTA features, or one via `featureKey`. */
110
+ getUsage(featureKey?: string): Promise<UsageRecord[]>;
111
+ joinWaitlist(params: JoinWaitlistParams): Promise<{
112
+ success: boolean;
113
+ }>;
114
+ /** Validate an invitation token before showing the registration form. */
115
+ validateInvitationToken(token: string): Promise<ValidateTokenResponse>;
116
+ /** Validate an invitation code before showing the registration form. */
117
+ validateInvitationCode(code: string): Promise<ValidateCodeResponse>;
118
+ sendVerificationEmail(verificationType?: 'code' | 'link'): Promise<void>;
119
+ verifyEmailWithCode(code: string): Promise<{
120
+ success: boolean;
121
+ needsTokenRefresh: boolean;
122
+ }>;
123
+ verifyEmailWithLink(token: string): Promise<{
124
+ success: boolean;
125
+ needsTokenRefresh: boolean;
126
+ }>;
127
+ getVerificationStatus(): Promise<{
128
+ emailVerified: boolean;
129
+ needsVerification: boolean;
130
+ email: string;
131
+ }>;
51
132
  }
52
- declare function createKerneClient(config: KerneReactConfig): KerneClient;
133
+ declare const KerneContext: React.Context<KerneClient | null>;
53
134
  interface KerneProviderProps extends KerneReactConfig {
54
135
  children: React.ReactNode;
55
136
  }
56
137
  declare function KerneProvider({ children, ...config }: KerneProviderProps): react_jsx_runtime.JSX.Element;
57
- declare function useKerne(): KerneClient;
138
+
139
+ /**
140
+ * Access the Kerne client instance
141
+ */
142
+ declare function useClient(): KerneClient;
143
+ /**
144
+ * Authentication hook with optimized re-renders
145
+ *
146
+ * @example
147
+ * ```tsx
148
+ * const { user, isAuthenticated, login, logout } = useAuth();
149
+ * ```
150
+ */
58
151
  declare function useAuth(): {
59
152
  user: User | null;
153
+ token: string | null;
60
154
  isAuthenticated: boolean;
155
+ isLoading: boolean;
61
156
  login: (params: {
62
157
  email: string;
63
158
  password: string;
64
- }) => Promise<AuthResponse>;
159
+ }) => Promise<_kerne_types.AuthResponse>;
65
160
  register: (params: {
66
161
  email: string;
67
162
  password: string;
68
163
  name?: string;
69
- }) => Promise<AuthResponse>;
164
+ invitationToken?: string;
165
+ invitationCode?: string;
166
+ }) => Promise<_kerne_types.AuthResponse>;
70
167
  logout: () => void;
71
168
  refreshUser: () => Promise<User | null>;
169
+ refreshToken: () => Promise<_kerne_types.AuthResponse | null>;
170
+ updateProfile: (params: {
171
+ first_name?: string;
172
+ last_name?: string;
173
+ avatar?: string;
174
+ }) => Promise<User>;
175
+ updatePassword: (params: {
176
+ currentPassword: string;
177
+ newPassword: string;
178
+ }) => Promise<void>;
179
+ requestPasswordReset: (email: string) => Promise<void>;
180
+ confirmPasswordReset: (token: string, password: string) => Promise<void>;
181
+ sendVerificationEmail: (verificationType?: "code" | "link") => Promise<void>;
182
+ verifyEmailWithCode: (code: string) => Promise<{
183
+ success: boolean;
184
+ needsTokenRefresh: boolean;
185
+ }>;
186
+ verifyEmailWithLink: (token: string) => Promise<{
187
+ success: boolean;
188
+ needsTokenRefresh: boolean;
189
+ }>;
190
+ getVerificationStatus: () => Promise<{
191
+ emailVerified: boolean;
192
+ needsVerification: boolean;
193
+ email: string;
194
+ }>;
72
195
  };
73
- declare function useBilling(): {
74
- checkEntitlement: (featureKey: string, requested?: number) => Promise<boolean>;
75
- createCheckout: (planId: string, options?: {
196
+ /**
197
+ * Checkout hook - starts a Stripe/Polar checkout session for a plan price.
198
+ *
199
+ * @example
200
+ * ```tsx
201
+ * const { openCheckout } = useCheckout();
202
+ * <button onClick={() => openCheckout(planPriceId)}>Upgrade</button>
203
+ * ```
204
+ */
205
+ declare function useCheckout(): {
206
+ createCheckout: (planPriceId: string, options?: {
76
207
  successUrl?: string;
77
208
  cancelUrl?: string;
78
209
  }) => Promise<string>;
79
- openCheckout: (planId: string, options?: {
210
+ openCheckout: (planPriceId: string, options?: {
80
211
  successUrl?: string;
81
212
  cancelUrl?: string;
82
213
  }) => Promise<void>;
214
+ };
215
+ /**
216
+ * Billing portal hook - lets a user manage their subscription/payment method.
217
+ *
218
+ * @example
219
+ * ```tsx
220
+ * const { openPortal } = usePortal();
221
+ * <button onClick={() => openPortal()}>Manage billing</button>
222
+ * ```
223
+ */
224
+ declare function usePortal(): {
83
225
  createPortal: (returnUrl?: string) => Promise<string>;
84
226
  openPortal: (returnUrl?: string) => Promise<void>;
85
227
  };
228
+ /**
229
+ * Hook for a plain allowed/denied entitlement check.
230
+ *
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.
244
+ *
245
+ * @example
246
+ * ```tsx
247
+ * const { check, isLoading } = useCheck('api_calls');
248
+ * // check?.remaining, check?.limit, check?.overage_behavior
249
+ * ```
250
+ */
251
+ declare function useCheck(featureKey: string, requested?: number): {
252
+ check: EntitlementCheck | null;
253
+ isLoading: boolean;
254
+ error: Error | null;
255
+ };
256
+ /**
257
+ * Hook for subscription state
258
+ *
259
+ * @example
260
+ * ```tsx
261
+ * const { subscription, isLoading, isPro } = useSubscription();
262
+ * ```
263
+ */
264
+ declare function useSubscription(productSlug?: string): {
265
+ isActive: boolean;
266
+ planSlug: any;
267
+ subscription: any | null;
268
+ isLoading: boolean;
269
+ error: Error | null;
270
+ };
271
+ /**
272
+ * Public pricing data for a pricing/checkout page - omit `productIdOrSlug`
273
+ * for the tenant's default product.
274
+ *
275
+ * @example
276
+ * ```tsx
277
+ * const { plans, isLoading } = usePlans();
278
+ * ```
279
+ */
280
+ declare function usePlans(productIdOrSlug?: string): {
281
+ plans: PublicPlan[];
282
+ isLoading: boolean;
283
+ error: Error | null;
284
+ };
285
+ /**
286
+ * 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).
290
+ *
291
+ * @example
292
+ * ```tsx
293
+ * const { entitlements, isLoading } = useEntitlements();
294
+ * ```
295
+ */
296
+ declare function useEntitlements(): {
297
+ entitlements: Entitlements | null;
298
+ isLoading: boolean;
299
+ error: Error | null;
300
+ };
301
+ /**
302
+ * Usage for the current user - every QUOTA feature, or one via `featureKey`.
303
+ *
304
+ * @example
305
+ * ```tsx
306
+ * const { usage, isLoading } = useUsage('api_calls');
307
+ * ```
308
+ */
309
+ declare function useUsage(featureKey?: string): {
310
+ usage: UsageRecord[];
311
+ isLoading: boolean;
312
+ error: Error | null;
313
+ };
314
+ /**
315
+ * Waitlist signup form - a mutation, not a data fetch, same shape as
316
+ * `useCheckout`/`usePortal`.
317
+ *
318
+ * @example
319
+ * ```tsx
320
+ * const { join } = useWaitlist();
321
+ * await join({ email });
322
+ * ```
323
+ */
324
+ declare function useWaitlist(): {
325
+ join: (params: JoinWaitlistParams) => Promise<{
326
+ success: boolean;
327
+ }>;
328
+ };
329
+ /**
330
+ * Validates an invitation token/code before showing the registration form -
331
+ * pass whichever one is present in the URL.
332
+ *
333
+ * @example
334
+ * ```tsx
335
+ * const { result, isLoading } = useInvitation({ token });
336
+ * if (!isLoading && !result?.valid) return <InvalidInvite />;
337
+ * ```
338
+ */
339
+ declare function useInvitation(params: {
340
+ token?: string;
341
+ code?: string;
342
+ }): {
343
+ result: ValidateTokenResponse | ValidateCodeResponse | null;
344
+ isLoading: boolean;
345
+ error: Error | null;
346
+ };
347
+ /**
348
+ * Hook for user profile data
349
+ *
350
+ * @example
351
+ * ```tsx
352
+ * const { user, isLoading, update } = useUser();
353
+ * ```
354
+ */
355
+ declare function useUser(): {
356
+ user: User | null;
357
+ isLoading: boolean;
358
+ update: (params: {
359
+ first_name?: string;
360
+ last_name?: string;
361
+ avatar?: string;
362
+ }) => Promise<User>;
363
+ fullName: string | null;
364
+ initials: string | null;
365
+ email: string | null;
366
+ emailVerified: boolean;
367
+ };
368
+
369
+ /**
370
+ * Guardian Components
371
+ *
372
+ * Conditional rendering based on auth/billing state.
373
+ * Inspired by Clerk's <SignedIn>, <SignedOut> pattern.
374
+ */
375
+
376
+ interface AuthGuardProps {
377
+ children: ReactNode;
378
+ /** Fallback content when condition is not met */
379
+ fallback?: ReactNode;
380
+ }
381
+ /**
382
+ * Renders children only when user is authenticated
383
+ *
384
+ * @example
385
+ * ```tsx
386
+ * <Authenticated>
387
+ * <Dashboard />
388
+ * </Authenticated>
389
+ * ```
390
+ */
391
+ declare function Authenticated({ children, fallback }: AuthGuardProps): ReactNode;
392
+ /**
393
+ * Renders children only when user is NOT authenticated
394
+ *
395
+ * @example
396
+ * ```tsx
397
+ * <Unauthenticated>
398
+ * <LoginPrompt />
399
+ * </Unauthenticated>
400
+ * ```
401
+ */
402
+ declare function Unauthenticated({ children, fallback }: AuthGuardProps): ReactNode;
403
+ /**
404
+ * Renders children while auth state is loading
405
+ *
406
+ * @example
407
+ * ```tsx
408
+ * <AuthLoading>
409
+ * <Spinner />
410
+ * </AuthLoading>
411
+ * ```
412
+ */
413
+ declare function AuthLoading({ children }: {
414
+ children: ReactNode;
415
+ }): ReactNode;
416
+ interface SubscriptionGuardProps {
417
+ children: ReactNode;
418
+ /** Fallback content when there's no active subscription */
419
+ fallback?: ReactNode;
420
+ }
421
+ /**
422
+ * Renders children only when user has an active subscription
423
+ *
424
+ * @example
425
+ * ```tsx
426
+ * <HasSubscription fallback={<UpgradePrompt />}>
427
+ * <PremiumFeature />
428
+ * </HasSubscription>
429
+ * ```
430
+ */
431
+ declare function HasSubscription({ children, fallback }: SubscriptionGuardProps): ReactNode;
432
+ interface EntitlementGuardProps {
433
+ children: ReactNode;
434
+ /** Capability key to check */
435
+ key: string;
436
+ /** Minimum value required (for limit features) */
437
+ minimum?: number;
438
+ /** Fallback content when entitlement is not met */
439
+ fallback?: ReactNode;
440
+ }
441
+ /**
442
+ * Renders children only when the scope is entitled to a capability. Checks the
443
+ * entitlement itself, never a plan name - a plan can be renamed or restructured
444
+ * without breaking every place that gated on its slug.
445
+ *
446
+ * @example
447
+ * ```tsx
448
+ * <Allows key="advanced_analytics" fallback={<UpgradePrompt feature="analytics" />}>
449
+ * <AnalyticsDashboard />
450
+ * </Allows>
451
+ * ```
452
+ */
453
+ declare function Allows({ children, key, minimum, fallback, }: EntitlementGuardProps): ReactNode;
454
+ interface ProtectedProps {
455
+ children: ReactNode;
456
+ /** Require authentication */
457
+ auth?: boolean;
458
+ /** Require specific capability key */
459
+ key?: string;
460
+ /** Fallback for unauthenticated */
461
+ authFallback?: ReactNode;
462
+ /** Fallback for missing feature */
463
+ billingFallback?: ReactNode;
464
+ }
465
+ /**
466
+ * Combined guard for auth + entitlement requirements
467
+ *
468
+ * @example
469
+ * ```tsx
470
+ * <Protected auth key="advanced_analytics" authFallback={<Login />} billingFallback={<Upgrade />}>
471
+ * <ProFeature />
472
+ * </Protected>
473
+ * ```
474
+ */
475
+ declare function Protected({ children, auth, key, authFallback, billingFallback, }: ProtectedProps): ReactNode;
476
+
477
+ /**
478
+ * Kerne Error Boundary
479
+ *
480
+ * Catches auth/billing errors and provides recovery UI
481
+ */
482
+
483
+ interface KerneError extends Error {
484
+ code?: string;
485
+ statusCode?: number;
486
+ details?: Record<string, any>;
487
+ }
488
+ interface ErrorBoundaryProps {
489
+ children: ReactNode;
490
+ /** Custom fallback UI */
491
+ fallback?: ReactNode | ((error: KerneError, reset: () => void) => ReactNode);
492
+ /** Called when error is caught */
493
+ onError?: (error: KerneError, errorInfo: ErrorInfo) => void;
494
+ /** Called when user clicks reset */
495
+ onReset?: () => void;
496
+ }
497
+ interface ErrorBoundaryState {
498
+ error: KerneError | null;
499
+ hasError: boolean;
500
+ }
501
+ /**
502
+ * Error boundary for Kerne SDK errors
503
+ *
504
+ * @example
505
+ * ```tsx
506
+ * <KerneErrorBoundary
507
+ * fallback={(error, reset) => (
508
+ * <div>
509
+ * <p>Something went wrong: {error.message}</p>
510
+ * <button onClick={reset}>Try again</button>
511
+ * </div>
512
+ * )}
513
+ * onError={(error) => console.error('Kerne error:', error)}
514
+ * >
515
+ * <App />
516
+ * </KerneErrorBoundary>
517
+ * ```
518
+ */
519
+ declare class KerneErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
520
+ constructor(props: ErrorBoundaryProps);
521
+ static getDerivedStateFromError(error: Error): ErrorBoundaryState;
522
+ componentDidCatch(error: Error, errorInfo: ErrorInfo): void;
523
+ reset: () => void;
524
+ render(): ReactNode;
525
+ }
526
+ /**
527
+ * Hook version for functional components that need error state
528
+ */
529
+ declare function useKerneError(): {
530
+ error: KerneError | null;
531
+ hasError: boolean;
532
+ handleError: (err: Error | KerneError) => void;
533
+ clearError: () => void;
534
+ };
535
+
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
+ }
86
566
 
87
- export { KerneClient, KerneProvider, type KerneProviderProps, type KerneReactConfig, createKerneClient, useAuth, useBilling, useKerne };
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 };