@oxyhq/auth 1.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.
Files changed (119) hide show
  1. package/README.md +56 -0
  2. package/dist/cjs/WebOxyProvider.js +287 -0
  3. package/dist/cjs/hooks/mutations/index.js +23 -0
  4. package/dist/cjs/hooks/mutations/mutationFactory.js +126 -0
  5. package/dist/cjs/hooks/mutations/useAccountMutations.js +275 -0
  6. package/dist/cjs/hooks/mutations/useServicesMutations.js +149 -0
  7. package/dist/cjs/hooks/queries/index.js +35 -0
  8. package/dist/cjs/hooks/queries/queryKeys.js +82 -0
  9. package/dist/cjs/hooks/queries/useAccountQueries.js +141 -0
  10. package/dist/cjs/hooks/queries/useSecurityQueries.js +45 -0
  11. package/dist/cjs/hooks/queries/useServicesQueries.js +113 -0
  12. package/dist/cjs/hooks/queryClient.js +110 -0
  13. package/dist/cjs/hooks/useAssets.js +225 -0
  14. package/dist/cjs/hooks/useFileDownloadUrl.js +91 -0
  15. package/dist/cjs/hooks/useFileFiltering.js +81 -0
  16. package/dist/cjs/hooks/useFollow.js +159 -0
  17. package/dist/cjs/hooks/useFollow.types.js +4 -0
  18. package/dist/cjs/hooks/useQueryClient.js +16 -0
  19. package/dist/cjs/hooks/useSessionSocket.js +215 -0
  20. package/dist/cjs/hooks/useWebSSO.js +146 -0
  21. package/dist/cjs/index.js +115 -0
  22. package/dist/cjs/stores/accountStore.js +226 -0
  23. package/dist/cjs/stores/assetStore.js +192 -0
  24. package/dist/cjs/stores/authStore.js +47 -0
  25. package/dist/cjs/stores/followStore.js +154 -0
  26. package/dist/cjs/utils/authHelpers.js +154 -0
  27. package/dist/cjs/utils/avatarUtils.js +77 -0
  28. package/dist/cjs/utils/errorHandlers.js +128 -0
  29. package/dist/cjs/utils/sessionHelpers.js +90 -0
  30. package/dist/cjs/utils/storageHelpers.js +147 -0
  31. package/dist/esm/WebOxyProvider.js +282 -0
  32. package/dist/esm/hooks/mutations/index.js +10 -0
  33. package/dist/esm/hooks/mutations/mutationFactory.js +122 -0
  34. package/dist/esm/hooks/mutations/useAccountMutations.js +267 -0
  35. package/dist/esm/hooks/mutations/useServicesMutations.js +141 -0
  36. package/dist/esm/hooks/queries/index.js +14 -0
  37. package/dist/esm/hooks/queries/queryKeys.js +76 -0
  38. package/dist/esm/hooks/queries/useAccountQueries.js +131 -0
  39. package/dist/esm/hooks/queries/useSecurityQueries.js +40 -0
  40. package/dist/esm/hooks/queries/useServicesQueries.js +105 -0
  41. package/dist/esm/hooks/queryClient.js +104 -0
  42. package/dist/esm/hooks/useAssets.js +220 -0
  43. package/dist/esm/hooks/useFileDownloadUrl.js +86 -0
  44. package/dist/esm/hooks/useFileFiltering.js +78 -0
  45. package/dist/esm/hooks/useFollow.js +154 -0
  46. package/dist/esm/hooks/useFollow.types.js +3 -0
  47. package/dist/esm/hooks/useQueryClient.js +12 -0
  48. package/dist/esm/hooks/useSessionSocket.js +209 -0
  49. package/dist/esm/hooks/useWebSSO.js +143 -0
  50. package/dist/esm/index.js +48 -0
  51. package/dist/esm/stores/accountStore.js +219 -0
  52. package/dist/esm/stores/assetStore.js +180 -0
  53. package/dist/esm/stores/authStore.js +44 -0
  54. package/dist/esm/stores/followStore.js +151 -0
  55. package/dist/esm/utils/authHelpers.js +145 -0
  56. package/dist/esm/utils/avatarUtils.js +72 -0
  57. package/dist/esm/utils/errorHandlers.js +121 -0
  58. package/dist/esm/utils/sessionHelpers.js +84 -0
  59. package/dist/esm/utils/storageHelpers.js +108 -0
  60. package/dist/types/WebOxyProvider.d.ts +97 -0
  61. package/dist/types/hooks/mutations/index.d.ts +8 -0
  62. package/dist/types/hooks/mutations/mutationFactory.d.ts +75 -0
  63. package/dist/types/hooks/mutations/useAccountMutations.d.ts +68 -0
  64. package/dist/types/hooks/mutations/useServicesMutations.d.ts +22 -0
  65. package/dist/types/hooks/queries/index.d.ts +10 -0
  66. package/dist/types/hooks/queries/queryKeys.d.ts +64 -0
  67. package/dist/types/hooks/queries/useAccountQueries.d.ts +42 -0
  68. package/dist/types/hooks/queries/useSecurityQueries.d.ts +14 -0
  69. package/dist/types/hooks/queries/useServicesQueries.d.ts +31 -0
  70. package/dist/types/hooks/queryClient.d.ts +18 -0
  71. package/dist/types/hooks/useAssets.d.ts +34 -0
  72. package/dist/types/hooks/useFileDownloadUrl.d.ts +18 -0
  73. package/dist/types/hooks/useFileFiltering.d.ts +28 -0
  74. package/dist/types/hooks/useFollow.d.ts +61 -0
  75. package/dist/types/hooks/useFollow.types.d.ts +32 -0
  76. package/dist/types/hooks/useQueryClient.d.ts +6 -0
  77. package/dist/types/hooks/useSessionSocket.d.ts +13 -0
  78. package/dist/types/hooks/useWebSSO.d.ts +57 -0
  79. package/dist/types/index.d.ts +46 -0
  80. package/dist/types/stores/accountStore.d.ts +33 -0
  81. package/dist/types/stores/assetStore.d.ts +53 -0
  82. package/dist/types/stores/authStore.d.ts +16 -0
  83. package/dist/types/stores/followStore.d.ts +24 -0
  84. package/dist/types/utils/authHelpers.d.ts +98 -0
  85. package/dist/types/utils/avatarUtils.d.ts +33 -0
  86. package/dist/types/utils/errorHandlers.d.ts +34 -0
  87. package/dist/types/utils/sessionHelpers.d.ts +63 -0
  88. package/dist/types/utils/storageHelpers.d.ts +27 -0
  89. package/package.json +71 -0
  90. package/src/WebOxyProvider.tsx +372 -0
  91. package/src/global.d.ts +1 -0
  92. package/src/hooks/mutations/index.ts +25 -0
  93. package/src/hooks/mutations/mutationFactory.ts +215 -0
  94. package/src/hooks/mutations/useAccountMutations.ts +344 -0
  95. package/src/hooks/mutations/useServicesMutations.ts +164 -0
  96. package/src/hooks/queries/index.ts +36 -0
  97. package/src/hooks/queries/queryKeys.ts +88 -0
  98. package/src/hooks/queries/useAccountQueries.ts +152 -0
  99. package/src/hooks/queries/useSecurityQueries.ts +64 -0
  100. package/src/hooks/queries/useServicesQueries.ts +126 -0
  101. package/src/hooks/queryClient.ts +112 -0
  102. package/src/hooks/useAssets.ts +291 -0
  103. package/src/hooks/useFileDownloadUrl.ts +118 -0
  104. package/src/hooks/useFileFiltering.ts +115 -0
  105. package/src/hooks/useFollow.ts +175 -0
  106. package/src/hooks/useFollow.types.ts +33 -0
  107. package/src/hooks/useQueryClient.ts +17 -0
  108. package/src/hooks/useSessionSocket.ts +233 -0
  109. package/src/hooks/useWebSSO.ts +187 -0
  110. package/src/index.ts +144 -0
  111. package/src/stores/accountStore.ts +296 -0
  112. package/src/stores/assetStore.ts +281 -0
  113. package/src/stores/authStore.ts +63 -0
  114. package/src/stores/followStore.ts +181 -0
  115. package/src/utils/authHelpers.ts +183 -0
  116. package/src/utils/avatarUtils.ts +103 -0
  117. package/src/utils/errorHandlers.ts +194 -0
  118. package/src/utils/sessionHelpers.ts +151 -0
  119. package/src/utils/storageHelpers.ts +130 -0
package/README.md ADDED
@@ -0,0 +1,56 @@
1
+ # @oxyhq/auth
2
+
3
+ OxyHQ Web Auth SDK. Headless React hooks for web applications. Zero React Native or Expo dependencies.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @oxyhq/auth
9
+ ```
10
+
11
+ ### Peer Dependencies
12
+
13
+ - `@oxyhq/core`
14
+ - `react`
15
+
16
+ ### Dependencies
17
+
18
+ - `@tanstack/react-query`
19
+ - `zustand`
20
+ - `socket.io-client`
21
+ - `sonner`
22
+
23
+ ## Contents
24
+
25
+ - **WebOxyProvider** — React context provider with auth state
26
+ - **useAuth** — hook for signIn, signOut, user, isAuthenticated
27
+ - **useWebOxy** — full context access including sessions, switchSession, clearSessionState
28
+ - **Query hooks** — useCurrentUser, useUserProfile, usePrivacySettings, useSecurityActivity, and more
29
+ - **Mutation hooks** — useUpdateProfile, useUploadAvatar, useSwitchSession, useLogoutSession, and more
30
+ - **Stores** — authStore, assetStore, accountStore, followStore (zustand)
31
+ - **Session management utilities**
32
+
33
+ ## Usage
34
+
35
+ ```tsx
36
+ import { WebOxyProvider, useAuth } from '@oxyhq/auth';
37
+ import type { User } from '@oxyhq/core';
38
+
39
+ function App() {
40
+ return (
41
+ <WebOxyProvider baseURL="https://api.oxy.so">
42
+ <YourApp />
43
+ </WebOxyProvider>
44
+ );
45
+ }
46
+
47
+ function YourApp() {
48
+ const { user, isAuthenticated, signIn, signOut } = useAuth();
49
+
50
+ if (!isAuthenticated) {
51
+ return <button onClick={() => signIn()}>Sign In</button>;
52
+ }
53
+
54
+ return <p>Welcome, {user?.name}</p>;
55
+ }
56
+ ```
@@ -0,0 +1,287 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WebOxyProvider = WebOxyProvider;
4
+ exports.useWebOxy = useWebOxy;
5
+ exports.useAuth = useAuth;
6
+ const jsx_runtime_1 = require("react/jsx-runtime");
7
+ /**
8
+ * @oxyhq/auth — Web Authentication Provider
9
+ *
10
+ * Clean implementation with ZERO React Native dependencies.
11
+ * Provides FedCM, popup, and redirect authentication methods.
12
+ * Uses centralized AuthManager for token and session management.
13
+ */
14
+ const react_1 = require("react");
15
+ const core_1 = require("@oxyhq/core");
16
+ const react_query_1 = require("@tanstack/react-query");
17
+ const queryClient_1 = require("./hooks/queryClient");
18
+ const WebOxyContext = (0, react_1.createContext)(null);
19
+ /**
20
+ * Web-only Oxy Provider
21
+ *
22
+ * Provides authentication context for pure web applications (React, Next.js, Vite).
23
+ * Supports FedCM, popup, and redirect authentication methods.
24
+ *
25
+ * @example
26
+ * ```tsx
27
+ * import { WebOxyProvider, useAuth } from '@oxyhq/auth';
28
+ *
29
+ * function App() {
30
+ * return (
31
+ * <WebOxyProvider baseURL="https://api.oxy.so">
32
+ * <YourApp />
33
+ * </WebOxyProvider>
34
+ * );
35
+ * }
36
+ * ```
37
+ */
38
+ function WebOxyProvider({ children, baseURL, authWebUrl, onAuthStateChange, onError, preferredAuthMethod = 'auto', skipAutoCheck = false, }) {
39
+ const [oxyServices] = (0, react_1.useState)(() => new core_1.OxyServices({ baseURL, authWebUrl }));
40
+ const [crossDomainAuth] = (0, react_1.useState)(() => new core_1.CrossDomainAuth(oxyServices));
41
+ const [authManager] = (0, react_1.useState)(() => (0, core_1.createAuthManager)(oxyServices, { autoRefresh: true }));
42
+ const [queryClient] = (0, react_1.useState)(() => (0, queryClient_1.createQueryClient)());
43
+ // Auth state
44
+ const [user, setUser] = (0, react_1.useState)(null);
45
+ const [isLoading, setIsLoading] = (0, react_1.useState)(!skipAutoCheck);
46
+ const [error, setError] = (0, react_1.useState)(null);
47
+ const [activeSessionId, setActiveSessionId] = (0, react_1.useState)(null);
48
+ const [sessions, setSessions] = (0, react_1.useState)([]);
49
+ const isAuthenticated = !!user;
50
+ const handleAuthSuccess = (0, react_1.useCallback)(async (session, method = 'credentials') => {
51
+ await authManager.handleAuthSuccess(session, method);
52
+ // Set active session
53
+ if (session.sessionId) {
54
+ setActiveSessionId(session.sessionId);
55
+ }
56
+ // Fetch full user profile
57
+ try {
58
+ const fullUser = await oxyServices.getCurrentUser();
59
+ if (fullUser) {
60
+ setUser(fullUser);
61
+ }
62
+ else {
63
+ setUser(session.user);
64
+ }
65
+ }
66
+ catch {
67
+ setUser(session.user);
68
+ }
69
+ setError(null);
70
+ setIsLoading(false);
71
+ }, [authManager, oxyServices]);
72
+ const handleAuthError = (0, react_1.useCallback)((err) => {
73
+ const errorMessage = err instanceof Error ? err.message : 'Authentication failed';
74
+ setError(errorMessage);
75
+ setIsLoading(false);
76
+ onError?.(err instanceof Error ? err : new Error(errorMessage));
77
+ }, [onError]);
78
+ // Initialize
79
+ (0, react_1.useEffect)(() => {
80
+ if (skipAutoCheck)
81
+ return;
82
+ let mounted = true;
83
+ const initAuth = async () => {
84
+ try {
85
+ const callbackSession = crossDomainAuth.handleRedirectCallback();
86
+ if (callbackSession && mounted) {
87
+ await handleAuthSuccess(callbackSession, 'redirect');
88
+ return;
89
+ }
90
+ const restoredUser = await authManager.initialize();
91
+ if (restoredUser && mounted) {
92
+ try {
93
+ const currentUser = await oxyServices.getCurrentUser();
94
+ if (mounted && currentUser) {
95
+ setUser(currentUser);
96
+ setIsLoading(false);
97
+ return;
98
+ }
99
+ }
100
+ catch {
101
+ await authManager.signOut();
102
+ }
103
+ }
104
+ try {
105
+ const session = await crossDomainAuth.silentSignIn();
106
+ if (mounted && session?.user) {
107
+ await handleAuthSuccess(session, 'fedcm');
108
+ return;
109
+ }
110
+ }
111
+ catch {
112
+ // Silent sign-in failed
113
+ }
114
+ if (mounted)
115
+ setIsLoading(false);
116
+ }
117
+ catch {
118
+ if (mounted)
119
+ setIsLoading(false);
120
+ }
121
+ };
122
+ initAuth();
123
+ return () => { mounted = false; };
124
+ }, [oxyServices, crossDomainAuth, authManager, skipAutoCheck, handleAuthSuccess]);
125
+ (0, react_1.useEffect)(() => {
126
+ onAuthStateChange?.(user);
127
+ }, [user, onAuthStateChange]);
128
+ const signIn = (0, react_1.useCallback)(async () => {
129
+ setError(null);
130
+ setIsLoading(true);
131
+ let selectedMethod = 'popup';
132
+ try {
133
+ const session = await crossDomainAuth.signIn({
134
+ method: preferredAuthMethod,
135
+ onMethodSelected: (method) => {
136
+ selectedMethod = method;
137
+ },
138
+ });
139
+ if (session) {
140
+ await handleAuthSuccess(session, selectedMethod);
141
+ }
142
+ else {
143
+ setIsLoading(false);
144
+ }
145
+ }
146
+ catch (err) {
147
+ handleAuthError(err);
148
+ }
149
+ }, [crossDomainAuth, preferredAuthMethod, handleAuthSuccess, handleAuthError]);
150
+ const signInWithFedCM = (0, react_1.useCallback)(async () => {
151
+ setError(null);
152
+ setIsLoading(true);
153
+ try {
154
+ const session = await crossDomainAuth.signInWithFedCM();
155
+ await handleAuthSuccess(session, 'fedcm');
156
+ }
157
+ catch (err) {
158
+ handleAuthError(err);
159
+ }
160
+ }, [crossDomainAuth, handleAuthSuccess, handleAuthError]);
161
+ const signInWithPopup = (0, react_1.useCallback)(async () => {
162
+ setError(null);
163
+ setIsLoading(true);
164
+ try {
165
+ const session = await crossDomainAuth.signInWithPopup();
166
+ await handleAuthSuccess(session, 'popup');
167
+ }
168
+ catch (err) {
169
+ handleAuthError(err);
170
+ }
171
+ }, [crossDomainAuth, handleAuthSuccess, handleAuthError]);
172
+ const signInWithRedirect = (0, react_1.useCallback)(() => {
173
+ setError(null);
174
+ crossDomainAuth.signInWithRedirect({
175
+ redirectUri: typeof window !== 'undefined' ? window.location.href : undefined,
176
+ });
177
+ }, [crossDomainAuth]);
178
+ const isFedCMSupported = (0, react_1.useCallback)(() => {
179
+ return crossDomainAuth.isFedCMSupported();
180
+ }, [crossDomainAuth]);
181
+ const signOut = (0, react_1.useCallback)(async () => {
182
+ setError(null);
183
+ try {
184
+ await authManager.signOut();
185
+ setUser(null);
186
+ setActiveSessionId(null);
187
+ setSessions([]);
188
+ }
189
+ catch (err) {
190
+ const errorMessage = err instanceof Error ? err.message : 'Sign out failed';
191
+ setError(errorMessage);
192
+ onError?.(err instanceof Error ? err : new Error(errorMessage));
193
+ }
194
+ }, [authManager, onError]);
195
+ const switchSession = (0, react_1.useCallback)(async (sessionId) => {
196
+ try {
197
+ const result = await oxyServices.getTokenBySession(sessionId);
198
+ if (result) {
199
+ setActiveSessionId(sessionId);
200
+ const currentUser = await oxyServices.getCurrentUser();
201
+ if (currentUser)
202
+ setUser(currentUser);
203
+ }
204
+ }
205
+ catch (err) {
206
+ handleAuthError(err);
207
+ }
208
+ }, [oxyServices, handleAuthError]);
209
+ const clearSessionState = (0, react_1.useCallback)(async () => {
210
+ await authManager.signOut();
211
+ setUser(null);
212
+ setActiveSessionId(null);
213
+ setSessions([]);
214
+ }, [authManager]);
215
+ (0, react_1.useEffect)(() => {
216
+ return () => { authManager.destroy(); };
217
+ }, [authManager]);
218
+ const contextValue = (0, react_1.useMemo)(() => ({
219
+ user,
220
+ isAuthenticated,
221
+ isLoading,
222
+ error,
223
+ activeSessionId,
224
+ sessions,
225
+ oxyServices,
226
+ crossDomainAuth,
227
+ authManager,
228
+ signIn,
229
+ signInWithFedCM,
230
+ signInWithPopup,
231
+ signInWithRedirect,
232
+ signOut,
233
+ isFedCMSupported,
234
+ switchSession,
235
+ clearSessionState,
236
+ }), [
237
+ user, isAuthenticated, isLoading, error, activeSessionId, sessions,
238
+ oxyServices, crossDomainAuth, authManager,
239
+ signIn, signInWithFedCM, signInWithPopup, signInWithRedirect,
240
+ signOut, isFedCMSupported, switchSession, clearSessionState,
241
+ ]);
242
+ return ((0, jsx_runtime_1.jsx)(react_query_1.QueryClientProvider, { client: queryClient, children: (0, jsx_runtime_1.jsx)(WebOxyContext.Provider, { value: contextValue, children: children }) }));
243
+ }
244
+ /**
245
+ * Hook to access the full Web Oxy context.
246
+ */
247
+ function useWebOxy() {
248
+ const context = (0, react_1.useContext)(WebOxyContext);
249
+ if (!context) {
250
+ throw new Error('useWebOxy must be used within WebOxyProvider');
251
+ }
252
+ return context;
253
+ }
254
+ /**
255
+ * Hook for authentication in web apps.
256
+ *
257
+ * @example
258
+ * ```tsx
259
+ * function LoginPage() {
260
+ * const { user, isAuthenticated, signIn, signOut } = useAuth();
261
+ * if (!isAuthenticated) return <button onClick={signIn}>Sign in</button>;
262
+ * return <button onClick={signOut}>Sign out</button>;
263
+ * }
264
+ * ```
265
+ */
266
+ function useAuth() {
267
+ const ctx = useWebOxy();
268
+ return {
269
+ user: ctx.user,
270
+ isAuthenticated: ctx.isAuthenticated,
271
+ isLoading: ctx.isLoading,
272
+ isReady: !ctx.isLoading,
273
+ error: ctx.error,
274
+ activeSessionId: ctx.activeSessionId,
275
+ sessions: ctx.sessions,
276
+ signIn: ctx.signIn,
277
+ signInWithFedCM: ctx.signInWithFedCM,
278
+ signInWithPopup: ctx.signInWithPopup,
279
+ signInWithRedirect: ctx.signInWithRedirect,
280
+ signOut: ctx.signOut,
281
+ isFedCMSupported: ctx.isFedCMSupported,
282
+ switchSession: ctx.switchSession,
283
+ oxyServices: ctx.oxyServices,
284
+ authManager: ctx.authManager,
285
+ };
286
+ }
287
+ exports.default = WebOxyProvider;
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ /**
3
+ * Mutation Hooks
4
+ *
5
+ * TanStack Query mutation hooks for updating Oxy services data.
6
+ * All mutations handle authentication, error handling, and query invalidation.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.useRemoveDevice = exports.useUpdateDeviceName = exports.useLogoutAll = exports.useLogoutSession = exports.useSwitchSession = exports.useUploadFile = exports.useUpdatePrivacySettings = exports.useUpdateAccountSettings = exports.useUploadAvatar = exports.useUpdateProfile = void 0;
10
+ // Account mutation hooks
11
+ var useAccountMutations_1 = require("./useAccountMutations");
12
+ Object.defineProperty(exports, "useUpdateProfile", { enumerable: true, get: function () { return useAccountMutations_1.useUpdateProfile; } });
13
+ Object.defineProperty(exports, "useUploadAvatar", { enumerable: true, get: function () { return useAccountMutations_1.useUploadAvatar; } });
14
+ Object.defineProperty(exports, "useUpdateAccountSettings", { enumerable: true, get: function () { return useAccountMutations_1.useUpdateAccountSettings; } });
15
+ Object.defineProperty(exports, "useUpdatePrivacySettings", { enumerable: true, get: function () { return useAccountMutations_1.useUpdatePrivacySettings; } });
16
+ Object.defineProperty(exports, "useUploadFile", { enumerable: true, get: function () { return useAccountMutations_1.useUploadFile; } });
17
+ // Service mutation hooks (sessions, devices)
18
+ var useServicesMutations_1 = require("./useServicesMutations");
19
+ Object.defineProperty(exports, "useSwitchSession", { enumerable: true, get: function () { return useServicesMutations_1.useSwitchSession; } });
20
+ Object.defineProperty(exports, "useLogoutSession", { enumerable: true, get: function () { return useServicesMutations_1.useLogoutSession; } });
21
+ Object.defineProperty(exports, "useLogoutAll", { enumerable: true, get: function () { return useServicesMutations_1.useLogoutAll; } });
22
+ Object.defineProperty(exports, "useUpdateDeviceName", { enumerable: true, get: function () { return useServicesMutations_1.useUpdateDeviceName; } });
23
+ Object.defineProperty(exports, "useRemoveDevice", { enumerable: true, get: function () { return useServicesMutations_1.useRemoveDevice; } });
@@ -0,0 +1,126 @@
1
+ "use strict";
2
+ /**
3
+ * Mutation Factory - Creates standardized mutations with optimistic updates
4
+ *
5
+ * This factory reduces boilerplate code for mutations that follow the common pattern:
6
+ * 1. Cancel outgoing queries
7
+ * 2. Snapshot previous data
8
+ * 3. Apply optimistic update
9
+ * 4. On error: rollback and show toast
10
+ * 5. On success: update cache, stores, and invalidate queries
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.createProfileMutation = createProfileMutation;
14
+ exports.createGenericMutation = createGenericMutation;
15
+ const queryKeys_1 = require("../queries/queryKeys");
16
+ const sonner_1 = require("sonner");
17
+ const authStore_1 = require("../../stores/authStore");
18
+ /**
19
+ * Creates a standard profile mutation with optimistic updates
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * const updateProfile = createProfileMutation({
24
+ * mutationFn: (updates) => oxyServices.updateProfile(updates),
25
+ * optimisticUpdate: (user, updates) => updates,
26
+ * errorMessage: 'Failed to update profile',
27
+ * });
28
+ * ```
29
+ */
30
+ function createProfileMutation(config, queryClient, activeSessionId) {
31
+ const { mutationFn, cancelQueryKeys = [], optimisticUpdate, errorMessage = 'Operation failed', successMessage, updateAuthStore = true, invalidateUserQueries: shouldInvalidateUserQueries = true, invalidateAccountQueries: shouldInvalidateAccountQueries = true, onSuccess: customOnSuccess, } = config;
32
+ return {
33
+ mutationFn,
34
+ onMutate: async (variables) => {
35
+ // Cancel queries that might conflict
36
+ await queryClient.cancelQueries({ queryKey: queryKeys_1.queryKeys.accounts.current() });
37
+ for (const key of cancelQueryKeys) {
38
+ await queryClient.cancelQueries({ queryKey: key });
39
+ }
40
+ // Snapshot previous user data
41
+ const previousUser = queryClient.getQueryData(queryKeys_1.queryKeys.accounts.current());
42
+ // Apply optimistic update if provided
43
+ if (previousUser && optimisticUpdate) {
44
+ const updates = optimisticUpdate(previousUser, variables);
45
+ const optimisticUser = { ...previousUser, ...updates };
46
+ queryClient.setQueryData(queryKeys_1.queryKeys.accounts.current(), optimisticUser);
47
+ if (activeSessionId) {
48
+ queryClient.setQueryData(queryKeys_1.queryKeys.users.profile(activeSessionId), optimisticUser);
49
+ }
50
+ }
51
+ return { previousUser };
52
+ },
53
+ onError: (error, _variables, context) => {
54
+ // Rollback optimistic update
55
+ if (context?.previousUser) {
56
+ queryClient.setQueryData(queryKeys_1.queryKeys.accounts.current(), context.previousUser);
57
+ if (activeSessionId) {
58
+ queryClient.setQueryData(queryKeys_1.queryKeys.users.profile(activeSessionId), context.previousUser);
59
+ }
60
+ }
61
+ // Show error toast
62
+ const message = typeof errorMessage === 'function'
63
+ ? errorMessage(error)
64
+ : (error instanceof Error ? error.message : errorMessage);
65
+ sonner_1.toast.error(message);
66
+ },
67
+ onSuccess: (data, variables) => {
68
+ // Update cache with server response
69
+ queryClient.setQueryData(queryKeys_1.queryKeys.accounts.current(), data);
70
+ if (activeSessionId) {
71
+ queryClient.setQueryData(queryKeys_1.queryKeys.users.profile(activeSessionId), data);
72
+ }
73
+ // Update authStore for immediate UI updates
74
+ if (updateAuthStore) {
75
+ authStore_1.useAuthStore.getState().setUser(data);
76
+ }
77
+ // Invalidate related queries
78
+ if (shouldInvalidateUserQueries) {
79
+ (0, queryKeys_1.invalidateUserQueries)(queryClient);
80
+ }
81
+ if (shouldInvalidateAccountQueries) {
82
+ (0, queryKeys_1.invalidateAccountQueries)(queryClient);
83
+ }
84
+ // Show success toast if configured
85
+ if (successMessage) {
86
+ sonner_1.toast.success(successMessage);
87
+ }
88
+ // Call custom onSuccess handler
89
+ if (customOnSuccess) {
90
+ customOnSuccess(data, variables, queryClient);
91
+ }
92
+ },
93
+ };
94
+ }
95
+ /**
96
+ * Creates a generic mutation with optimistic updates
97
+ */
98
+ function createGenericMutation(config, queryClient) {
99
+ const { mutationFn, queryKey, optimisticData, errorMessage = 'Operation failed', successMessage, invalidateQueries = [], } = config;
100
+ return {
101
+ mutationFn,
102
+ onMutate: async (variables) => {
103
+ await queryClient.cancelQueries({ queryKey });
104
+ const previous = queryClient.getQueryData(queryKey);
105
+ if (optimisticData) {
106
+ queryClient.setQueryData(queryKey, optimisticData(previous, variables));
107
+ }
108
+ return { previous };
109
+ },
110
+ onError: (error, _variables, context) => {
111
+ if (context?.previous !== undefined) {
112
+ queryClient.setQueryData(queryKey, context.previous);
113
+ }
114
+ sonner_1.toast.error(error instanceof Error ? error.message : errorMessage);
115
+ },
116
+ onSuccess: (data) => {
117
+ queryClient.setQueryData(queryKey, data);
118
+ for (const key of invalidateQueries) {
119
+ queryClient.invalidateQueries({ queryKey: key });
120
+ }
121
+ if (successMessage) {
122
+ sonner_1.toast.success(successMessage);
123
+ }
124
+ },
125
+ };
126
+ }