@lushly-dev/afd-auth 1.0.0 → 2.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/README.md +134 -0
- package/dist/adapters/better-auth.d.ts +34 -6
- package/dist/adapters/better-auth.d.ts.map +1 -1
- package/dist/adapters/better-auth.js +73 -25
- package/dist/adapters/better-auth.js.map +1 -1
- package/dist/adapters/convex.d.ts +29 -6
- package/dist/adapters/convex.d.ts.map +1 -1
- package/dist/adapters/convex.js +139 -56
- package/dist/adapters/convex.js.map +1 -1
- package/dist/adapters/mock.d.ts +16 -4
- package/dist/adapters/mock.d.ts.map +1 -1
- package/dist/adapters/mock.js +13 -11
- package/dist/adapters/mock.js.map +1 -1
- package/dist/adapters/provider-errors.d.ts +16 -0
- package/dist/adapters/provider-errors.d.ts.map +1 -0
- package/dist/adapters/provider-errors.js +27 -0
- package/dist/adapters/provider-errors.js.map +1 -0
- package/dist/command-names.d.ts +13 -0
- package/dist/command-names.d.ts.map +1 -0
- package/dist/command-names.js +13 -0
- package/dist/command-names.js.map +1 -0
- package/dist/commands.d.ts +11 -2
- package/dist/commands.d.ts.map +1 -1
- package/dist/commands.js +141 -9
- package/dist/commands.js.map +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -6
- package/dist/index.js.map +1 -1
- package/dist/listeners.d.ts +39 -0
- package/dist/listeners.d.ts.map +1 -0
- package/dist/listeners.js +72 -0
- package/dist/listeners.js.map +1 -0
- package/dist/middleware.d.ts +22 -3
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +32 -14
- package/dist/middleware.js.map +1 -1
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +19 -5
- package/dist/react.js.map +1 -1
- package/dist/session-state.d.ts +19 -0
- package/dist/session-state.d.ts.map +1 -0
- package/dist/session-state.js +42 -0
- package/dist/session-state.js.map +1 -0
- package/dist/session-sync-message.d.ts +29 -0
- package/dist/session-sync-message.d.ts.map +1 -0
- package/dist/session-sync-message.js +27 -0
- package/dist/session-sync-message.js.map +1 -0
- package/dist/session-sync.d.ts +35 -8
- package/dist/session-sync.d.ts.map +1 -1
- package/dist/session-sync.js +178 -44
- package/dist/session-sync.js.map +1 -1
- package/dist/types.d.ts +35 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +21 -15
package/README.md
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# @lushly-dev/afd-auth
|
|
2
|
+
|
|
3
|
+
Provider-agnostic authentication adapters for AFD applications. The main
|
|
4
|
+
entrypoint exposes the `AuthAdapter` contract, structured auth errors, the auth
|
|
5
|
+
middleware, multi-tab session synchronization, and the Mock and Better Auth
|
|
6
|
+
adapters.
|
|
7
|
+
|
|
8
|
+
Auth command definitions are available from the optional commands subpath:
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
import { createAuthCommands } from '@lushly-dev/afd-auth/commands';
|
|
12
|
+
|
|
13
|
+
const authCommands = createAuthCommands(adapter, { signInTimeoutMs: 10_000 });
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
React hooks are available from the optional `@lushly-dev/afd-auth/react`
|
|
17
|
+
subpath:
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { createAuthHooks, useConvexAuthAdapter } from '@lushly-dev/afd-auth/react';
|
|
21
|
+
|
|
22
|
+
const { useSession, useUser } = createAuthHooks(adapter);
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The root import stays usable for server and non-React consumers without
|
|
26
|
+
installing React, `@lushly-dev/afd-server`, or `zod`. Import
|
|
27
|
+
`useConvexAuthAdapter` from `/react` and `createAuthCommands` from `/commands`
|
|
28
|
+
when those integrations are needed.
|
|
29
|
+
|
|
30
|
+
## Middleware
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { createAuthMiddleware } from '@lushly-dev/afd-auth';
|
|
34
|
+
|
|
35
|
+
const middleware = createAuthMiddleware(adapter, { exclude: ['public-cmd'] });
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
| Adapter state | Result |
|
|
39
|
+
| --- | --- |
|
|
40
|
+
| `unauthenticated` | `UNAUTHORIZED`, not retryable |
|
|
41
|
+
| `loading` | `UNAUTHORIZED`, retryable |
|
|
42
|
+
| `authenticated`, `session.expiresAt` at or before now (or not a valid date) | `TOKEN_EXPIRED`, retryable |
|
|
43
|
+
| `authenticated` | sets `context.auth` and runs the command |
|
|
44
|
+
|
|
45
|
+
- The built-in commands `auth-sign-in`, `auth-sign-out` and `auth-session-get`
|
|
46
|
+
(`AUTH_COMMAND_NAMES`) always run, so a signed-out caller can sign in.
|
|
47
|
+
`exclude` adds more commands.
|
|
48
|
+
- The middleware sets or deletes `context.auth` on every call, excluded
|
|
49
|
+
commands included. A handler never sees a `context.auth` that the caller
|
|
50
|
+
supplied.
|
|
51
|
+
- Importing the package types `context.auth` as
|
|
52
|
+
`AuthenticatedSessionState | undefined` through a `CommandContext` module
|
|
53
|
+
augmentation.
|
|
54
|
+
- `Session.expiresAt` is optional. A session without it (Convex) is never
|
|
55
|
+
rejected as expired; the provider manages the token lifetime.
|
|
56
|
+
|
|
57
|
+
## Sign-in results
|
|
58
|
+
|
|
59
|
+
`AuthAdapter.signIn()` may resolve with a `SignInOutcome`:
|
|
60
|
+
`{ kind: 'signed-in' }`, `{ kind: 'redirect', url? }` (OAuth continues at the
|
|
61
|
+
provider) or `{ kind: 'pending' }` (another step, such as email verification).
|
|
62
|
+
Resolving with nothing is still allowed and counts as `signed-in`.
|
|
63
|
+
|
|
64
|
+
`auth-sign-in` uses it to report what happened:
|
|
65
|
+
|
|
66
|
+
- `redirect` / `pending`: returns the current (not yet signed-in) state with
|
|
67
|
+
"Not signed in yet" reasoning and a `SIGN_IN_REDIRECT` or `SIGN_IN_PENDING`
|
|
68
|
+
warning. The redirect URL is in the warning's `details.url`.
|
|
69
|
+
- otherwise: waits for the next non-loading `onAuthStateChange`, up to
|
|
70
|
+
`signInTimeoutMs` (default 10 s), and returns the new session. If the wait
|
|
71
|
+
runs out it returns the current state with a `SIGN_IN_PENDING` warning; if
|
|
72
|
+
the adapter settles on no session it fails with `PROVIDER_ERROR`.
|
|
73
|
+
|
|
74
|
+
## Adapters
|
|
75
|
+
|
|
76
|
+
All adapters accept `onListenerError`. Each `onAuthStateChange` subscriber
|
|
77
|
+
runs in its own `try/catch`: one that throws does not stop the others from
|
|
78
|
+
seeing the change, and does not make `signIn()`/`signOut()` reject. The error
|
|
79
|
+
goes to `onListenerError`, or is rethrown in a microtask when none is set.
|
|
80
|
+
|
|
81
|
+
### Convex
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
const adapter = useConvexAuthAdapter({
|
|
85
|
+
useAuthActions,
|
|
86
|
+
useConvexAuth,
|
|
87
|
+
meQuery: () => useQuery(api.users.me),
|
|
88
|
+
passwordProviderId: 'password', // default: the stock Password() provider
|
|
89
|
+
});
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
- Credentials sign-in calls `signIn(passwordProviderId, { email, password, flow: 'signIn' })`.
|
|
93
|
+
A missing password is rejected with `INVALID_CREDENTIALS`.
|
|
94
|
+
- OAuth sign-in forwards `redirectTo`. `scopes` are rejected with
|
|
95
|
+
`PROVIDER_ERROR`: Convex configures scopes on the provider in
|
|
96
|
+
`convex/auth.ts`.
|
|
97
|
+
- Errors use the same codes as Better Auth: `InvalidAccountId`,
|
|
98
|
+
`InvalidSecret` and `Invalid credentials` become `INVALID_CREDENTIALS`, fetch
|
|
99
|
+
failures `NETWORK_ERROR`, everything else `PROVIDER_ERROR`. Production
|
|
100
|
+
Convex deployments hide server error text unless it is thrown as a
|
|
101
|
+
`ConvexError`, so there bad credentials surface as `PROVIDER_ERROR`.
|
|
102
|
+
- The state is `loading` while `meQuery` returns `undefined`, and
|
|
103
|
+
`unauthenticated` when it returns `null`.
|
|
104
|
+
- The session has no `expiresAt`, because Convex refreshes its token
|
|
105
|
+
internally.
|
|
106
|
+
- The hook returns the same adapter object on every render.
|
|
107
|
+
|
|
108
|
+
### Better Auth
|
|
109
|
+
|
|
110
|
+
`BetterAuthAdapter` forwards OAuth `scopes` and `redirectTo` (as
|
|
111
|
+
`callbackURL`) to `signIn.social`, and reports its redirect URL as a
|
|
112
|
+
`redirect` outcome.
|
|
113
|
+
|
|
114
|
+
## Session sync
|
|
115
|
+
|
|
116
|
+
`SessionSync` exchanges typed `SessionSyncMessage` values: `signed-in`,
|
|
117
|
+
`signed-out`, `session-refreshed`, `profile-updated` (both with an optional
|
|
118
|
+
`userId`) and `visibility-refresh`. Received payloads are validated and reduced
|
|
119
|
+
to those fields; `notifySessionChanged()` throws a `TypeError` for an invalid
|
|
120
|
+
message.
|
|
121
|
+
|
|
122
|
+
Received messages are debounced per type by `debounceMs`. `signed-out` is
|
|
123
|
+
delivered at once and cancels older messages still waiting, so it is never
|
|
124
|
+
coalesced away.
|
|
125
|
+
|
|
126
|
+
`SessionSync.acquireRefreshLock()` and `releaseRefreshLock()` provide a
|
|
127
|
+
synchronous best-effort fallback for coordinating refreshes between tabs. The
|
|
128
|
+
fallback records an instance owner and only that owner can release its current
|
|
129
|
+
lock. `acquireRefreshLockAsync()` waits `lockCheckDelayMs` after writing the
|
|
130
|
+
lock and reads it back before claiming it, which catches most write races. A
|
|
131
|
+
lock timestamp more than one second in the future counts as stale.
|
|
132
|
+
`localStorage` does not provide compare-and-swap, so acquisition remains
|
|
133
|
+
non-atomic under a simultaneous cross-tab race; applications that require
|
|
134
|
+
stronger serialization should use a provider-level refresh mechanism.
|
|
@@ -4,20 +4,38 @@
|
|
|
4
4
|
* Bridges better-auth client to the AuthAdapter interface.
|
|
5
5
|
* Types are defined locally to avoid hard dependency on better-auth.
|
|
6
6
|
*/
|
|
7
|
-
import
|
|
7
|
+
import { type ListenerErrorHandler } from '../listeners.js';
|
|
8
|
+
import type { AuthAdapter, AuthSessionState, SignInOptions, SignInOutcome } from '../types.js';
|
|
8
9
|
/** Minimal interface for better-auth client — avoids hard import */
|
|
10
|
+
interface BetterAuthErrorResponse {
|
|
11
|
+
message?: string;
|
|
12
|
+
status?: number;
|
|
13
|
+
statusText?: string;
|
|
14
|
+
code?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The small portion of Better Auth's response contract used by this adapter.
|
|
18
|
+
* Better Auth reports provider failures as resolved `{ data, error }` values;
|
|
19
|
+
* they do not necessarily reject the promise.
|
|
20
|
+
*/
|
|
21
|
+
interface BetterAuthMethodResult {
|
|
22
|
+
data?: unknown | null;
|
|
23
|
+
error?: BetterAuthErrorResponse | null;
|
|
24
|
+
}
|
|
25
|
+
type BetterAuthMethodResponse = BetterAuthMethodResult | undefined;
|
|
9
26
|
interface BetterAuthClient {
|
|
10
27
|
signIn: {
|
|
11
28
|
social: (params: {
|
|
12
29
|
provider: string;
|
|
13
30
|
callbackURL?: string;
|
|
14
|
-
|
|
31
|
+
scopes?: string[];
|
|
32
|
+
}) => Promise<BetterAuthMethodResponse>;
|
|
15
33
|
email: (params: {
|
|
16
34
|
email: string;
|
|
17
35
|
password: string;
|
|
18
|
-
}) => Promise<
|
|
36
|
+
}) => Promise<BetterAuthMethodResponse>;
|
|
19
37
|
};
|
|
20
|
-
signOut: () => Promise<
|
|
38
|
+
signOut: () => Promise<BetterAuthMethodResponse>;
|
|
21
39
|
useSession: () => {
|
|
22
40
|
subscribe: (callback: (value: {
|
|
23
41
|
data: BetterAuthSessionData | null;
|
|
@@ -44,14 +62,20 @@ interface BetterAuthSessionData {
|
|
|
44
62
|
export interface BetterAuthAdapterOptions {
|
|
45
63
|
/** better-auth client instance */
|
|
46
64
|
client: BetterAuthClient;
|
|
65
|
+
/**
|
|
66
|
+
* Receives errors thrown by `onAuthStateChange` subscribers. Without it,
|
|
67
|
+
* they are rethrown in a microtask. Either way the other subscribers still
|
|
68
|
+
* run.
|
|
69
|
+
*/
|
|
70
|
+
onListenerError?: ListenerErrorHandler;
|
|
47
71
|
}
|
|
48
72
|
export declare class BetterAuthAdapter implements AuthAdapter {
|
|
49
73
|
private readonly client;
|
|
50
|
-
private listeners;
|
|
74
|
+
private readonly listeners;
|
|
51
75
|
private currentState;
|
|
52
76
|
private unsubscribeStore;
|
|
53
77
|
constructor(options: BetterAuthAdapterOptions);
|
|
54
|
-
signIn(options: SignInOptions): Promise<
|
|
78
|
+
signIn(options: SignInOptions): Promise<SignInOutcome>;
|
|
55
79
|
signOut(): Promise<void>;
|
|
56
80
|
getSession(): AuthSessionState;
|
|
57
81
|
onAuthStateChange(callback: (state: AuthSessionState) => void): {
|
|
@@ -62,6 +86,10 @@ export declare class BetterAuthAdapter implements AuthAdapter {
|
|
|
62
86
|
*/
|
|
63
87
|
dispose(): void;
|
|
64
88
|
private setupSubscription;
|
|
89
|
+
private updateState;
|
|
90
|
+
private getResolvedProviderError;
|
|
91
|
+
private describeError;
|
|
92
|
+
private mapThrownError;
|
|
65
93
|
private mapToState;
|
|
66
94
|
}
|
|
67
95
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"better-auth.d.ts","sourceRoot":"","sources":["../../src/adapters/better-auth.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"better-auth.d.ts","sourceRoot":"","sources":["../../src/adapters/better-auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,57 +5,66 @@
|
|
|
5
5
|
* Types are defined locally to avoid hard dependency on better-auth.
|
|
6
6
|
*/
|
|
7
7
|
import { AuthAdapterError } from '../errors.js';
|
|
8
|
+
import { ListenerSet } from '../listeners.js';
|
|
9
|
+
import { areSessionStatesEqual } from '../session-state.js';
|
|
8
10
|
import { LOADING, UNAUTHENTICATED } from '../types.js';
|
|
11
|
+
import { isKnownNetworkFailure } from './provider-errors.js';
|
|
9
12
|
export class BetterAuthAdapter {
|
|
10
13
|
client;
|
|
11
|
-
listeners
|
|
14
|
+
listeners;
|
|
12
15
|
currentState = LOADING;
|
|
13
16
|
unsubscribeStore = null;
|
|
14
17
|
constructor(options) {
|
|
15
18
|
this.client = options.client;
|
|
19
|
+
this.listeners = new ListenerSet(options.onListenerError);
|
|
16
20
|
this.setupSubscription();
|
|
17
21
|
}
|
|
18
22
|
async signIn(options) {
|
|
19
23
|
try {
|
|
24
|
+
let result;
|
|
20
25
|
if (options.method === 'credentials') {
|
|
21
|
-
await this.client.signIn.email({
|
|
26
|
+
result = await this.client.signIn.email({
|
|
22
27
|
email: options.email,
|
|
23
28
|
password: options.password ?? '',
|
|
24
29
|
});
|
|
25
30
|
}
|
|
26
31
|
else {
|
|
27
|
-
await this.client.signIn.social({
|
|
32
|
+
result = await this.client.signIn.social({
|
|
28
33
|
provider: options.provider,
|
|
29
34
|
callbackURL: options.redirectTo,
|
|
35
|
+
scopes: options.scopes,
|
|
30
36
|
});
|
|
31
37
|
}
|
|
38
|
+
const providerError = this.getResolvedProviderError(result);
|
|
39
|
+
if (providerError) {
|
|
40
|
+
if (options.method === 'credentials' && providerError.status === 401) {
|
|
41
|
+
throw AuthAdapterError.invalidCredentials();
|
|
42
|
+
}
|
|
43
|
+
throw AuthAdapterError.providerError('better-auth', this.describeError(providerError));
|
|
44
|
+
}
|
|
45
|
+
return toSignInOutcome(result);
|
|
32
46
|
}
|
|
33
47
|
catch (error) {
|
|
34
|
-
|
|
35
|
-
throw error;
|
|
36
|
-
throw AuthAdapterError.providerError('better-auth', error instanceof Error ? error.message : String(error));
|
|
48
|
+
throw this.mapThrownError(error);
|
|
37
49
|
}
|
|
38
50
|
}
|
|
39
51
|
async signOut() {
|
|
40
52
|
try {
|
|
41
|
-
await this.client.signOut();
|
|
53
|
+
const result = await this.client.signOut();
|
|
54
|
+
const providerError = this.getResolvedProviderError(result);
|
|
55
|
+
if (providerError) {
|
|
56
|
+
throw AuthAdapterError.providerError('better-auth', this.describeError(providerError));
|
|
57
|
+
}
|
|
42
58
|
}
|
|
43
59
|
catch (error) {
|
|
44
|
-
|
|
45
|
-
throw error;
|
|
46
|
-
throw AuthAdapterError.providerError('better-auth', error instanceof Error ? error.message : String(error));
|
|
60
|
+
throw this.mapThrownError(error);
|
|
47
61
|
}
|
|
48
62
|
}
|
|
49
63
|
getSession() {
|
|
50
64
|
return this.currentState;
|
|
51
65
|
}
|
|
52
66
|
onAuthStateChange(callback) {
|
|
53
|
-
this.listeners.add(callback);
|
|
54
|
-
return {
|
|
55
|
-
unsubscribe: () => {
|
|
56
|
-
this.listeners.delete(callback);
|
|
57
|
-
},
|
|
58
|
-
};
|
|
67
|
+
return this.listeners.add(callback);
|
|
59
68
|
}
|
|
60
69
|
/**
|
|
61
70
|
* Clean up the nanostore subscription.
|
|
@@ -70,17 +79,44 @@ export class BetterAuthAdapter {
|
|
|
70
79
|
setupSubscription() {
|
|
71
80
|
// biome-ignore lint/correctness/useHookAtTopLevel: useSession is a better-auth store accessor, not a React hook
|
|
72
81
|
const store = this.client.useSession();
|
|
82
|
+
// Read before subscribing so stores that invoke the callback immediately do
|
|
83
|
+
// not replace an equivalent initial snapshot with a new object.
|
|
84
|
+
this.currentState = this.mapToState(store.get());
|
|
73
85
|
this.unsubscribeStore = store.subscribe((value) => {
|
|
74
|
-
|
|
75
|
-
if (newState.status !== this.currentState.status) {
|
|
76
|
-
this.currentState = newState;
|
|
77
|
-
for (const listener of this.listeners) {
|
|
78
|
-
listener(newState);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
86
|
+
this.updateState(value);
|
|
81
87
|
});
|
|
82
|
-
//
|
|
83
|
-
|
|
88
|
+
// A store can change while subscribe() is being attached without calling
|
|
89
|
+
// the callback. Reconcile once more so the adapter never exposes a stale
|
|
90
|
+
// initial snapshot.
|
|
91
|
+
this.updateState(store.get());
|
|
92
|
+
}
|
|
93
|
+
updateState(value) {
|
|
94
|
+
const newState = this.mapToState(value);
|
|
95
|
+
if (areSessionStatesEqual(newState, this.currentState))
|
|
96
|
+
return;
|
|
97
|
+
this.currentState = newState;
|
|
98
|
+
this.listeners.emit(newState);
|
|
99
|
+
}
|
|
100
|
+
getResolvedProviderError(result) {
|
|
101
|
+
return result?.error ?? null;
|
|
102
|
+
}
|
|
103
|
+
describeError(error) {
|
|
104
|
+
if (error.message)
|
|
105
|
+
return error.message;
|
|
106
|
+
if (error.statusText)
|
|
107
|
+
return error.statusText;
|
|
108
|
+
if (error.code)
|
|
109
|
+
return error.code;
|
|
110
|
+
return error.status === undefined ? undefined : `status ${error.status}`;
|
|
111
|
+
}
|
|
112
|
+
mapThrownError(error) {
|
|
113
|
+
if (error instanceof AuthAdapterError)
|
|
114
|
+
return error;
|
|
115
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
116
|
+
if (isKnownNetworkFailure(error, message)) {
|
|
117
|
+
return AuthAdapterError.networkError();
|
|
118
|
+
}
|
|
119
|
+
return AuthAdapterError.providerError('better-auth', message);
|
|
84
120
|
}
|
|
85
121
|
mapToState(value) {
|
|
86
122
|
if (value.isPending) {
|
|
@@ -106,4 +142,16 @@ export class BetterAuthAdapter {
|
|
|
106
142
|
};
|
|
107
143
|
}
|
|
108
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* Better Auth answers a social sign-in with `{ url, redirect: true }` and
|
|
147
|
+
* navigates the browser there; the session exists only after the callback.
|
|
148
|
+
*/
|
|
149
|
+
function toSignInOutcome(result) {
|
|
150
|
+
const data = result?.data;
|
|
151
|
+
if (typeof data === 'object' && data !== null && 'redirect' in data && data.redirect === true) {
|
|
152
|
+
const url = 'url' in data && typeof data.url === 'string' ? data.url : undefined;
|
|
153
|
+
return url === undefined ? { kind: 'redirect' } : { kind: 'redirect', url };
|
|
154
|
+
}
|
|
155
|
+
return { kind: 'signed-in' };
|
|
156
|
+
}
|
|
109
157
|
//# sourceMappingURL=better-auth.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"better-auth.js","sourceRoot":"","sources":["../../src/adapters/better-auth.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"better-auth.js","sourceRoot":"","sources":["../../src/adapters/better-auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,25 +4,48 @@
|
|
|
4
4
|
* Bridges @convex-dev/auth React hooks to the AuthAdapter interface.
|
|
5
5
|
* This is a React hook — must be called inside a component.
|
|
6
6
|
*/
|
|
7
|
+
import { type ListenerErrorHandler } from '../listeners.js';
|
|
7
8
|
import type { AuthAdapter, User } from '../types.js';
|
|
9
|
+
/** The part of `useAuthActions()` from `@convex-dev/auth/react` that the adapter calls. */
|
|
10
|
+
export interface ConvexAuthActions {
|
|
11
|
+
/** Resolves with `{ signingIn, redirect? }` in @convex-dev/auth. */
|
|
12
|
+
signIn: (provider: string, params?: Record<string, string>) => Promise<unknown>;
|
|
13
|
+
signOut: () => Promise<void>;
|
|
14
|
+
}
|
|
8
15
|
export interface ConvexAuthAdapterOptions {
|
|
9
16
|
/** Return value of useAuthActions() from @convex-dev/auth/react */
|
|
10
|
-
useAuthActions: () =>
|
|
11
|
-
signIn: (provider: string, params?: Record<string, unknown>) => Promise<void>;
|
|
12
|
-
signOut: () => Promise<void>;
|
|
13
|
-
};
|
|
17
|
+
useAuthActions: () => ConvexAuthActions;
|
|
14
18
|
/** Return value of useConvexAuth() from convex/react */
|
|
15
19
|
useConvexAuth: () => {
|
|
16
20
|
isAuthenticated: boolean;
|
|
17
21
|
isLoading: boolean;
|
|
18
22
|
};
|
|
19
|
-
/**
|
|
23
|
+
/**
|
|
24
|
+
* A Convex query hook that returns the current user, e.g. useQuery(api.users.me).
|
|
25
|
+
* While it returns `undefined` (still loading) the adapter reports `loading`.
|
|
26
|
+
* `null` while Convex is authenticated (no user document) is reported as
|
|
27
|
+
* `unauthenticated`.
|
|
28
|
+
*/
|
|
20
29
|
meQuery: () => User | null | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* Id of the Password provider used for `credentials` sign-in (default:
|
|
32
|
+
* `'password'`, the id of the stock `Password()` provider). Set it when the
|
|
33
|
+
* provider is configured with a custom `id`.
|
|
34
|
+
*/
|
|
35
|
+
passwordProviderId?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Receives errors thrown by `onAuthStateChange` subscribers. Without it,
|
|
38
|
+
* they are rethrown in a microtask. Either way the other subscribers still
|
|
39
|
+
* run.
|
|
40
|
+
*/
|
|
41
|
+
onListenerError?: ListenerErrorHandler;
|
|
21
42
|
}
|
|
22
43
|
/**
|
|
23
44
|
* React hook that creates an AuthAdapter backed by Convex.
|
|
24
45
|
*
|
|
25
|
-
* Must be called inside a ConvexProvider + ConvexAuthProvider tree.
|
|
46
|
+
* Must be called inside a ConvexProvider + ConvexAuthProvider tree. It returns
|
|
47
|
+
* the same adapter object on every render; the adapter always uses the latest
|
|
48
|
+
* actions and options passed to the hook.
|
|
26
49
|
*/
|
|
27
50
|
export declare function useConvexAuthAdapter(options: ConvexAuthAdapterOptions): AuthAdapter;
|
|
28
51
|
//# sourceMappingURL=convex.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"convex.d.ts","sourceRoot":"","sources":["../../src/adapters/convex.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"convex.d.ts","sourceRoot":"","sources":["../../src/adapters/convex.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,EAAE,KAAK,oBAAoB,EAAoC,MAAM,iBAAiB,CAAC;AAE9F,OAAO,KAAK,EACX,WAAW,EAIX,IAAI,EACJ,MAAM,aAAa,CAAC;AAOrB,2FAA2F;AAC3F,MAAM,WAAW,iBAAiB;IACjC,oEAAoE;IACpE,MAAM,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAChF,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7B;AAED,MAAM,WAAW,wBAAwB;IACxC,mEAAmE;IACnE,cAAc,EAAE,MAAM,iBAAiB,CAAC;IACxC,wDAAwD;IACxD,aAAa,EAAE,MAAM;QAAE,eAAe,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,OAAO,CAAA;KAAE,CAAC;IACtE;;;;;OAKG;IACH,OAAO,EAAE,MAAM,IAAI,GAAG,IAAI,GAAG,SAAS,CAAC;IACvC;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;OAIG;IACH,eAAe,CAAC,EAAE,oBAAoB,CAAC;CACvC;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,wBAAwB,GAAG,WAAW,CAmDnF"}
|