@terreno/rtk 0.1.0 → 0.3.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,329 @@
1
+ import {beforeEach, describe, expect, it, mock} from "bun:test";
2
+ import {
3
+ type BetterAuthState,
4
+ generateBetterAuthSlice,
5
+ selectBetterAuthError,
6
+ selectBetterAuthIsAuthenticated,
7
+ selectBetterAuthIsLoading,
8
+ selectBetterAuthUser,
9
+ selectBetterAuthUserId,
10
+ } from "./betterAuthSlice";
11
+ import type {BetterAuthClientConfig, BetterAuthUser} from "./betterAuthTypes";
12
+
13
+ // Mock Better Auth client
14
+ const createMockAuthClient = () => ({
15
+ getSession: mock(() =>
16
+ Promise.resolve({
17
+ data: {
18
+ session: {
19
+ createdAt: new Date(),
20
+ expiresAt: new Date(Date.now() + 3600000),
21
+ id: "session-456",
22
+ ipAddress: null,
23
+ updatedAt: new Date(),
24
+ userAgent: null,
25
+ userId: "user-123",
26
+ },
27
+ user: {
28
+ createdAt: new Date(),
29
+ email: "test@example.com",
30
+ emailVerified: true,
31
+ id: "user-123",
32
+ image: null,
33
+ name: "Test User",
34
+ updatedAt: new Date(),
35
+ },
36
+ },
37
+ })
38
+ ),
39
+ signIn: {
40
+ email: mock(() => Promise.resolve({data: {user: {id: "user-123"}}})),
41
+ social: mock(() => Promise.resolve({data: {user: {id: "user-123"}}})),
42
+ },
43
+ signOut: mock(() => Promise.resolve()),
44
+ signUp: {
45
+ email: mock(() => Promise.resolve({data: {user: {id: "user-123"}}})),
46
+ },
47
+ });
48
+
49
+ describe("BetterAuthClientConfig", () => {
50
+ it("defines config interface correctly", () => {
51
+ const config: BetterAuthClientConfig = {
52
+ baseURL: "http://localhost:3000",
53
+ scheme: "terreno",
54
+ storagePrefix: "myapp",
55
+ };
56
+
57
+ expect(config.baseURL).toBe("http://localhost:3000");
58
+ expect(config.scheme).toBe("terreno");
59
+ expect(config.storagePrefix).toBe("myapp");
60
+ });
61
+
62
+ it("allows minimal config without storagePrefix", () => {
63
+ const config: BetterAuthClientConfig = {
64
+ baseURL: "http://localhost:3000",
65
+ scheme: "terreno",
66
+ };
67
+
68
+ expect(config.storagePrefix).toBeUndefined();
69
+ });
70
+ });
71
+
72
+ describe("generateBetterAuthSlice", () => {
73
+ let mockAuthClient: ReturnType<typeof createMockAuthClient>;
74
+
75
+ beforeEach(() => {
76
+ mockAuthClient = createMockAuthClient();
77
+ });
78
+
79
+ it("creates a slice with initial state", () => {
80
+ // biome-ignore lint/suspicious/noExplicitAny: Mock client type
81
+ const betterAuthSlice = generateBetterAuthSlice({authClient: mockAuthClient as any});
82
+
83
+ const initialState = betterAuthSlice.reducer(undefined, {type: "@@INIT"});
84
+
85
+ expect(initialState.isAuthenticated).toBe(false);
86
+ expect(initialState.userId).toBeNull();
87
+ expect(initialState.user).toBeNull();
88
+ expect(initialState.isLoading).toBe(true);
89
+ expect(initialState.error).toBeNull();
90
+ });
91
+
92
+ it("setSession action updates state correctly", () => {
93
+ // biome-ignore lint/suspicious/noExplicitAny: Mock client type
94
+ const betterAuthSlice = generateBetterAuthSlice({authClient: mockAuthClient as any});
95
+
96
+ const user: BetterAuthUser = {
97
+ createdAt: new Date(),
98
+ email: "test@example.com",
99
+ emailVerified: true,
100
+ id: "user-123",
101
+ image: null,
102
+ name: "Test User",
103
+ updatedAt: new Date(),
104
+ };
105
+
106
+ const state = betterAuthSlice.reducer(
107
+ undefined,
108
+ betterAuthSlice.actions.setSession({user, userId: "user-123"})
109
+ );
110
+
111
+ expect(state.isAuthenticated).toBe(true);
112
+ expect(state.userId).toBe("user-123");
113
+ expect(state.user?.email).toBe("test@example.com");
114
+ expect(state.isLoading).toBe(false);
115
+ expect(state.error).toBeNull();
116
+ });
117
+
118
+ it("clearSession action resets state", () => {
119
+ // biome-ignore lint/suspicious/noExplicitAny: Mock client type
120
+ const betterAuthSlice = generateBetterAuthSlice({authClient: mockAuthClient as any});
121
+
122
+ // First set a session
123
+ let state = betterAuthSlice.reducer(
124
+ undefined,
125
+ betterAuthSlice.actions.setSession({
126
+ user: {
127
+ createdAt: new Date(),
128
+ email: "test@example.com",
129
+ emailVerified: true,
130
+ id: "user-123",
131
+ image: null,
132
+ name: "Test",
133
+ updatedAt: new Date(),
134
+ },
135
+ userId: "user-123",
136
+ })
137
+ );
138
+
139
+ // Then clear it
140
+ state = betterAuthSlice.reducer(state, betterAuthSlice.actions.clearSession());
141
+
142
+ expect(state.isAuthenticated).toBe(false);
143
+ expect(state.userId).toBeNull();
144
+ expect(state.user).toBeNull();
145
+ });
146
+
147
+ it("setLoading action updates loading state", () => {
148
+ // biome-ignore lint/suspicious/noExplicitAny: Mock client type
149
+ const betterAuthSlice = generateBetterAuthSlice({authClient: mockAuthClient as any});
150
+
151
+ let state = betterAuthSlice.reducer(undefined, betterAuthSlice.actions.setLoading(true));
152
+ expect(state.isLoading).toBe(true);
153
+
154
+ state = betterAuthSlice.reducer(state, betterAuthSlice.actions.setLoading(false));
155
+ expect(state.isLoading).toBe(false);
156
+ });
157
+
158
+ it("setError action updates error state", () => {
159
+ // biome-ignore lint/suspicious/noExplicitAny: Mock client type
160
+ const betterAuthSlice = generateBetterAuthSlice({authClient: mockAuthClient as any});
161
+
162
+ const state = betterAuthSlice.reducer(
163
+ undefined,
164
+ betterAuthSlice.actions.setError("Something went wrong")
165
+ );
166
+
167
+ expect(state.error).toBe("Something went wrong");
168
+ expect(state.isLoading).toBe(false);
169
+ });
170
+
171
+ it("logout action clears session state", () => {
172
+ // biome-ignore lint/suspicious/noExplicitAny: Mock client type
173
+ const betterAuthSlice = generateBetterAuthSlice({authClient: mockAuthClient as any});
174
+
175
+ // First set a session
176
+ let state = betterAuthSlice.reducer(
177
+ undefined,
178
+ betterAuthSlice.actions.setSession({
179
+ user: {
180
+ createdAt: new Date(),
181
+ email: "test@example.com",
182
+ emailVerified: true,
183
+ id: "user-123",
184
+ image: null,
185
+ name: "Test",
186
+ updatedAt: new Date(),
187
+ },
188
+ userId: "user-123",
189
+ })
190
+ );
191
+
192
+ // Then logout
193
+ state = betterAuthSlice.reducer(state, betterAuthSlice.actions.logout());
194
+
195
+ expect(state.isAuthenticated).toBe(false);
196
+ expect(state.userId).toBeNull();
197
+ expect(state.user).toBeNull();
198
+ });
199
+
200
+ it("returns middleware array", () => {
201
+ // biome-ignore lint/suspicious/noExplicitAny: Mock client type
202
+ const betterAuthSlice = generateBetterAuthSlice({authClient: mockAuthClient as any});
203
+
204
+ expect(Array.isArray(betterAuthSlice.middleware)).toBe(true);
205
+ expect(betterAuthSlice.middleware.length).toBeGreaterThan(0);
206
+ });
207
+
208
+ it("returns authClient reference", () => {
209
+ // biome-ignore lint/suspicious/noExplicitAny: Mock client type
210
+ const betterAuthSlice = generateBetterAuthSlice({authClient: mockAuthClient as any});
211
+
212
+ // biome-ignore lint/suspicious/noExplicitAny: Mock client type comparison
213
+ expect(betterAuthSlice.authClient).toBe(mockAuthClient as any);
214
+ });
215
+
216
+ it("syncSession function updates state from auth client", async () => {
217
+ // biome-ignore lint/suspicious/noExplicitAny: Mock client type
218
+ const betterAuthSlice = generateBetterAuthSlice({authClient: mockAuthClient as any});
219
+
220
+ // biome-ignore lint/suspicious/noExplicitAny: Test mock for dispatched actions
221
+ const dispatchedActions: any[] = [];
222
+ // biome-ignore lint/suspicious/noExplicitAny: Test mock dispatch function
223
+ const mockDispatch = (action: any) => {
224
+ dispatchedActions.push(action);
225
+ };
226
+
227
+ await betterAuthSlice.syncSession(mockDispatch);
228
+
229
+ // Should dispatch setLoading(true) then setSession
230
+ expect(dispatchedActions.length).toBeGreaterThanOrEqual(2);
231
+ expect(dispatchedActions[0].type).toBe("betterAuth/setLoading");
232
+ });
233
+ });
234
+
235
+ describe("Better Auth selectors", () => {
236
+ // biome-ignore lint/suspicious/noExplicitAny: Test mock state factory
237
+ const createMockState = (betterAuth: Partial<BetterAuthState> = {}): any => ({
238
+ betterAuth: {
239
+ error: null,
240
+ isAuthenticated: false,
241
+ isLoading: false,
242
+ lastSyncTimestamp: null,
243
+ user: null,
244
+ userId: null,
245
+ ...betterAuth,
246
+ },
247
+ });
248
+
249
+ it("selectBetterAuthIsAuthenticated returns correct value", () => {
250
+ expect(selectBetterAuthIsAuthenticated(createMockState({isAuthenticated: true}))).toBe(true);
251
+ expect(selectBetterAuthIsAuthenticated(createMockState({isAuthenticated: false}))).toBe(false);
252
+ });
253
+
254
+ it("selectBetterAuthUserId returns correct value", () => {
255
+ expect(selectBetterAuthUserId(createMockState({userId: "user-123"}))).toBe("user-123");
256
+ expect(selectBetterAuthUserId(createMockState({userId: null}))).toBeNull();
257
+ });
258
+
259
+ it("selectBetterAuthUser returns correct value", () => {
260
+ const user: BetterAuthUser = {
261
+ createdAt: new Date(),
262
+ email: "test@example.com",
263
+ emailVerified: true,
264
+ id: "user-123",
265
+ image: null,
266
+ name: "Test",
267
+ updatedAt: new Date(),
268
+ };
269
+
270
+ expect(selectBetterAuthUser(createMockState({user}))?.email).toBe("test@example.com");
271
+ expect(selectBetterAuthUser(createMockState({user: null}))).toBeNull();
272
+ });
273
+
274
+ it("selectBetterAuthIsLoading returns correct value", () => {
275
+ expect(selectBetterAuthIsLoading(createMockState({isLoading: true}))).toBe(true);
276
+ expect(selectBetterAuthIsLoading(createMockState({isLoading: false}))).toBe(false);
277
+ });
278
+
279
+ it("selectBetterAuthError returns correct value", () => {
280
+ expect(selectBetterAuthError(createMockState({error: "Error message"}))).toBe("Error message");
281
+ expect(selectBetterAuthError(createMockState({error: null}))).toBeNull();
282
+ });
283
+
284
+ it("selectors handle missing betterAuth state gracefully", () => {
285
+ // biome-ignore lint/suspicious/noExplicitAny: Test empty state for selector edge case
286
+ const emptyState = {} as any;
287
+
288
+ expect(selectBetterAuthIsAuthenticated(emptyState)).toBe(false);
289
+ expect(selectBetterAuthUserId(emptyState)).toBeNull();
290
+ expect(selectBetterAuthUser(emptyState)).toBeNull();
291
+ expect(selectBetterAuthIsLoading(emptyState)).toBe(false);
292
+ expect(selectBetterAuthError(emptyState)).toBeNull();
293
+ });
294
+ });
295
+
296
+ describe("BetterAuthUser interface", () => {
297
+ it("defines user data structure correctly", () => {
298
+ const user: BetterAuthUser = {
299
+ createdAt: new Date("2024-01-01"),
300
+ email: "test@example.com",
301
+ emailVerified: true,
302
+ id: "user-123",
303
+ image: "https://example.com/avatar.jpg",
304
+ name: "Test User",
305
+ updatedAt: new Date("2024-01-02"),
306
+ };
307
+
308
+ expect(user.id).toBe("user-123");
309
+ expect(user.email).toBe("test@example.com");
310
+ expect(user.name).toBe("Test User");
311
+ expect(user.image).toBe("https://example.com/avatar.jpg");
312
+ expect(user.emailVerified).toBe(true);
313
+ });
314
+
315
+ it("allows null values for optional fields", () => {
316
+ const user: BetterAuthUser = {
317
+ createdAt: new Date(),
318
+ email: "test@example.com",
319
+ emailVerified: false,
320
+ id: "user-123",
321
+ image: null,
322
+ name: null,
323
+ updatedAt: new Date(),
324
+ };
325
+
326
+ expect(user.name).toBeNull();
327
+ expect(user.image).toBeNull();
328
+ });
329
+ });
@@ -0,0 +1,307 @@
1
+ /**
2
+ * Better Auth Redux slice for session state management.
3
+ *
4
+ * Provides Redux integration for Better Auth, including session synchronization,
5
+ * login/logout actions, and selectors for auth state.
6
+ */
7
+
8
+ import {createListenerMiddleware, createSlice, type PayloadAction} from "@reduxjs/toolkit";
9
+
10
+ import type {BetterAuthClientInterface, BetterAuthUser} from "./betterAuthTypes";
11
+
12
+ /**
13
+ * Global logout action type for compatibility with other auth systems.
14
+ */
15
+ const LOGOUT_ACTION_TYPE = "auth/logout";
16
+
17
+ /**
18
+ * Root state type - loosely typed to avoid circular dependencies.
19
+ */
20
+ // biome-ignore lint/suspicious/noExplicitAny: RootState is loosely typed to work with any Redux store configuration.
21
+ type RootState = any;
22
+
23
+ /**
24
+ * Better Auth Redux state interface.
25
+ */
26
+ export interface BetterAuthState {
27
+ /**
28
+ * Whether the user is authenticated.
29
+ */
30
+ isAuthenticated: boolean;
31
+
32
+ /**
33
+ * The authenticated user's ID, or null if not authenticated.
34
+ */
35
+ userId: string | null;
36
+
37
+ /**
38
+ * The authenticated user data, or null if not authenticated.
39
+ */
40
+ user: BetterAuthUser | null;
41
+
42
+ /**
43
+ * Whether the auth state is currently loading.
44
+ */
45
+ isLoading: boolean;
46
+
47
+ /**
48
+ * Last error message, if any.
49
+ */
50
+ error: string | null;
51
+
52
+ /**
53
+ * Timestamp of the last session sync.
54
+ */
55
+ lastSyncTimestamp: number | null;
56
+ }
57
+
58
+ const initialState: BetterAuthState = {
59
+ error: null,
60
+ isAuthenticated: false,
61
+ isLoading: true,
62
+ lastSyncTimestamp: null,
63
+ user: null,
64
+ userId: null,
65
+ };
66
+
67
+ /**
68
+ * Configuration for generating the Better Auth slice.
69
+ */
70
+ export interface GenerateBetterAuthSliceConfig {
71
+ /**
72
+ * The Better Auth client instance.
73
+ */
74
+ authClient: BetterAuthClientInterface;
75
+
76
+ /**
77
+ * How often to sync the session state (in milliseconds).
78
+ * @default 60000 (1 minute)
79
+ */
80
+ syncInterval?: number;
81
+ }
82
+
83
+ /**
84
+ * Generates a Better Auth Redux slice with session management.
85
+ *
86
+ * @example
87
+ * ```typescript
88
+ * const authClient = createBetterAuthClient({
89
+ * baseURL: "http://localhost:3000",
90
+ * scheme: "terreno",
91
+ * });
92
+ *
93
+ * const betterAuthSlice = generateBetterAuthSlice({ authClient });
94
+ *
95
+ * // Add to your store
96
+ * const store = configureStore({
97
+ * reducer: {
98
+ * betterAuth: betterAuthSlice.reducer,
99
+ * },
100
+ * middleware: (getDefaultMiddleware) =>
101
+ * getDefaultMiddleware().concat(betterAuthSlice.middleware),
102
+ * });
103
+ *
104
+ * // Use in components
105
+ * const isAuthenticated = useSelector(selectBetterAuthIsAuthenticated);
106
+ * const user = useSelector(selectBetterAuthUser);
107
+ *
108
+ * // Trigger logout
109
+ * dispatch(betterAuthSlice.actions.logout());
110
+ * ```
111
+ */
112
+ export const generateBetterAuthSlice = (config: GenerateBetterAuthSliceConfig) => {
113
+ const {authClient} = config;
114
+
115
+ const betterAuthSlice = createSlice({
116
+ initialState,
117
+ name: "betterAuth",
118
+ reducers: {
119
+ /**
120
+ * Clear the session data on logout.
121
+ */
122
+ clearSession: (state) => {
123
+ state.isAuthenticated = false;
124
+ state.userId = null;
125
+ state.user = null;
126
+ state.isLoading = false;
127
+ state.error = null;
128
+ state.lastSyncTimestamp = Date.now();
129
+ },
130
+
131
+ /**
132
+ * Trigger logout action.
133
+ */
134
+ logout: (state) => {
135
+ state.isAuthenticated = false;
136
+ state.userId = null;
137
+ state.user = null;
138
+ state.isLoading = false;
139
+ state.error = null;
140
+ },
141
+
142
+ /**
143
+ * Set error state.
144
+ */
145
+ setError: (state, action: PayloadAction<string | null>) => {
146
+ state.error = action.payload;
147
+ state.isLoading = false;
148
+ },
149
+
150
+ /**
151
+ * Set loading state.
152
+ */
153
+ setLoading: (state, action: PayloadAction<boolean>) => {
154
+ state.isLoading = action.payload;
155
+ },
156
+ /**
157
+ * Set the session data after successful authentication or session refresh.
158
+ */
159
+ setSession: (state, action: PayloadAction<{user: BetterAuthUser; userId: string}>) => {
160
+ state.isAuthenticated = true;
161
+ state.userId = action.payload.userId;
162
+ state.user = action.payload.user;
163
+ state.isLoading = false;
164
+ state.error = null;
165
+ state.lastSyncTimestamp = Date.now();
166
+ },
167
+ },
168
+ });
169
+
170
+ // Listener middleware for handling logout side effects
171
+ const logoutListenerMiddleware = createListenerMiddleware();
172
+
173
+ // Handle logout action - sign out from Better Auth
174
+ logoutListenerMiddleware.startListening({
175
+ actionCreator: betterAuthSlice.actions.logout,
176
+ effect: async (_action, _listenerApi) => {
177
+ try {
178
+ await authClient.signOut();
179
+ console.debug("Better Auth: Signed out successfully");
180
+ } catch (error) {
181
+ console.error("Better Auth: Error signing out:", error);
182
+ }
183
+ },
184
+ });
185
+
186
+ // Also listen for the global logout action type for compatibility
187
+ logoutListenerMiddleware.startListening({
188
+ effect: async (_action, listenerApi) => {
189
+ try {
190
+ await authClient.signOut();
191
+ listenerApi.dispatch(betterAuthSlice.actions.clearSession());
192
+ console.debug("Better Auth: Signed out via global logout action");
193
+ } catch (error) {
194
+ console.error("Better Auth: Error signing out:", error);
195
+ }
196
+ },
197
+ type: LOGOUT_ACTION_TYPE,
198
+ });
199
+
200
+ /**
201
+ * Syncs the session state from Better Auth to Redux.
202
+ * Call this on app startup and periodically to keep state in sync.
203
+ */
204
+ // biome-ignore lint/suspicious/noExplicitAny: Redux dispatch type varies by store configuration
205
+ const syncSession = async (dispatch: any): Promise<void> => {
206
+ dispatch(betterAuthSlice.actions.setLoading(true));
207
+
208
+ try {
209
+ const sessionData = await authClient.getSession();
210
+
211
+ if (sessionData?.data?.user && sessionData?.data?.session) {
212
+ dispatch(
213
+ betterAuthSlice.actions.setSession({
214
+ user: sessionData.data.user as BetterAuthUser,
215
+ userId: sessionData.data.user.id,
216
+ })
217
+ );
218
+ } else {
219
+ dispatch(betterAuthSlice.actions.clearSession());
220
+ }
221
+ } catch (error) {
222
+ console.error("Better Auth: Error syncing session:", error);
223
+ dispatch(betterAuthSlice.actions.setError("Failed to sync session"));
224
+ dispatch(betterAuthSlice.actions.clearSession());
225
+ }
226
+ };
227
+
228
+ return {
229
+ /**
230
+ * Actions for the Better Auth slice.
231
+ */
232
+ actions: betterAuthSlice.actions,
233
+
234
+ /**
235
+ * The Better Auth client instance.
236
+ */
237
+ authClient,
238
+
239
+ /**
240
+ * Middleware for handling Better Auth side effects.
241
+ */
242
+ middleware: [logoutListenerMiddleware.middleware],
243
+
244
+ /**
245
+ * The reducer for the Better Auth slice.
246
+ */
247
+ reducer: betterAuthSlice.reducer,
248
+ /**
249
+ * The Better Auth Redux slice.
250
+ */
251
+ slice: betterAuthSlice,
252
+
253
+ /**
254
+ * Function to sync session state from Better Auth to Redux.
255
+ */
256
+ syncSession,
257
+ };
258
+ };
259
+
260
+ /**
261
+ * Type for the return value of generateBetterAuthSlice.
262
+ */
263
+ export type BetterAuthSlice = ReturnType<typeof generateBetterAuthSlice>;
264
+
265
+ // Selectors
266
+
267
+ /**
268
+ * Selects the entire Better Auth state.
269
+ */
270
+ export const selectBetterAuthState = (state: RootState): BetterAuthState | undefined =>
271
+ // biome-ignore lint/suspicious/noExplicitAny: RootState is loosely typed
272
+ (state as any).betterAuth;
273
+
274
+ /**
275
+ * Selects whether the user is authenticated.
276
+ */
277
+ export const selectBetterAuthIsAuthenticated = (state: RootState): boolean =>
278
+ // biome-ignore lint/suspicious/noExplicitAny: RootState is loosely typed
279
+ (state as any).betterAuth?.isAuthenticated ?? false;
280
+
281
+ /**
282
+ * Selects the current user ID.
283
+ */
284
+ export const selectBetterAuthUserId = (state: RootState): string | null =>
285
+ // biome-ignore lint/suspicious/noExplicitAny: RootState is loosely typed
286
+ (state as any).betterAuth?.userId ?? null;
287
+
288
+ /**
289
+ * Selects the current user data.
290
+ */
291
+ export const selectBetterAuthUser = (state: RootState): BetterAuthUser | null =>
292
+ // biome-ignore lint/suspicious/noExplicitAny: RootState is loosely typed
293
+ (state as any).betterAuth?.user ?? null;
294
+
295
+ /**
296
+ * Selects whether the auth state is loading.
297
+ */
298
+ export const selectBetterAuthIsLoading = (state: RootState): boolean =>
299
+ // biome-ignore lint/suspicious/noExplicitAny: RootState is loosely typed
300
+ (state as any).betterAuth?.isLoading ?? false;
301
+
302
+ /**
303
+ * Selects the last error message.
304
+ */
305
+ export const selectBetterAuthError = (state: RootState): string | null =>
306
+ // biome-ignore lint/suspicious/noExplicitAny: RootState is loosely typed
307
+ (state as any).betterAuth?.error ?? null;
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Better Auth types for use in Redux slices and components.
3
+ *
4
+ * This file contains only type definitions to avoid importing React Native
5
+ * dependencies in environments that don't support them (e.g., tests).
6
+ */
7
+
8
+ /**
9
+ * Configuration options for the Better Auth client.
10
+ */
11
+ export interface BetterAuthClientConfig {
12
+ /**
13
+ * Base URL of the auth server (e.g., "http://localhost:3000").
14
+ */
15
+ baseURL: string;
16
+
17
+ /**
18
+ * App URL scheme for deep linking (e.g., "terreno").
19
+ */
20
+ scheme: string;
21
+
22
+ /**
23
+ * Storage key prefix for auth tokens.
24
+ * @default "terreno"
25
+ */
26
+ storagePrefix?: string;
27
+ }
28
+
29
+ /**
30
+ * User data from Better Auth session.
31
+ */
32
+ export interface BetterAuthUser {
33
+ id: string;
34
+ email: string;
35
+ name: string | null;
36
+ image: string | null;
37
+ emailVerified: boolean;
38
+ createdAt: Date;
39
+ updatedAt: Date;
40
+ }
41
+
42
+ /**
43
+ * Session data from Better Auth.
44
+ */
45
+ export interface BetterAuthSession {
46
+ id: string;
47
+ userId: string;
48
+ expiresAt: Date;
49
+ ipAddress: string | null;
50
+ userAgent: string | null;
51
+ createdAt: Date;
52
+ updatedAt: Date;
53
+ }
54
+
55
+ /**
56
+ * Combined session and user data from Better Auth.
57
+ */
58
+ export interface BetterAuthSessionData {
59
+ session: BetterAuthSession;
60
+ user: BetterAuthUser;
61
+ }
62
+
63
+ /**
64
+ * OAuth provider types supported by Better Auth.
65
+ */
66
+ export type BetterAuthOAuthProvider = "google" | "github" | "apple";
67
+
68
+ /**
69
+ * Minimal interface for the Better Auth client used by the Redux slice.
70
+ * This interface defines only the methods needed by the slice, allowing
71
+ * tests to use mock clients without importing React Native.
72
+ */
73
+ export interface BetterAuthClientInterface {
74
+ getSession: () => Promise<{
75
+ data?: {
76
+ user?: BetterAuthUser;
77
+ session?: BetterAuthSession;
78
+ };
79
+ }>;
80
+ signOut: () => Promise<void>;
81
+ }