@terreno/mcp 57.21.0 → 58.0.0-beta.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/CHANGELOG.md +43 -0
- package/README.md +57 -3
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +17 -16
- package/dist/bootstrap.js.map +1 -1
- package/dist/docs/guidelines/admin-frontend/guidelines/core.md +6 -5
- package/dist/docs/guidelines/ui/guidelines/core.md +1 -1
- package/dist/docs/prompts/debug.md +6 -4
- package/dist/docs/prompts/screen.md +11 -17
- package/dist/docs/resources/overview.md +5 -3
- package/dist/docs/resources/patterns.md +157 -238
- package/dist/docs/resources/syncdb.md +44 -16
- package/dist/docs/templates/bootstrap/CLAUDE.template.md +2 -2
- package/dist/docs/templates/bootstrap/cursorrules.template.md +7 -5
- package/dist/docs/templates/bootstrap/frontend/app/(tabs)/profile.template.tsx +8 -12
- package/dist/docs/templates/bootstrap/frontend/app/_layout.template.tsx +75 -52
- package/dist/docs/templates/bootstrap/frontend/app/login.template.tsx +46 -108
- package/dist/docs/templates/bootstrap/frontend/app/signup.template.tsx +53 -0
- package/dist/docs/templates/bootstrap/frontend/lib/betterAuth.template.md +18 -0
- package/dist/docs/templates/bootstrap/frontend/store/apiClient.template.md +33 -0
- package/dist/docs/templates/bootstrap/frontend/store/apiSdk.template.md +76 -0
- package/dist/docs/templates/bootstrap/frontend/store/syncdb.template.md +56 -0
- package/dist/docs/templates/bootstrap/frontend/utils/index.template.md +0 -4
- package/dist/docs/upgrades/58.0.0-beta.1.md +504 -0
- package/dist/docs/upgrades/58.0.0.md +495 -4
- package/dist/docs/versioned/next/README.md +2 -2
- package/dist/docs/versioned/next/approval-rubric.md +2 -2
- package/dist/docs/versioned/next/explanation/admin-interface.md +6 -5
- package/dist/docs/versioned/next/explanation/admin-plugin-frontend.md +1 -1
- package/dist/docs/versioned/next/explanation/ai-workflows.md +5 -3
- package/dist/docs/versioned/next/explanation/announcements/rtk-deprecation.md +16 -11
- package/dist/docs/versioned/next/explanation/authentication.md +62 -43
- package/dist/docs/versioned/next/explanation/dependency-management.md +1 -1
- package/dist/docs/versioned/next/explanation/deployment-baseline.md +1 -1
- package/dist/docs/versioned/next/explanation/example-coverage.md +2 -2
- package/dist/docs/versioned/next/explanation/explicit-any-policy.md +5 -6
- package/dist/docs/versioned/next/explanation/gcp-hosting-architecture.md +1 -1
- package/dist/docs/versioned/next/explanation/local-first-data.md +11 -9
- package/dist/docs/versioned/next/explanation/model-router-actions.md +1 -1
- package/dist/docs/versioned/next/explanation/modular-api-design.md +2 -2
- package/dist/docs/versioned/next/explanation/no-barrel-imports.md +5 -5
- package/dist/docs/versioned/next/explanation/roadmap-seed-issues.md +4 -3
- package/dist/docs/versioned/next/explanation/source-rules.md +2 -2
- package/dist/docs/versioned/next/explanation/typescript-configuration.md +2 -4
- package/dist/docs/versioned/next/explanation/versioning-policy.md +3 -3
- package/dist/docs/versioned/next/how-to/README.md +3 -2
- package/dist/docs/versioned/next/how-to/add-feature-flags.md +50 -25
- package/dist/docs/versioned/next/how-to/add-organizations.md +2 -1
- package/dist/docs/versioned/next/how-to/admin-custom-home.md +8 -2
- package/dist/docs/versioned/next/how-to/admin-custom-widget.md +0 -1
- package/dist/docs/versioned/next/how-to/agent-ui-asks.md +6 -6
- package/dist/docs/versioned/next/how-to/build-admin-screens.md +10 -8
- package/dist/docs/versioned/next/how-to/build-for-web.md +1 -1
- package/dist/docs/versioned/next/how-to/call-external-apis.md +2 -2
- package/dist/docs/versioned/next/how-to/circleci.md +12 -6
- package/dist/docs/versioned/next/how-to/configure-better-auth.md +114 -60
- package/dist/docs/versioned/next/how-to/connect-mcp-service-token.md +10 -4
- package/dist/docs/versioned/next/how-to/create-a-terreno-app.md +14 -2
- package/dist/docs/versioned/next/how-to/debug-with-mcp.md +8 -13
- package/dist/docs/versioned/next/how-to/expose-mcp-tools.md +15 -4
- package/dist/docs/versioned/next/how-to/generate-the-client-sdk.md +33 -0
- package/dist/docs/versioned/next/how-to/github-actions-ci.md +2 -2
- package/dist/docs/versioned/next/how-to/in-app-notifications.md +1 -1
- package/dist/docs/versioned/next/how-to/migrate-rtk-to-syncdb.md +318 -447
- package/dist/docs/versioned/next/how-to/password-reset.md +1 -1
- package/dist/docs/versioned/next/how-to/product-announcements.md +6 -3
- package/dist/docs/versioned/next/how-to/rate-limiting.md +38 -16
- package/dist/docs/versioned/next/how-to/run-mongodb-migrations.md +19 -0
- package/dist/docs/versioned/next/how-to/run-tests-locally.md +2 -3
- package/dist/docs/versioned/next/how-to/set-up-social-login.md +1 -1
- package/dist/docs/versioned/next/how-to/set-up-terreno-mcp.md +2 -2
- package/dist/docs/versioned/next/how-to/track-upstream-expo.md +1 -1
- package/dist/docs/versioned/next/how-to/upgrade-banner.md +5 -3
- package/dist/docs/versioned/next/how-to/upgrade-terreno.md +5 -5
- package/dist/docs/versioned/next/how-to/use-syncdb-correctly.md +1 -1
- package/dist/docs/versioned/next/how-to/websocket-integration.md +113 -402
- package/dist/docs/versioned/next/implementationPlans/b2b-platform-program.md +1 -1
- package/dist/docs/versioned/next/implementationPlans/migrate-admin-to-syncdb.md +5 -0
- package/dist/docs/versioned/next/implementationPlans/native-module-baseline.md +29 -5
- package/dist/docs/versioned/next/implementationPlans/remove-legacy-realtime.md +1 -1
- package/dist/docs/versioned/next/implementationPlans/remove-rtk.md +185 -0
- package/dist/docs/versioned/next/reference/README.md +1 -1
- package/dist/docs/versioned/next/reference/admin-frontend.md +85 -70
- package/dist/docs/versioned/next/reference/admin-spa.md +10 -4
- package/dist/docs/versioned/next/reference/ai.md +36 -4
- package/dist/docs/versioned/next/reference/announcements.md +3 -3
- package/dist/docs/versioned/next/reference/api.md +39 -24
- package/dist/docs/versioned/next/reference/cli.md +3 -4
- package/dist/docs/versioned/next/reference/environment-variables.md +16 -16
- package/dist/docs/versioned/next/reference/feature-flags.md +48 -7
- package/dist/docs/versioned/next/reference/jobs.md +2 -2
- package/dist/docs/versioned/next/reference/legacy/rtk.md +25 -600
- package/dist/docs/versioned/next/reference/mcp-server.md +441 -1158
- package/dist/docs/versioned/next/reference/syncdb.md +426 -16
- package/dist/docs/versioned/next/reference/test.md +28 -3
- package/dist/docs/versioned/next/reference/ui.md +132 -19
- package/dist/docs/versioned/next/tasks/native-module-baseline.md +8 -4
- package/dist/docs/versioned/next/tasks/rate-limiting.md +9 -2
- package/dist/docs/versioned/next/tasks/remove-legacy-realtime.md +5 -5
- package/dist/docs/versioned/next/tasks/remove-rtk.md +155 -0
- package/dist/local/localTools.d.ts.map +1 -1
- package/dist/local/localTools.js +3 -54
- package/dist/local/localTools.js.map +1 -1
- package/dist/local/tools/runtime.d.ts +0 -21
- package/dist/local/tools/runtime.d.ts.map +1 -1
- package/dist/local/tools/runtime.js +0 -176
- package/dist/local/tools/runtime.js.map +1 -1
- package/dist/local/tools/syncDb.d.ts +0 -11
- package/dist/local/tools/syncDb.d.ts.map +1 -1
- package/dist/local/tools/syncDb.js +1 -5
- package/dist/local/tools/syncDb.js.map +1 -1
- package/dist/packageGuidelines.d.ts +3 -2
- package/dist/packageGuidelines.d.ts.map +1 -1
- package/dist/packageGuidelines.js +4 -11
- package/dist/packageGuidelines.js.map +1 -1
- package/dist/prompts.d.ts.map +1 -1
- package/dist/prompts.js +17 -18
- package/dist/prompts.js.map +1 -1
- package/dist/resources.d.ts.map +1 -1
- package/dist/resources.js +1 -8
- package/dist/resources.js.map +1 -1
- package/dist/search/chunker.d.ts +1 -1
- package/dist/search/chunker.d.ts.map +1 -1
- package/dist/search/inferPackages.d.ts +1 -1
- package/dist/search/inferPackages.js +5 -5
- package/dist/search/inferPackages.js.map +1 -1
- package/dist/tools.d.ts +6 -0
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +126 -119
- package/dist/tools.js.map +1 -1
- package/package.json +6 -6
- package/src/docs/guidelines/admin-frontend/guidelines/core.md +6 -5
- package/src/docs/guidelines/ui/guidelines/core.md +1 -1
- package/src/docs/prompts/debug.md +6 -4
- package/src/docs/prompts/screen.md +11 -17
- package/src/docs/resources/overview.md +5 -3
- package/src/docs/resources/patterns.md +157 -238
- package/src/docs/resources/syncdb.md +44 -16
- package/src/docs/templates/bootstrap/CLAUDE.template.md +2 -2
- package/src/docs/templates/bootstrap/cursorrules.template.md +7 -5
- package/src/docs/templates/bootstrap/frontend/app/(tabs)/profile.template.tsx +8 -12
- package/src/docs/templates/bootstrap/frontend/app/_layout.template.tsx +75 -52
- package/src/docs/templates/bootstrap/frontend/app/login.template.tsx +46 -108
- package/src/docs/templates/bootstrap/frontend/app/signup.template.tsx +53 -0
- package/src/docs/templates/bootstrap/frontend/lib/betterAuth.template.md +18 -0
- package/src/docs/templates/bootstrap/frontend/store/apiClient.template.md +33 -0
- package/src/docs/templates/bootstrap/frontend/store/apiSdk.template.md +76 -0
- package/src/docs/templates/bootstrap/frontend/store/syncdb.template.md +56 -0
- package/src/docs/templates/bootstrap/frontend/utils/index.template.md +0 -4
- package/src/docs/upgrades/58.0.0-beta.1.md +504 -0
- package/src/docs/upgrades/58.0.0.md +495 -4
- package/src/docs/versioned/next/README.md +2 -2
- package/src/docs/versioned/next/approval-rubric.md +2 -2
- package/src/docs/versioned/next/explanation/admin-interface.md +6 -5
- package/src/docs/versioned/next/explanation/admin-plugin-frontend.md +1 -1
- package/src/docs/versioned/next/explanation/ai-workflows.md +5 -3
- package/src/docs/versioned/next/explanation/announcements/rtk-deprecation.md +16 -11
- package/src/docs/versioned/next/explanation/authentication.md +62 -43
- package/src/docs/versioned/next/explanation/dependency-management.md +1 -1
- package/src/docs/versioned/next/explanation/deployment-baseline.md +1 -1
- package/src/docs/versioned/next/explanation/example-coverage.md +2 -2
- package/src/docs/versioned/next/explanation/explicit-any-policy.md +5 -6
- package/src/docs/versioned/next/explanation/gcp-hosting-architecture.md +1 -1
- package/src/docs/versioned/next/explanation/local-first-data.md +11 -9
- package/src/docs/versioned/next/explanation/model-router-actions.md +1 -1
- package/src/docs/versioned/next/explanation/modular-api-design.md +2 -2
- package/src/docs/versioned/next/explanation/no-barrel-imports.md +5 -5
- package/src/docs/versioned/next/explanation/roadmap-seed-issues.md +4 -3
- package/src/docs/versioned/next/explanation/source-rules.md +2 -2
- package/src/docs/versioned/next/explanation/typescript-configuration.md +2 -4
- package/src/docs/versioned/next/explanation/versioning-policy.md +3 -3
- package/src/docs/versioned/next/how-to/README.md +3 -2
- package/src/docs/versioned/next/how-to/add-feature-flags.md +50 -25
- package/src/docs/versioned/next/how-to/add-organizations.md +2 -1
- package/src/docs/versioned/next/how-to/admin-custom-home.md +8 -2
- package/src/docs/versioned/next/how-to/admin-custom-widget.md +0 -1
- package/src/docs/versioned/next/how-to/agent-ui-asks.md +6 -6
- package/src/docs/versioned/next/how-to/build-admin-screens.md +10 -8
- package/src/docs/versioned/next/how-to/build-for-web.md +1 -1
- package/src/docs/versioned/next/how-to/call-external-apis.md +2 -2
- package/src/docs/versioned/next/how-to/circleci.md +12 -6
- package/src/docs/versioned/next/how-to/configure-better-auth.md +114 -60
- package/src/docs/versioned/next/how-to/connect-mcp-service-token.md +10 -4
- package/src/docs/versioned/next/how-to/create-a-terreno-app.md +14 -2
- package/src/docs/versioned/next/how-to/debug-with-mcp.md +8 -13
- package/src/docs/versioned/next/how-to/expose-mcp-tools.md +15 -4
- package/src/docs/versioned/next/how-to/generate-the-client-sdk.md +33 -0
- package/src/docs/versioned/next/how-to/github-actions-ci.md +2 -2
- package/src/docs/versioned/next/how-to/in-app-notifications.md +1 -1
- package/src/docs/versioned/next/how-to/migrate-rtk-to-syncdb.md +318 -447
- package/src/docs/versioned/next/how-to/password-reset.md +1 -1
- package/src/docs/versioned/next/how-to/product-announcements.md +6 -3
- package/src/docs/versioned/next/how-to/rate-limiting.md +38 -16
- package/src/docs/versioned/next/how-to/run-mongodb-migrations.md +19 -0
- package/src/docs/versioned/next/how-to/run-tests-locally.md +2 -3
- package/src/docs/versioned/next/how-to/set-up-social-login.md +1 -1
- package/src/docs/versioned/next/how-to/set-up-terreno-mcp.md +2 -2
- package/src/docs/versioned/next/how-to/track-upstream-expo.md +1 -1
- package/src/docs/versioned/next/how-to/upgrade-banner.md +5 -3
- package/src/docs/versioned/next/how-to/upgrade-terreno.md +5 -5
- package/src/docs/versioned/next/how-to/use-syncdb-correctly.md +1 -1
- package/src/docs/versioned/next/how-to/websocket-integration.md +113 -402
- package/src/docs/versioned/next/implementationPlans/b2b-platform-program.md +1 -1
- package/src/docs/versioned/next/implementationPlans/migrate-admin-to-syncdb.md +5 -0
- package/src/docs/versioned/next/implementationPlans/native-module-baseline.md +29 -5
- package/src/docs/versioned/next/implementationPlans/remove-legacy-realtime.md +1 -1
- package/src/docs/versioned/next/implementationPlans/remove-rtk.md +185 -0
- package/src/docs/versioned/next/reference/README.md +1 -1
- package/src/docs/versioned/next/reference/admin-frontend.md +85 -70
- package/src/docs/versioned/next/reference/admin-spa.md +10 -4
- package/src/docs/versioned/next/reference/ai.md +36 -4
- package/src/docs/versioned/next/reference/announcements.md +3 -3
- package/src/docs/versioned/next/reference/api.md +39 -24
- package/src/docs/versioned/next/reference/cli.md +3 -4
- package/src/docs/versioned/next/reference/environment-variables.md +16 -16
- package/src/docs/versioned/next/reference/feature-flags.md +48 -7
- package/src/docs/versioned/next/reference/jobs.md +2 -2
- package/src/docs/versioned/next/reference/legacy/rtk.md +25 -600
- package/src/docs/versioned/next/reference/mcp-server.md +441 -1158
- package/src/docs/versioned/next/reference/syncdb.md +426 -16
- package/src/docs/versioned/next/reference/test.md +28 -3
- package/src/docs/versioned/next/reference/ui.md +132 -19
- package/src/docs/versioned/next/tasks/native-module-baseline.md +8 -4
- package/src/docs/versioned/next/tasks/rate-limiting.md +9 -2
- package/src/docs/versioned/next/tasks/remove-legacy-realtime.md +5 -5
- package/src/docs/versioned/next/tasks/remove-rtk.md +155 -0
- package/dist/docs/guidelines/rtk/guidelines/core.md +0 -26
- package/dist/docs/resources/rtk.md +0 -181
- package/dist/docs/templates/bootstrap/frontend/openapi-config.template.md +0 -16
- package/dist/docs/templates/bootstrap/frontend/scripts/generate-sdk.template.md +0 -49
- package/dist/docs/templates/bootstrap/frontend/store/appState.template.md +0 -45
- package/dist/docs/templates/bootstrap/frontend/store/errors.template.md +0 -51
- package/dist/docs/templates/bootstrap/frontend/store/index.template.md +0 -86
- package/dist/docs/templates/bootstrap/frontend/store/openApiSdk.template.md +0 -49
- package/dist/docs/templates/bootstrap/frontend/store/sdk.template.md +0 -80
- package/src/docs/guidelines/rtk/guidelines/core.md +0 -26
- package/src/docs/resources/rtk.md +0 -181
- package/src/docs/templates/bootstrap/frontend/openapi-config.template.md +0 -16
- package/src/docs/templates/bootstrap/frontend/scripts/generate-sdk.template.md +0 -49
- package/src/docs/templates/bootstrap/frontend/store/appState.template.md +0 -45
- package/src/docs/templates/bootstrap/frontend/store/errors.template.md +0 -51
- package/src/docs/templates/bootstrap/frontend/store/index.template.md +0 -86
- package/src/docs/templates/bootstrap/frontend/store/openApiSdk.template.md +0 -49
- package/src/docs/templates/bootstrap/frontend/store/sdk.template.md +0 -80
|
@@ -1,600 +1,25 @@
|
|
|
1
|
-
# @terreno/rtk (
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
Creates a complete Redux auth system with JWT token management, automatic token refresh, and secure storage.
|
|
28
|
-
|
|
29
|
-
``````typescript
|
|
30
|
-
import {generateAuthSlice} from "@terreno/rtk";
|
|
31
|
-
import {configureStore} from "@reduxjs/toolkit";
|
|
32
|
-
import {openapi} from "./openApiSdk";
|
|
33
|
-
|
|
34
|
-
const {authReducer, logout, setUserId, middleware} = generateAuthSlice(openapi);
|
|
35
|
-
|
|
36
|
-
export const store = configureStore({
|
|
37
|
-
reducer: {
|
|
38
|
-
auth: authReducer,
|
|
39
|
-
[openapi.reducerPath]: openapi.reducer,
|
|
40
|
-
},
|
|
41
|
-
middleware: (getDefault) =>
|
|
42
|
-
getDefault().concat(openapi.middleware, ...middleware),
|
|
43
|
-
});
|
|
44
|
-
``````
|
|
45
|
-
|
|
46
|
-
**Returns:**
|
|
47
|
-
- `authReducer` — Redux reducer for auth state
|
|
48
|
-
- `authSlice` — Full Redux slice
|
|
49
|
-
- `logout` — Action to clear tokens and reset state. A logout that ends a signed-in session also purges the API's RTK Query cache (`resetApiState`), so the next user never gets the previous user's cached responses
|
|
50
|
-
- `setUserId` — Action to set current user ID
|
|
51
|
-
- `tokenRefreshedSuccess` — Action signaling token refresh
|
|
52
|
-
- `middleware` — Login/logout listener middleware array
|
|
53
|
-
|
|
54
|
-
**Auth State:**
|
|
55
|
-
``````typescript
|
|
56
|
-
{
|
|
57
|
-
userId: string | null;
|
|
58
|
-
error: string | null;
|
|
59
|
-
lastTokenRefreshTimestamp: number | null;
|
|
60
|
-
}
|
|
61
|
-
``````
|
|
62
|
-
|
|
63
|
-
**Built-in Endpoints:**
|
|
64
|
-
- `emailLogin` — POST `/auth/login`
|
|
65
|
-
- `emailSignUp` — POST `/auth/signup`
|
|
66
|
-
- `googleLogin` — POST `/auth/google`
|
|
67
|
-
- `createEmailUser` — Create user without login (admin use)
|
|
68
|
-
- `resetPassword` — POST `/resetPassword`
|
|
69
|
-
|
|
70
|
-
**Token Storage:**
|
|
71
|
-
- **Native (iOS/Android):** Secure encrypted storage via `expo-secure-store`
|
|
72
|
-
- **Web:** `@react-native-async-storage/async-storage` with SSR safety
|
|
73
|
-
- Automatic storage/retrieval on login/logout via listener middleware
|
|
74
|
-
|
|
75
|
-
**Selectors:**
|
|
76
|
-
``````typescript
|
|
77
|
-
import {selectCurrentUserId, useSelectCurrentUserId} from "@terreno/rtk";
|
|
78
|
-
|
|
79
|
-
// Hook version
|
|
80
|
-
const userId = useSelectCurrentUserId();
|
|
81
|
-
|
|
82
|
-
// Selector version
|
|
83
|
-
const userId = selectCurrentUserId(state);
|
|
84
|
-
``````
|
|
85
|
-
|
|
86
|
-
### Better Auth Integration
|
|
87
|
-
|
|
88
|
-
For apps using Better Auth instead of JWT:
|
|
89
|
-
|
|
90
|
-
``````typescript
|
|
91
|
-
import {configureStore} from "@reduxjs/toolkit";
|
|
92
|
-
import {createBetterAuthClient, generateBetterAuthSlice} from "@terreno/rtk";
|
|
93
|
-
import {api} from "./sdk"; // the app's generated RTK Query API
|
|
94
|
-
|
|
95
|
-
// Create Better Auth client. `scheme` is the app's deep-link scheme (required).
|
|
96
|
-
export const authClient = createBetterAuthClient({
|
|
97
|
-
baseURL: process.env.EXPO_PUBLIC_API_URL || "http://localhost:4000",
|
|
98
|
-
scheme: "myapp",
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
// Generate the session Redux slice; list the APIs whose cache belongs to the signed-in user
|
|
102
|
-
const betterAuth = generateBetterAuthSlice({apisToReset: [api], authClient});
|
|
103
|
-
|
|
104
|
-
export const store = configureStore({
|
|
105
|
-
reducer: {
|
|
106
|
-
betterAuth: betterAuth.reducer, // selectors read `state.betterAuth`
|
|
107
|
-
// ... other reducers
|
|
108
|
-
},
|
|
109
|
-
middleware: (getDefault) => getDefault().concat(...betterAuth.middleware),
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
// Call after sign-in, sign-out, and at app startup to copy the session into Redux
|
|
113
|
-
void betterAuth.syncSession(store.dispatch);
|
|
114
|
-
``````
|
|
115
|
-
|
|
116
|
-
**Key exports:**
|
|
117
|
-
- `createBetterAuthClient({baseURL, scheme, storagePrefix?, webAuth?, bearerStorage?, features?})` — Factory for Better Auth client with Expo support. `webAuth: "bearer"` authenticates the web client with a stored bearer token and no cookies, for a frontend and API on different sites ([how-to](../../how-to/configure-better-auth.md#frontend-and-api-on-different-sites)). `features` (`BetterAuthClientFeatures`: `twoFactor?: boolean | {onTwoFactorRedirect}`, `passkey?`, `multiSession?`, `lastLoginMethod?`, `oneTimeToken?`) adds `twoFactorClient`, `passkeyClient` (`@better-auth/passkey/client`), `multiSessionClient`, `lastLoginMethodClient` and `oneTimeTokenClient` after the Expo plugin, and types their namespaces (`client.twoFactor.enable`, `client.passkey.addPasskey`, `client.multiSession.listDeviceSessions`, `client.getLastUsedLoginMethod`, `client.oneTimeToken.verify`). Without `features`, or when its type does not name the features (a config typed `BetterAuthClientConfig`), the return type is `BetterAuthClient`, unchanged. In bearer mode `multiSession` lists no sessions and 2FA and passkey challenges need cookies ([how-to](../../how-to/configure-better-auth.md#client-features))
|
|
118
|
-
- `BetterAuthClient` — The client type without `features` (also `ReturnType<typeof createBetterAuthClient>`). `BetterAuthClientWithFeatures<Features>` — the client type with them. `BetterAuthClientFor<Features>` — which of the two `createBetterAuthClient` returns. `BetterAuthHandoffClient` — what the OAuth handoff helpers accept: any client from `createBetterAuthClient`
|
|
119
|
-
- `BetterAuthOAuthProvider` — `"google" | "github" | "apple"` or any generic OAuth provider id, such as `"mock-oidc"`
|
|
120
|
-
- `getBetterAuthBearerToken({storagePrefix?, bearerStorage?})` — Reads the token bearer mode stored, or `null`
|
|
121
|
-
- `signInSocialWithHandoff({client, provider, callbackURL?, errorCallbackURL?})` — Starts a social or generic OAuth sign-in for a web bearer client: stores a random nonce in `sessionStorage` and navigates the page to the server's `{basePath}/oauth-handoff/start` with `handoff=<nonce>` on the callback URLs. Throws on native and in cookie mode ([how-to](../../how-to/configure-better-auth.md#social-sign-in-in-bearer-mode))
|
|
122
|
-
- `completeBetterAuthOAuthHandoff({client}): Promise<boolean>` — On the return page, removes `#ott` and `handoff` from the URL first, then exchanges the token at `{basePath}/one-time-token/verify` only when `handoff` matches the stored nonce. Resolves `true` only when a bearer token was stored; never rejects. Await it at startup before session reads and Sentry
|
|
123
|
-
- `linkSocialWithHandoff({client, provider, callbackURL, errorCallbackURL?}): Promise<{error}>` — Links a provider to the signed-in user. On web in bearer mode it posts the intent to `{basePath}/oauth-handoff/link-intent` with the bearer, then submits the returned single-use token in a hidden POST form to `startURL`, which redirects to the provider; the page comes back to `callbackURL` with no `#ott`. Both URLs may be any page on a web origin (`callbackPaths` do not apply); `errorCallbackURL` defaults to `callbackURL`. In cookie mode and on native it calls `client.linkSocial`. Resolves `{error: null}` once the browser is on its way, or `{error: {status, code?, message?}}` (such as 403 `LINKING_DISABLED`) without submitting anything. `BetterAuthLinkClient` is its client type ([how-to](../../how-to/configure-better-auth.md#linking-a-provider-in-bearer-mode))
|
|
124
|
-
- `useAuthMethods(authClient, {skip?}): {methods, isLoading, error, refetch}` — Loads the server's enabled sign-in methods (`BetterAuthMethods`: `providers`, `emailPassword`, `signUp`, `accountLinking`, and feature flags) once per client from `{basePath}/terreno/methods` through the client's `$fetch`, so bearer and cookie modes both work. `methods` is `undefined` while loading and after an error; render no provider buttons until it arrives. `refetch()` requests them again (for Try again); `skip: true` makes no request ([response](../api.md#better-auth-authentication))
|
|
125
|
-
- `createRememberedAccountsStore({rememberIdentity, storageKey?, max?, storage?})` — On-device store of accounts that signed in here, for the login screen. Returns `{record(entry), forget(userId), clear(), list(), lastUsedMethod(), isHydrated(), ready, getSnapshot(), subscribe(listener), dispose()}`. Each change re-reads storage first, and on web the store follows `storage` events for its key until `dispose()`, so a stale tab cannot bring back a forgotten account. A failed change rejects to its caller without blocking later changes. Persists `{userId, method, lastUsedAt}` (newest first, at most `max`, default 5, and at most 2048 bytes) under `storageKey` (default `terreno_remembered_accounts`) in SecureStore on native (`WHEN_UNLOCKED_THIS_DEVICE_ONLY` on iOS) or localStorage on web. `rememberIdentity` is required: pass `false` to keep only the method. `name` (in clear), a masked `email`, and an `https://` `image` are kept only with `rememberIdentity: true`. Unreadable or oversized stored data resets to an empty list ([how-to](../../how-to/configure-better-auth.md#4-remember-accounts-on-this-device))
|
|
126
|
-
- `useRememberedAccounts(store): {accounts, lastUsedMethod, isHydrated, forget, clear}` — Subscribes a component to the store. `isHydrated` is `false` until native storage has loaded
|
|
127
|
-
- `toLoginAccountOptions(accounts)` — Maps `RememberedAccount[]` to `@terreno/ui` `LoginAccountOption[]` (`id` is the `userId`) for `LoginScreen`'s `rememberedAccounts`
|
|
128
|
-
- `maskEmail(email)` — `josh@mail.flourish.health` → `j***@m***.health`: first character of the local part and domain, subdomains hidden, top-level domain kept; `undefined` for non-emails
|
|
129
|
-
- `getBetterAuthClientWebAuth(client): {webAuth, bearerToken}` — Whether a client from `createBetterAuthClient` is in web bearer mode, and the token it holds; `{webAuth: "cookie", bearerToken: null}` on native, in cookie mode, and for other clients. No request
|
|
130
|
-
- `useLoginController({client, rememberedAccounts, onSignedIn, onTwoFactorRequired?, rememberIdentity?, callbackURL?, errorCallbackURL?, error?, onSignUpPress?, onForgotPasswordPress?, providerLabels?, passkeyAutofill?, skipWhenSignedIn?, webAuth?, getBearerToken?, initialEmail?})` — Drives a login screen from a Better Auth client. Returns `{step, methods, isEmailPasswordEnabled, screenProps, twoFactor, signInEmail, signInSocial, signInPasskey, continueAs, forget}`: `step` is `"checking"`, `"login"`, `"twoFactor"` or `"signedIn"`; `isEmailPasswordEnabled` is `false` when the server has email and password off (render `LoginScreen` with `fields={[]}`); `screenProps` spreads into `@terreno/ui` `LoginScreen`; `twoFactor` feeds `TwoFactorChallengeScreen`. A live session calls `onSignedIn` without showing the screen (in bearer mode it first finishes a pending OAuth handoff; with no stored token it asks the server nothing). If `onSignedIn` throws, `step` returns to `"login"` with an error. The passkey button and autofill are web-only. Device sessions (`multiSession.listDeviceSessions`, never in bearer mode) become session cards and hide remembered entries for the same user. Social sign-in uses `signInSocialWithHandoff` in bearer mode and `signIn.social` otherwise. Every finished sign-in is recorded in the store. Server refusals become notices through `getLoginFeedback` ([how-to](../../how-to/configure-better-auth.md#5-drop-in-login-screen))
|
|
131
|
-
- `TerrenoLoginScreen` — `LoginScreen` connected through `useLoginController`; takes its options plus `fields?` (default `DEFAULT_LOGIN_FIELDS`), `title?`, `logo?`, `trustDeviceDays?`, `initialTrustDevice?` (pass `false` to start "Trust this device" unchecked), `onStepChange?` (called with each new step: `checking`, `login`, `twoFactor`, `signedIn`), `testID?` (default `login-screen`; spinner `${testID}-checking`). Renders the inline two-factor challenge unless `onTwoFactorRequired` is set
|
|
132
|
-
- `useAccountSecurity({client, callbackURL, methods?, errorCallbackURL?, linkError?, providerLabels?, passkeySupported?, minPasswordLength?})` — Drives an account security screen from a Better Auth client. Returns `{screenProps, methods, user, refetch}`; `screenProps` (`linkedAccounts`, `password`, `passkeys`, `twoFactor`, `sessions`, `isLoading`, `error`, `onRetry`) spreads into `@terreno/ui` `AccountSecurityScreen`. A card's props are `undefined` when the server's methods (or the client's features) leave it off; passkeys are web-only. Loads `listAccounts`, `passkey.listUserPasskeys`, `getSession({query: {disableCookieCache: true}})` and `listSessions`; each Better Auth `{error}` becomes a thrown `Error` for the card, and after each change only the affected section reloads. Never calls `unlinkAccount` for the only account, or `deletePasskey` for the only sign-in method (`LAST_SIGN_IN_METHOD_MESSAGE`). Links through `linkSocialWithHandoff`; "Set a password" posts `{basePath}/terreno/set-password`. `SESSION_NOT_FRESH` becomes `SIGN_IN_AGAIN_MESSAGE`; a change refused with `CREDENTIAL_ACCOUNT_NOT_FOUND` switches the password card to set mode (`NO_PASSWORD_YET_MESSAGE`). `getLinkErrorMessage(code)` maps a link's `?error=` to safe text ([how-to](../../how-to/configure-better-auth.md#6-drop-in-security-screen))
|
|
133
|
-
- `TerrenoAccountSecurityScreen` — `AccountSecurityScreen` connected through `useAccountSecurity`; takes its options plus `title?`, `description?`, `header?`, `children?`, `withPage?`, `maxWidth?`, `testID?`
|
|
134
|
-
- `recordRememberedSignIn({store, user, method, rememberIdentity?})` — Records a sign-in the controller did not finish, such as one completed on the app's own two-factor screen
|
|
135
|
-
- `getLoginFeedback({failure, mode?, originalMethod?}): {notice?, error?, isSignUpClosed}` — Maps `{code?, message?, source: "response" | "url"}` to login screen feedback: `account_not_linked` / `OAUTH_LINK_ERROR` / "account not linked" to guidance for the linking mode, `VERIFY_EMAIL_TO_USE_ACCOUNT` to "verify your email", `signup_disabled` / `EMAIL_PASSWORD_SIGN_UP_DISABLED` to "ask your administrator" (and `isSignUpClosed`), `LINKING_DISABLED`, `provider_not_found`; a response's own message otherwise, and never text from a URL
|
|
136
|
-
- `generateBetterAuthSlice({authClient, apisToReset?, syncInterval?, signOutTimeoutMs?})` — Returns `{reducer, middleware, actions, slice, syncSession, authClient}`. Pass the app's RTK Query APIs (for example `apisToReset: [terrenoApi]`) so their caches are purged (`util.resetApiState()`) when a signed-in session logs out (`betterAuth/logout` purges once the session clears, so a sign-out that fails keeps both; `auth/logout` purges at once and again when the session clears), when `syncSession` finds the session gone, and when the session changes to another user. Without it, the next account on the same client can be served the previous account's cached responses. `actions.logout()` keeps the session in the state with `isSigningOut: true` (`isLoading` unchanged) while the middleware calls `authClient.signOut()` (`signOutTimeoutMs`, default 10000), then dispatches `clearSession`. A sign-out that fails or times out asks `getSession`: a session that is still live, or a cookie or native client the server cannot be asked about, stays signed in with `error` set to `SIGN_OUT_FAILED_MESSAGE` ("Couldn't sign out. Check your connection and try again."); a web bearer client, which drops its token before the request leaves, ends signed out. The global `auth/logout` action (a refused session) always ends signed out. Either sign-out leaves alone a session for another user that signed in while it was in flight; a sign-out that started signed out asks `getSession` first, and clears a session that landed meanwhile unless the server still has it
|
|
137
|
-
- `selectBetterAuthIsAuthenticated`, `selectBetterAuthUserId`, `selectBetterAuthUser`, `selectBetterAuthIsLoading`, `selectBetterAuthIsSigningOut`, `selectBetterAuthError` — Selectors over `state.betterAuth`
|
|
138
|
-
- `BetterAuthUser`, `BetterAuthState` — TypeScript types for session/user data
|
|
139
|
-
|
|
140
|
-
**Session State (`state.betterAuth`):**
|
|
141
|
-
``````typescript
|
|
142
|
-
{
|
|
143
|
-
isAuthenticated: boolean;
|
|
144
|
-
userId: string | null;
|
|
145
|
-
user: BetterAuthUser | null;
|
|
146
|
-
isLoading: boolean;
|
|
147
|
-
isSigningOut: boolean;
|
|
148
|
-
error: string | null;
|
|
149
|
-
lastSyncTimestamp: number | null;
|
|
150
|
-
}
|
|
151
|
-
``````
|
|
152
|
-
|
|
153
|
-
**Learn more:** [Configure Better Auth](../../how-to/configure-better-auth.md)
|
|
154
|
-
|
|
155
|
-
### Token source
|
|
156
|
-
|
|
157
|
-
rtk reads the request token from one registered **token source**, typed as syncdb's
|
|
158
|
-
`AuthProvider` (`getToken`, `getUserId`, `onAuthChange`, optional `refresh`). A Better Auth
|
|
159
|
-
app registers the same adapter it builds for syncdb, once at startup:
|
|
160
|
-
|
|
161
|
-
``````typescript
|
|
162
|
-
import {setTokenSource} from "@terreno/rtk";
|
|
163
|
-
import {betterAuthAdapter, bridgeBetterAuthReactClient} from "@terreno/syncdb";
|
|
164
|
-
import {authClient} from "./betterAuth";
|
|
165
|
-
|
|
166
|
-
export const authProvider = betterAuthAdapter(bridgeBetterAuthReactClient(authClient));
|
|
167
|
-
setTokenSource(authProvider); // before the first RTK Query request
|
|
168
|
-
``````
|
|
169
|
-
|
|
170
|
-
With `webAuth: "bearer"`, also pass the adapter `getBearerToken` so it reads the stored token
|
|
171
|
-
without a `get-session` call ([syncdb adapter](../syncdb.md#createsyncdb-configuration)).
|
|
172
|
-
|
|
173
|
-
**Exports:**
|
|
174
|
-
- `setTokenSource(source | undefined)` — Registers the source. `undefined` restores the default.
|
|
175
|
-
- `getTokenSource()` — The registered source, or `undefined` when none is registered.
|
|
176
|
-
- `jwtTokenSource` — The legacy JWT source: `getToken` is `getAuthToken`, `refresh` runs
|
|
177
|
-
`refreshAuthToken` and resolves `true` on success, `getUserId` reads the stored JWT's `id`
|
|
178
|
-
(or `sub`) claim, and `onAuthChange` fires on JWT login, logout, and token refresh.
|
|
179
|
-
- `isJwtTokenSource(source)` — `true` for `undefined` or `jwtTokenSource`.
|
|
180
|
-
|
|
181
|
-
**Base query behavior (`emptySplitApi` / `staggeredBaseQuery`):**
|
|
182
|
-
- **No source registered, or `jwtTokenSource`:** the JWT path below, unchanged. JWT apps need
|
|
183
|
-
no code change.
|
|
184
|
-
- **Any other source:** sends `authorization: Bearer <token>` from `getToken()` on every
|
|
185
|
-
request, or no `authorization` header when the token is null. It never logs out because a
|
|
186
|
-
token is missing. On a 401 it calls `refresh()` once (shared by concurrent requests), re-reads
|
|
187
|
-
the token, and retries once; only a second 401 dispatches `auth/logout`. The public endpoints
|
|
188
|
-
`emailLogin`, `emailSignUp`, `getVersionCheck`, `getZoomSignature`, and `googleLogin` skip
|
|
189
|
-
the refresh and retry.
|
|
190
|
-
|
|
191
|
-
The registry is module-global, like `baseUrl`: one app, one source. Apps that forked the base
|
|
192
|
-
query to read the Better Auth session can point their SDK back at `emptySplitApi` once they
|
|
193
|
-
register the source.
|
|
194
|
-
|
|
195
|
-
**Other consumers.** The socket, chat, MCP, and offline APIs read the same source. Each takes a
|
|
196
|
-
`tokenSource` option that overrides it for that one consumer. Without the option they use the
|
|
197
|
-
registered source, else `jwtTokenSource`. Pass `useSocketConnection` a stable object, not a new
|
|
198
|
-
one per render, or it re-subscribes to `onAuthChange`.
|
|
199
|
-
|
|
200
|
-
| API | Option | How it uses the source |
|
|
201
|
-
| --- | --- | --- |
|
|
202
|
-
| `useSocketConnection` | `tokenSource`, used when `getAuthToken` is omitted | Reads `getToken()` on every connect and reconnect, refreshes once per auth failure, and reconnects when `onAuthChange` brings a new token. See [useSocketConnection](#usesocketconnection). |
|
|
203
|
-
| `useTerrenoChat` | `tokenSource` | Reads `getToken()` per chat request. Sends no `Authorization` header when it returns null. |
|
|
204
|
-
| `useMCPTools` | `tokenSource` | Reads `getToken()` before every MCP request, so a refreshed token needs no refetch. A fetch that starts with no token connects without auth. |
|
|
205
|
-
| `createOfflineMiddleware` | `tokenSource` | With a non-JWT source, tags queued mutations with `getUserId()` and replays them with `getToken()`. See [Offline queue](#offline-queue). |
|
|
206
|
-
|
|
207
|
-
#### Offline queue
|
|
208
|
-
|
|
209
|
-
`createOfflineMiddleware({endpoints, api, tokenSource?})` queues the listed mutations while
|
|
210
|
-
offline and replays them on reconnect.
|
|
211
|
-
|
|
212
|
-
- **JWT source:** the queue reads `state.auth.userId` and the stored JWT, as before.
|
|
213
|
-
- **Any other source** (for example syncdb's `betterAuthAdapter`): each queued mutation carries
|
|
214
|
-
`userId` from `getUserId()`. Replay runs only while that same user is signed in. It sends
|
|
215
|
-
`authorization: Bearer <token>` from `getToken()`, or no header when the token is null.
|
|
216
|
-
- **Last user id:** `state.offline.lastUserId` holds the last id the source confirmed. It
|
|
217
|
-
refreshes on the first action, on rehydrate, on `onAuthChange`, and on reconnect, so a failed
|
|
218
|
-
session read while offline does not untag new mutations.
|
|
219
|
-
- **Persist the `offline` slice** with redux-persist (do not blacklist it). Otherwise mutations
|
|
220
|
-
made after an app launches offline carry no user and are dropped on reconnect.
|
|
221
|
-
- **Unconfirmed ids:** a rehydrated id is unconfirmed until a read in this process returns a
|
|
222
|
-
user. Mutations tagged with it carry `userIdConfirmed: false`. A later read keeps them only
|
|
223
|
-
when it names the same user and no `onAuthChange` or source swap happened since. Any auth
|
|
224
|
-
change drops them, even for the same user.
|
|
225
|
-
- **Cost:** syncdb's `betterAuthAdapter` fires `onAuthChange` on the session's first successful
|
|
226
|
-
load. After an offline launch that happens when the network returns, so it drops
|
|
227
|
-
offline-launch writes the reconnect read has not confirmed yet.
|
|
228
|
-
- **Residual risk:** the signed-in user changes while no running instance of the middleware sees
|
|
229
|
-
it (for example in another browser tab that shares the persisted slice), between an
|
|
230
|
-
unconfirmed write and the next launch's first read. Then that write can replay as a user who
|
|
231
|
-
did not make it.
|
|
232
|
-
- **Sign-out:** `auth/logout`, `betterAuth/logout` (on a non-JWT source), or the source
|
|
233
|
-
reporting no user after `onAuthChange` clears the queue, conflicts, and `lastUserId`, and
|
|
234
|
-
discards in-flight enqueues and replays.
|
|
235
|
-
- **No user on reconnect:** when the source has no user, or the user changes during a replay,
|
|
236
|
-
the rest of the queue stays for a later reconnect.
|
|
237
|
-
- **Different user on reconnect:** a replay drops each mutation queued by a user other than the
|
|
238
|
-
one signed in.
|
|
239
|
-
|
|
240
|
-
Offline-slice exports the middleware uses for the user tag (exported from the package root):
|
|
241
|
-
|
|
242
|
-
- `setLastUserId(userId | undefined)` — Action that sets `state.offline.lastUserId`.
|
|
243
|
-
- `resolveUnconfirmedMutations(userId)` — Action that keeps unconfirmed mutations tagged with
|
|
244
|
-
`userId`, marks them confirmed, and drops the other unconfirmed ones.
|
|
245
|
-
- `dropUnconfirmedMutations()` — Action that drops every mutation whose user id is unconfirmed.
|
|
246
|
-
- `selectLastUserId(state)` — The last user id the source confirmed, or `undefined` when unknown
|
|
247
|
-
or signed out.
|
|
248
|
-
|
|
249
|
-
## WebSocket Integration
|
|
250
|
-
|
|
251
|
-
> **Deprecated (removed in Terreno 58):** `realtimeList`, `realtimeDocument`, `setRealtimeSocket`, and `getRealtimeSocket` patch RTK Query caches from `sync` events. Use [`@terreno/syncdb`](../syncdb.md). `useSocketConnection` remains for feature flags and other sockets.
|
|
252
|
-
|
|
253
|
-
A `realtimeList` query subscription (`subscribe:query`) may filter only on fields in the router's
|
|
254
|
-
`queryFields`, the same as the REST list; the server ignores a subscription that names any other
|
|
255
|
-
field. On RBAC routers with field views, each subscriber's create, update, or delete event is
|
|
256
|
-
decided against the document as their field view masks it, so a hidden field never changes which
|
|
257
|
-
event they receive.
|
|
258
|
-
|
|
259
|
-
### useSocketConnection
|
|
260
|
-
|
|
261
|
-
React hook for managing Socket.io connections with automatic reconnection, token refresh, and user feedback.
|
|
262
|
-
|
|
263
|
-
``````typescript
|
|
264
|
-
import {useSocketConnection} from "@terreno/rtk";
|
|
265
|
-
|
|
266
|
-
// Reads the token source registered with setTokenSource (the JWT in storage by default).
|
|
267
|
-
const {socket, isSocketConnected} = useSocketConnection({
|
|
268
|
-
baseUrl: "wss://api.example.com",
|
|
269
|
-
shouldConnect: !!userId,
|
|
270
|
-
onConnect: () => console.info("WebSocket connected"),
|
|
271
|
-
onDisconnect: () => console.warn("WebSocket disconnected"),
|
|
272
|
-
onConnectError: (error) => console.error("Connection error:", error),
|
|
273
|
-
captureEvent: (eventName, data) => analytics.track(eventName, data),
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
// Use socket for real-time events
|
|
277
|
-
useEffect(() => {
|
|
278
|
-
if (!socket) return;
|
|
279
|
-
|
|
280
|
-
socket.on("notification", (data) => {
|
|
281
|
-
console.info("Received notification:", data);
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
return () => {
|
|
285
|
-
socket.off("notification");
|
|
286
|
-
};
|
|
287
|
-
}, [socket]);
|
|
288
|
-
``````
|
|
289
|
-
|
|
290
|
-
**Options:**
|
|
291
|
-
- `baseUrl` (string, required) — WebSocket server URL
|
|
292
|
-
- `shouldConnect` (boolean, required) — Whether to connect (typically `!!userId`)
|
|
293
|
-
- `tokenSource` (`AuthProvider`) — Source for the handshake token. Defaults to the
|
|
294
|
-
[registered source](#token-source), else `jwtTokenSource`. Used only when `getAuthToken` is
|
|
295
|
-
omitted.
|
|
296
|
-
- `getAuthToken` (function) — Legacy getter. When passed, the hook reads the token once per
|
|
297
|
-
connect, sends it as a static `auth` payload, and ignores `tokenSource`.
|
|
298
|
-
- `onConnect` (function) — Callback on successful connection
|
|
299
|
-
- `onDisconnect` (function) — Callback on disconnection
|
|
300
|
-
- `onConnectError` (function) — Callback on connection error
|
|
301
|
-
- `onReconnectFailed` (function) — Callback after all reconnection attempts fail
|
|
302
|
-
- `captureEvent` (function) — Analytics event tracking (optional)
|
|
303
|
-
- `transports` (string[]) — Socket.IO transports in order. Default `["polling", "websocket"]`. Pass `["websocket"]` for hosts without session affinity, such as Vercel Functions ([details](../../how-to/vercel-function-websockets.md))
|
|
304
|
-
|
|
305
|
-
**Returns:**
|
|
306
|
-
- `socket` (Socket | null) — Socket.io client instance
|
|
307
|
-
- `isSocketConnected` (object) — Connection state with `{isConnected: boolean, lastDisconnectedAt: string | null}`
|
|
308
|
-
|
|
309
|
-
**Features:**
|
|
310
|
-
- **Automatic reconnection:** 5 attempts with exponential backoff (1-5 seconds)
|
|
311
|
-
- **Bearer token authentication:** Sends `Bearer <token>` in `socket.auth`
|
|
312
|
-
- **Token refresh integration:** Reconnects automatically when tokens are refreshed
|
|
313
|
-
- **User feedback:** Toast notifications for disconnections (after 9+ seconds) and token errors
|
|
314
|
-
- **Connection monitoring:** Periodic checks with automatic reconnection attempts
|
|
315
|
-
- **SSR-safe:** Checks `typeof window` before initialization
|
|
316
|
-
|
|
317
|
-
**Toast Behavior:**
|
|
318
|
-
- **Disconnection:** Shows "You have been disconnected. Attempting to reconnect..." after 9 seconds
|
|
319
|
-
- **Reconnection:** Shows "You have been reconnected" (suppressed if reconnect within 10 seconds)
|
|
320
|
-
- **Token error:** Shows "Error refreshing token. Please log out and log back in..." with persistent error state
|
|
321
|
-
|
|
322
|
-
**Token source behavior (no `getAuthToken`):**
|
|
323
|
-
- The handshake reads `getToken()` on every connect and reconnect, including socket.io's own
|
|
324
|
-
reconnects.
|
|
325
|
-
- On an auth `connect_error` (the server's `UnauthorizedError`), the hook calls `refresh()`
|
|
326
|
-
once and reconnects. A second auth failure before a successful connect stops reconnecting and
|
|
327
|
-
calls `onConnectError`. If the source has no `refresh`, or it fails, the hook stops at once.
|
|
328
|
-
A successful connect resets this.
|
|
329
|
-
- While `shouldConnect` is true, an `onAuthChange` with a different token drops a live
|
|
330
|
-
connection and reconnects with the new token. With no token it disconnects. The same token
|
|
331
|
-
keeps the connection up.
|
|
332
|
-
|
|
333
|
-
**JWT token management** (`getAuthToken` passed, or the JWT source):
|
|
334
|
-
- Checks token expiration on disconnect and connection errors
|
|
335
|
-
- Automatically refreshes tokens if expiring within 60 seconds
|
|
336
|
-
- Attempts reconnection after successful token refresh
|
|
337
|
-
- Tracks refresh events via Redux state (`lastTokenRefreshTimestamp`)
|
|
338
|
-
|
|
339
|
-
## Cache Management
|
|
340
|
-
|
|
341
|
-
### generateTags
|
|
342
|
-
|
|
343
|
-
Generates RTK Query cache tags for automatic invalidation.
|
|
344
|
-
|
|
345
|
-
``````typescript
|
|
346
|
-
import {providesIdTags, invalidatesIdTags} from "@terreno/rtk";
|
|
347
|
-
|
|
348
|
-
// In your API endpoints
|
|
349
|
-
getTodos: build.query({
|
|
350
|
-
query: () => "/todos",
|
|
351
|
-
providesTags: providesIdTags("todos"), // Tags individual items + collection
|
|
352
|
-
}),
|
|
353
|
-
|
|
354
|
-
createTodo: build.mutation({
|
|
355
|
-
query: (body) => ({url: "/todos", method: "POST", body}),
|
|
356
|
-
invalidatesTags: invalidatesIdTags("todos"), // Invalidates collection
|
|
357
|
-
}),
|
|
358
|
-
``````
|
|
359
|
-
|
|
360
|
-
**Functions:**
|
|
361
|
-
- `providesIdTags(tagName)` — Returns tags for list responses (individual items + collection tag)
|
|
362
|
-
- `invalidatesIdTags(tagName)` — Returns tags to invalidate on mutations
|
|
363
|
-
|
|
364
|
-
### populateId
|
|
365
|
-
|
|
366
|
-
Helper for normalizing MongoDB ObjectIds in RTK Query responses.
|
|
367
|
-
|
|
368
|
-
``````typescript
|
|
369
|
-
import {populateId} from "@terreno/rtk";
|
|
370
|
-
|
|
371
|
-
// Ensures _id is properly handled in Redux normalization
|
|
372
|
-
const normalizedData = populateId(responseData);
|
|
373
|
-
``````
|
|
374
|
-
|
|
375
|
-
### ListResponse
|
|
376
|
-
|
|
377
|
-
Standard interface for paginated list responses from @terreno/api:
|
|
378
|
-
|
|
379
|
-
``````typescript
|
|
380
|
-
interface ListResponse<T> {
|
|
381
|
-
data: T[];
|
|
382
|
-
page: number;
|
|
383
|
-
limit: number;
|
|
384
|
-
total: number;
|
|
385
|
-
more: boolean;
|
|
386
|
-
}
|
|
387
|
-
``````
|
|
388
|
-
|
|
389
|
-
## Token Management
|
|
390
|
-
|
|
391
|
-
### Token Expiration Helpers
|
|
392
|
-
|
|
393
|
-
``````typescript
|
|
394
|
-
import {
|
|
395
|
-
getAuthToken,
|
|
396
|
-
getTokenExpirationTimes,
|
|
397
|
-
getFriendlyExpirationInfo,
|
|
398
|
-
shouldShowStillThereModal,
|
|
399
|
-
} from "@terreno/rtk";
|
|
400
|
-
|
|
401
|
-
// Get current token from secure storage
|
|
402
|
-
const token = await getAuthToken();
|
|
403
|
-
|
|
404
|
-
// Check token expiration times
|
|
405
|
-
const {authRemainingSecs, refreshRemainingSecs} = await getTokenExpirationTimes();
|
|
406
|
-
console.info(`Auth token expires in ${authRemainingSecs} seconds`);
|
|
407
|
-
|
|
408
|
-
// Get human-readable expiration info
|
|
409
|
-
const info = await getFriendlyExpirationInfo();
|
|
410
|
-
console.info(info); // "Auth: 14m 23s, Refresh: 29d 23h"
|
|
411
|
-
|
|
412
|
-
// Check if should show "still there?" modal (refresh token <= 65 seconds)
|
|
413
|
-
if (shouldShowStillThereModal()) {
|
|
414
|
-
showModal("Your session is about to expire. Continue?");
|
|
415
|
-
}
|
|
416
|
-
``````
|
|
417
|
-
|
|
418
|
-
**Functions:**
|
|
419
|
-
- `getAuthToken()` — Returns JWT token from secure storage
|
|
420
|
-
- `getRefreshToken()` — Returns refresh token from secure storage
|
|
421
|
-
- `getTokenExpirationTimes()` — Returns `{authRemainingSecs, refreshRemainingSecs}`
|
|
422
|
-
- `getFriendlyExpirationInfo()` — Returns formatted expiration string
|
|
423
|
-
- `shouldShowStillThereModal()` — Returns true if refresh token expires in <= 65 seconds
|
|
424
|
-
|
|
425
|
-
## SDK Generation
|
|
426
|
-
|
|
427
|
-
### OpenAPI Code Generation
|
|
428
|
-
|
|
429
|
-
Generate typed RTK Query hooks from your @terreno/api backend's OpenAPI spec.
|
|
430
|
-
|
|
431
|
-
**Configuration (openapi-config.ts):**
|
|
432
|
-
``````typescript
|
|
433
|
-
import type {ConfigFile} from "@rtk-query/codegen-openapi";
|
|
434
|
-
|
|
435
|
-
const config: ConfigFile = {
|
|
436
|
-
apiFile: "@terreno/rtk",
|
|
437
|
-
apiImport: "emptySplitApi",
|
|
438
|
-
outputFile: "./store/openApiSdk.ts",
|
|
439
|
-
schemaFile: "http://localhost:4000/openapi.json",
|
|
440
|
-
hooks: true,
|
|
441
|
-
tag: true,
|
|
442
|
-
flattenArg: true,
|
|
443
|
-
argSuffix: "Args",
|
|
444
|
-
responseSuffix: "Res",
|
|
445
|
-
};
|
|
446
|
-
|
|
447
|
-
export default config;
|
|
448
|
-
``````
|
|
449
|
-
|
|
450
|
-
**Generate SDK:**
|
|
451
|
-
``````bash
|
|
452
|
-
# Backend must be running on the specified port
|
|
453
|
-
npx @rtk-query/codegen-openapi openapi-config.ts
|
|
454
|
-
``````
|
|
455
|
-
|
|
456
|
-
**Usage:**
|
|
457
|
-
``````typescript
|
|
458
|
-
import {useGetTodosQuery, usePostTodosMutation} from "@/store/openApiSdk";
|
|
459
|
-
|
|
460
|
-
const {data, isLoading, error, refetch} = useGetTodosQuery({completed: false});
|
|
461
|
-
const [createTodo, {isLoading: isCreating}] = usePostTodosMutation();
|
|
462
|
-
``````
|
|
463
|
-
|
|
464
|
-
**Critical Rules:**
|
|
465
|
-
- **Never modify `openApiSdk.ts` manually** — it is auto-generated
|
|
466
|
-
- **Never use `axios` or `fetch` directly** — always use generated hooks
|
|
467
|
-
- Regenerate SDK after any backend route changes
|
|
468
|
-
|
|
469
|
-
### emptyApi / emptySplitApi
|
|
470
|
-
|
|
471
|
-
Base RTK Query API with authentication, retry logic, and automatic token refresh.
|
|
472
|
-
|
|
473
|
-
**Features:**
|
|
474
|
-
- **Axios with retry:** 3 retries with exponential backoff for queries
|
|
475
|
-
- **Token refresh:** Automatically refreshes tokens when < 2 minutes from expiry
|
|
476
|
-
- **Mutex locking:** Prevents simultaneous token refreshes across concurrent requests
|
|
477
|
-
- **401 handling:** Auto-refreshes token on 401 responses and retries the request
|
|
478
|
-
- **Mutation safety:** Mutations don't retry on non-401 errors (prevents duplicates)
|
|
479
|
-
- **Query serialization:** Uses `qs.stringify()` for complex queries (`$in`, `$lt`, `$gte`)
|
|
480
|
-
|
|
481
|
-
**Automatic Headers:**
|
|
482
|
-
- `authorization: Bearer <token>` (from the registered [token source](#token-source); the JWT in storage by default)
|
|
483
|
-
- `App-Version` (from Expo config)
|
|
484
|
-
- `App-Platform` ("web" or "mobile")
|
|
485
|
-
|
|
486
|
-
**Response Handling:**
|
|
487
|
-
- 204 responses return `null`
|
|
488
|
-
- List endpoints return full response: `{data, more, page, limit, total}`
|
|
489
|
-
- CRUD endpoints extract and return `result.data`
|
|
490
|
-
|
|
491
|
-
**Base URL Resolution (priority order):**
|
|
492
|
-
1. `Constants.expoConfig?.extra?.BASE_URL` (production/staging)
|
|
493
|
-
2. `process.env.EXPO_PUBLIC_API_URL` (dev web)
|
|
494
|
-
3. `Constants.expoConfig?.hostUri` + the dev API port (dev simulator/device)
|
|
495
|
-
4. `http://localhost:<dev API port>` (fallback)
|
|
496
|
-
|
|
497
|
-
The dev API port defaults to `4000` and is overridable per app via `EXPO_PUBLIC_DEV_API_PORT`
|
|
498
|
-
or `expoConfig.extra.DEV_API_PORT` (for example `3000` or `9000`).
|
|
499
|
-
|
|
500
|
-
## Debugging
|
|
501
|
-
|
|
502
|
-
### Debug Logging
|
|
503
|
-
|
|
504
|
-
``````typescript
|
|
505
|
-
import {logAuth, logSocket, AUTH_DEBUG, WEBSOCKETS_DEBUG} from "@terreno/rtk";
|
|
506
|
-
|
|
507
|
-
// Check if debug logging is enabled (via expoConfig.extra)
|
|
508
|
-
console.info("Auth debug:", AUTH_DEBUG);
|
|
509
|
-
console.info("WebSocket debug:", WEBSOCKETS_DEBUG);
|
|
510
|
-
|
|
511
|
-
// Log auth events (only logs if AUTH_DEBUG is true)
|
|
512
|
-
logAuth("Token refreshed", {userId, timestamp: Date.now()});
|
|
513
|
-
|
|
514
|
-
// Log socket events (only logs if WEBSOCKETS_DEBUG is true)
|
|
515
|
-
logSocket("Connected", {socketId: socket.id});
|
|
516
|
-
``````
|
|
517
|
-
|
|
518
|
-
**Enable Debug Logging:**
|
|
519
|
-
|
|
520
|
-
Add to your `app.json` or `app.config.ts`:
|
|
521
|
-
``````json
|
|
522
|
-
{
|
|
523
|
-
"expo": {
|
|
524
|
-
"extra": {
|
|
525
|
-
"AUTH_DEBUG": true,
|
|
526
|
-
"WEBSOCKETS_DEBUG": true
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
``````
|
|
531
|
-
|
|
532
|
-
## Platform Detection
|
|
533
|
-
|
|
534
|
-
``````typescript
|
|
535
|
-
import {IsWeb} from "@terreno/rtk";
|
|
536
|
-
|
|
537
|
-
if (IsWeb) {
|
|
538
|
-
console.info("Running on web platform");
|
|
539
|
-
} else {
|
|
540
|
-
console.info("Running on native (iOS/Android)");
|
|
541
|
-
}
|
|
542
|
-
``````
|
|
543
|
-
|
|
544
|
-
**Platform-specific behavior:**
|
|
545
|
-
- **Token storage:** SecureStore (native) vs AsyncStorage (web)
|
|
546
|
-
- **WebSocket:** Uses native WebSocket API on both platforms
|
|
547
|
-
- **SSR safety:** Web code checks `typeof window !== "undefined"` before browser APIs
|
|
548
|
-
|
|
549
|
-
## Environment Variables
|
|
550
|
-
|
|
551
|
-
Configuration for frontend apps using @terreno/rtk:
|
|
552
|
-
|
|
553
|
-
| Variable | Required | Default | Description |
|
|
554
|
-
|----------|----------|---------|-------------|
|
|
555
|
-
| `EXPO_PUBLIC_API_URL` | No | Auto-detected | Backend API base URL (production/staging deployments) |
|
|
556
|
-
| `EXPO_PUBLIC_DEV_API_PORT` | No | 4000 | Local dev API port for host/localhost resolution (also settable via `extra.DEV_API_PORT`) |
|
|
557
|
-
| `NODE_ENV` | No | `development` | Environment: `development`, `production`, `test` |
|
|
558
|
-
|
|
559
|
-
**Base URL resolution priority:**
|
|
560
|
-
|
|
561
|
-
1. `Constants.expoConfig?.extra?.BASE_URL` (from `app.json` `extra` field)
|
|
562
|
-
2. `process.env.EXPO_PUBLIC_API_URL` (for web development)
|
|
563
|
-
3. `Constants.expoConfig?.hostUri` + the dev API port (for Expo dev server - simulator/device)
|
|
564
|
-
4. `http://localhost:<dev API port>` (fallback)
|
|
565
|
-
|
|
566
|
-
The dev API port defaults to `4000` and is overridable per app via `EXPO_PUBLIC_DEV_API_PORT`
|
|
567
|
-
or `expoConfig.extra.DEV_API_PORT` (for example `3000` or `9000`).
|
|
568
|
-
|
|
569
|
-
**Example `app.json` configuration:**
|
|
570
|
-
|
|
571
|
-
``````json
|
|
572
|
-
{
|
|
573
|
-
"expo": {
|
|
574
|
-
"extra": {
|
|
575
|
-
"BASE_URL": "https://api.example.com",
|
|
576
|
-
"AUTH_DEBUG": true,
|
|
577
|
-
"WEBSOCKETS_DEBUG": false
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
``````
|
|
582
|
-
|
|
583
|
-
**Debug flags (via `app.json` `extra` field):**
|
|
584
|
-
|
|
585
|
-
- `AUTH_DEBUG` — Enable verbose auth logging (token refresh, login/logout)
|
|
586
|
-
- `WEBSOCKETS_DEBUG` — Enable WebSocket connection/disconnection logs
|
|
587
|
-
|
|
588
|
-
**Example `.env` for web development:**
|
|
589
|
-
|
|
590
|
-
``````env
|
|
591
|
-
EXPO_PUBLIC_API_URL=http://localhost:4000
|
|
592
|
-
``````
|
|
593
|
-
|
|
594
|
-
## Related Documentation
|
|
595
|
-
|
|
596
|
-
- [Authentication Architecture](../../explanation/authentication.md) — Deep-dive into JWT + Passport system
|
|
597
|
-
- [WebSocket Integration How-To](../../how-to/websocket-integration.md) — Real-time connection setup
|
|
598
|
-
- [Add GitHub OAuth](../../how-to/add-github-oauth.md) — Step-by-step OAuth setup guide
|
|
599
|
-
- [@terreno/api Reference](../api.md) — Backend API framework
|
|
600
|
-
- [@terreno/ui Reference](../ui.md) — React Native components
|
|
1
|
+
# @terreno/rtk (removed)
|
|
2
|
+
|
|
3
|
+
`@terreno/rtk` was removed in **Terreno 58.0.0**. It is not published for 58 or later, and no
|
|
4
|
+
Terreno package depends on Redux. To upgrade, follow
|
|
5
|
+
[How to upgrade from @terreno/rtk to @terreno/syncdb](../../how-to/migrate-rtk-to-syncdb.md),
|
|
6
|
+
which maps every former export to its replacement.
|
|
7
|
+
|
|
8
|
+
| Former `@terreno/rtk` area | Terreno 58 home |
|
|
9
|
+
|----------------------------|-----------------|
|
|
10
|
+
| RTK Query base API and `@rtk-query/codegen-openapi` SDK | `createApiClient` (`@terreno/syncdb/api`) and `terreno-syncdb-codegen --api-out` → `store/apiSdk.ts` |
|
|
11
|
+
| Collection CRUD hooks | `useQuery` / `useMutate` (`@terreno/syncdb/react`) |
|
|
12
|
+
| Better Auth client and Redux slice | `createBetterAuthClient`, `SessionProvider`, `useSession` (`@terreno/syncdb/auth`) |
|
|
13
|
+
| JWT `generateAuthSlice` | `jwtAuthAdapter`, `useJwtAuth` (`@terreno/syncdb/auth`) |
|
|
14
|
+
| `baseUrl`, `baseWebsocketsUrl`, base URL resolution | `@terreno/syncdb/config` |
|
|
15
|
+
| `useSocketConnection` | The syncdb socket: `useSocketEvent` (`@terreno/syncdb/react`) |
|
|
16
|
+
| `useFeatureFlags`, `useTerrenoFeatureFlags` | `@terreno/syncdb/flags` |
|
|
17
|
+
| `useUpgradeCheck`, permissions helpers, dev console logger, `useMCPTools` | `@terreno/syncdb/app` |
|
|
18
|
+
| `@terreno/rtk/buildNumber` | `@terreno/syncdb/buildNumber` |
|
|
19
|
+
| Offline middleware, `useOfflineStatus`, `useServerStatus` | The syncdb outbox and `useSyncStatus` (`@terreno/syncdb/react`) |
|
|
20
|
+
|
|
21
|
+
Apps that stay on Terreno 57.x keep using `@terreno/rtk` 57.x. Its reference is in the 57.x
|
|
22
|
+
docs snapshot.
|
|
23
|
+
|
|
24
|
+
See also: [`@terreno/syncdb` reference](../syncdb.md) and the
|
|
25
|
+
[58.0.0 upgrade note](https://github.com/TerrenoLabs/terreno/blob/master/mcp-server/src/docs/upgrades/58.0.0.md#terrenortk-and-redux-are-removed).
|