@@ -33,7 +33,7 @@ export interface AuthSession {
3333// On Amplify Lambda, credentials come via IAM role (SDK auto-detects).
3434// Only fall back to in-memory when DynamoDB actually fails or in local dev
3535// without any AWS config.
36- const dynamoFailed = false ;
36+ let dynamoFailed = false ;
3737
3838function shouldUseDynamo ( ) : boolean {
3939 if ( dynamoFailed ) return false ;
@@ -49,6 +49,27 @@ function shouldUseDynamo(): boolean {
4949 return true ;
5050}
5151
52+ /**
53+ * Try a DynamoDB operation; on failure, set dynamoFailed = true and
54+ * transparently retry against the in-memory adapter.
55+ */
56+ async function withFallback < T > (
57+ operation : string ,
58+ dynamoFn : ( ) => Promise < T > ,
59+ memoryFn : ( ) => Promise < T > ,
60+ ) : Promise < T > {
61+ if ( ! shouldUseDynamo ( ) ) {
62+ return memoryFn ( ) ;
63+ }
64+ try {
65+ return await dynamoFn ( ) ;
66+ } catch ( err ) {
67+ console . warn ( `[auth/dynamodb] ${ operation } failed, falling back to in-memory:` , err ) ;
68+ dynamoFailed = true ;
69+ return memoryFn ( ) ;
70+ }
71+ }
72+
5273// ─── In-memory fallback for local dev ────────────────────────────────
5374const memoryUsers = new Map < string , AuthUser > ( ) ;
5475const memoryUsersByEmail = new Map < string , AuthUser > ( ) ;
@@ -231,41 +252,34 @@ const dynamoAdapter = {
231252 } ,
232253} ;
233254
234- // ─── Exported interface — auto-selects adapter ───────────────────────
235- function getAdapter ( ) {
236- if ( shouldUseDynamo ( ) ) {
237- return dynamoAdapter ;
238- }
239- console . warn ( "[auth/dynamodb] Using in-memory store (DynamoDB unavailable)" ) ;
240- return memoryAdapter ;
241- }
255+ // ─── Exported interface — uses withFallback for resilience ───────────
242256
243257export async function createUser ( user : AuthUser ) : Promise < AuthUser > {
244- return getAdapter ( ) . createUser ( user ) ;
258+ return withFallback ( "createUser" , ( ) => dynamoAdapter . createUser ( user ) , ( ) => memoryAdapter . createUser ( user ) ) ;
245259}
246260
247261export async function getUserByEmail ( email : string ) : Promise < AuthUser | null > {
248- return getAdapter ( ) . getUserByEmail ( email ) ;
262+ return withFallback ( "getUserByEmail" , ( ) => dynamoAdapter . getUserByEmail ( email ) , ( ) => memoryAdapter . getUserByEmail ( email ) ) ;
249263}
250264
251265export async function getUserById ( id : string ) : Promise < AuthUser | null > {
252- return getAdapter ( ) . getUserById ( id ) ;
266+ return withFallback ( "getUserById" , ( ) => dynamoAdapter . getUserById ( id ) , ( ) => memoryAdapter . getUserById ( id ) ) ;
253267}
254268
255269export async function updateUser ( id : string , updates : Partial < AuthUser > ) : Promise < AuthUser | null > {
256- return getAdapter ( ) . updateUser ( id , updates ) ;
270+ return withFallback ( "updateUser" , ( ) => dynamoAdapter . updateUser ( id , updates ) , ( ) => memoryAdapter . updateUser ( id , updates ) ) ;
257271}
258272
259273export async function createAuthSession ( session : AuthSession ) : Promise < AuthSession > {
260- return getAdapter ( ) . createAuthSession ( session ) ;
274+ return withFallback ( "createAuthSession" , ( ) => dynamoAdapter . createAuthSession ( session ) , ( ) => memoryAdapter . createAuthSession ( session ) ) ;
261275}
262276
263277export async function getAuthSession ( token : string ) : Promise < AuthSession | null > {
264- return getAdapter ( ) . getAuthSession ( token ) ;
278+ return withFallback ( "getAuthSession" , ( ) => dynamoAdapter . getAuthSession ( token ) , ( ) => memoryAdapter . getAuthSession ( token ) ) ;
265279}
266280
267281export async function deleteAuthSession ( token : string ) : Promise < void > {
268- return getAdapter ( ) . deleteAuthSession ( token ) ;
282+ return withFallback ( "deleteAuthSession" , ( ) => dynamoAdapter . deleteAuthSession ( token ) , ( ) => memoryAdapter . deleteAuthSession ( token ) ) ;
269283}
270284
271285/**
@@ -278,20 +292,17 @@ export async function upsertGoogleUser(profile: {
278292 name : string ;
279293 picture : string ;
280294} ) : Promise < AuthUser > {
281- const adapter = getAdapter ( ) ;
282- const existing = await adapter . getUserByEmail ( profile . email ) ;
295+ const existing = await getUserByEmail ( profile . email ) ;
283296
284297 if ( existing ) {
285- // Update name/picture/googleId if they changed
286- const updated = await adapter . updateUser ( existing . id , {
298+ const updated = await updateUser ( existing . id , {
287299 name : profile . name ,
288300 picture : profile . picture ,
289301 googleId : profile . id ,
290302 } ) ;
291303 return updated ! ;
292304 }
293305
294- // New user
295306 const now = new Date ( ) . toISOString ( ) ;
296307 const newUser : AuthUser = {
297308 id : crypto . randomUUID ( ) ,
@@ -302,7 +313,7 @@ export async function upsertGoogleUser(profile: {
302313 createdAt : now ,
303314 updatedAt : now ,
304315 } ;
305- return adapter . createUser ( newUser ) ;
316+ return createUser ( newUser ) ;
306317}
307318
308319/**
0 commit comments