@eminuckan/spine 0.3.0 → 0.3.2

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 (87) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +116 -98
  3. package/SECURITY.md +5 -15
  4. package/dist/access/index.mjs +2 -0
  5. package/dist/{access-CGrsDMwu.js → access-BHy1v5sr.mjs} +6 -8
  6. package/dist/access-BHy1v5sr.mjs.map +1 -0
  7. package/dist/api-client/index.mjs +3 -0
  8. package/dist/api-client/server.mjs +4 -0
  9. package/dist/{api-client-CgZUMukO.js → api-client-CB7XZaMg.mjs} +8 -10
  10. package/dist/api-client-CB7XZaMg.mjs.map +1 -0
  11. package/dist/auth/auth.server.d.ts +1 -0
  12. package/dist/auth/auth.server.d.ts.map +1 -1
  13. package/dist/auth/index.mjs +1 -0
  14. package/dist/auth/route-protection.server.d.ts.map +1 -1
  15. package/dist/auth/server.mjs +3 -0
  16. package/dist/{identity-C5un-xAs.js → identity/index.mjs} +24 -21
  17. package/dist/identity/index.mjs.map +1 -0
  18. package/dist/{server-Dxsww9v9.js → identity/server.mjs} +7 -8
  19. package/dist/identity/server.mjs.map +1 -0
  20. package/dist/{index.js → index.mjs} +11 -13
  21. package/dist/{logging-DviiAkLx.js → logging/index.mjs} +22 -24
  22. package/dist/logging/index.mjs.map +1 -0
  23. package/dist/{permission-service-CvGpwI0d.js → permission-service-DUyKomU3.mjs} +6 -10
  24. package/dist/permission-service-DUyKomU3.mjs.map +1 -0
  25. package/dist/permissions/index.mjs +3 -0
  26. package/dist/{permissions-_n1hIG7Q.js → permissions-C5dTP9y4.mjs} +23 -15
  27. package/dist/permissions-C5dTP9y4.mjs.map +1 -0
  28. package/dist/query-client/index.mjs +2 -0
  29. package/dist/{query-client-Bh3TyB8W.js → query-client-_xB300gc.mjs} +49 -15
  30. package/dist/query-client-_xB300gc.mjs.map +1 -0
  31. package/dist/react-router/{index.js → index.mjs} +11 -13
  32. package/dist/react-router/server.mjs +17 -0
  33. package/dist/{redis-session-storage.server-v2VcpFsQ.js → redis-session-storage.server-DGgQcJ9B.mjs} +30 -42
  34. package/dist/redis-session-storage.server-DGgQcJ9B.mjs.map +1 -0
  35. package/dist/{server-C96NMFBy.js → server-ByScGRXA.mjs} +39 -36
  36. package/dist/server-ByScGRXA.mjs.map +1 -0
  37. package/dist/{auth.server-D5oiz9aF.js → server-CqX2yCa4.mjs} +388 -119
  38. package/dist/server-CqX2yCa4.mjs.map +1 -0
  39. package/dist/server.mjs +90 -0
  40. package/dist/server.mjs.map +1 -0
  41. package/dist/signalr/index.mjs +2 -0
  42. package/dist/{signalr-k-70VHKo.js → signalr-C-TA8nEE.mjs} +11 -6
  43. package/dist/signalr-C-TA8nEE.mjs.map +1 -0
  44. package/dist/{tenant-DfLemb6n.js → tenant/index.mjs} +19 -12
  45. package/dist/tenant/index.mjs.map +1 -0
  46. package/dist/{server-Ca-3DJf2.js → tenant/server.mjs} +12 -12
  47. package/dist/tenant/server.mjs.map +1 -0
  48. package/dist/{types-BaaE6wjY.js → types-DqCXinAk.mjs} +3 -3
  49. package/dist/types-DqCXinAk.mjs.map +1 -0
  50. package/docs/codex-for-oss-application.md +14 -2
  51. package/package.json +14 -2
  52. package/dist/access/index.js +0 -4
  53. package/dist/access-CGrsDMwu.js.map +0 -1
  54. package/dist/api-client/index.js +0 -5
  55. package/dist/api-client/server.js +0 -6
  56. package/dist/api-client-CgZUMukO.js.map +0 -1
  57. package/dist/auth/index.js +0 -0
  58. package/dist/auth/server.js +0 -6
  59. package/dist/auth.server-D5oiz9aF.js.map +0 -1
  60. package/dist/auth.server-lC94lmlx.js +0 -5
  61. package/dist/identity/index.js +0 -3
  62. package/dist/identity/server.js +0 -5
  63. package/dist/identity-C5un-xAs.js.map +0 -1
  64. package/dist/logging/index.js +0 -3
  65. package/dist/logging-DviiAkLx.js.map +0 -1
  66. package/dist/permission-service-CvGpwI0d.js.map +0 -1
  67. package/dist/permissions/index.js +0 -4
  68. package/dist/permissions-_n1hIG7Q.js.map +0 -1
  69. package/dist/query-client/index.js +0 -3
  70. package/dist/query-client-Bh3TyB8W.js.map +0 -1
  71. package/dist/react-router/server.js +0 -20
  72. package/dist/redis-session-storage.server-v2VcpFsQ.js.map +0 -1
  73. package/dist/server-B6PCxc-T.js +0 -304
  74. package/dist/server-B6PCxc-T.js.map +0 -1
  75. package/dist/server-C96NMFBy.js.map +0 -1
  76. package/dist/server-Ca-3DJf2.js.map +0 -1
  77. package/dist/server-Dxsww9v9.js.map +0 -1
  78. package/dist/server-hGrElJMG.js +0 -78
  79. package/dist/server-hGrElJMG.js.map +0 -1
  80. package/dist/server.js +0 -20
  81. package/dist/signalr/index.js +0 -3
  82. package/dist/signalr-k-70VHKo.js.map +0 -1
  83. package/dist/src-B9rDUgSb.js +0 -0
  84. package/dist/tenant/index.js +0 -3
  85. package/dist/tenant/server.js +0 -5
  86. package/dist/tenant-DfLemb6n.js.map +0 -1
  87. package/dist/types-BaaE6wjY.js.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"server-B6PCxc-T.js","names":["request: Request","jwtExpiry: number | null","protectionConfig: RouteProtectionConfig<any>","config: RouteProtectionConfig<TIdentityContext>","redirect: RouteProtectionRedirect","currentPath: string","protection: ProtectionLevel","reason: AutomaticLogoutRequestContext['reason']","user: UserInfo","identityContextPromise: Promise<any | null> | null","currentTenantPromise: Promise<string | null> | null","initializeTenantPromise: Promise<{ headers: Headers; tenantId: string } | null> | null","loaderFn: (user?: UserInfo) => Promise<T> | T","loaderFn: (user: UserInfo) => Promise<T> | T","request: Request","response: Response","hasAuth: boolean"],"sources":["../src/auth/route-protection.server.ts","../src/auth/token-refresh.server.ts"],"sourcesContent":["/**\n * Route Protection Server Module\n * \n * Provides route protection utilities for different authentication levels.\n */\n\nimport { getUser, requireAuth, refreshTokens, login } from './auth.server';\nimport { getAuthSession } from './redis-session-storage.server';\nimport type { ProtectionLevel, UserInfo } from './types';\nimport { logger } from '../logging';\nimport { createRedirectResponse } from '../http/response';\n\n// Refresh shortly before expiry. Keep this below common 5-minute access-token\n// lifetimes so a freshly issued token does not refresh again immediately.\nconst REFRESH_THRESHOLD = resolveRefreshThreshold();\n\nfunction resolveRefreshThreshold(): number {\n const configured = Number(process.env.OIDC_REFRESH_THRESHOLD_MS);\n return Number.isFinite(configured) && configured > 0 ? configured : 60_000;\n}\n\n/**\n * Check if token needs refresh\n */\nexport async function shouldRefreshToken(request: Request): Promise<boolean> {\n try {\n const sessionData = await getAuthSession(request);\n\n if (!sessionData.refreshToken || sessionData.refreshToken.length === 0) {\n if (sessionData.expiresAt && Date.now() >= sessionData.expiresAt) {\n logger.info('Token expired and no refresh token, forcing local application logout');\n const { logout } = await import('./auth.server');\n const logoutRequest = await createAutomaticLogoutRequest(request, 'expired-no-refresh-token');\n throw await logout(logoutRequest);\n }\n return false;\n }\n\n if (!sessionData.expiresAt || !sessionData.accessToken) {\n return false;\n }\n\n const now = Date.now();\n\n // Check JWT expiry\n let jwtExpiry: number | null = null;\n try {\n const tokenParts = sessionData.accessToken.split('.');\n if (tokenParts.length === 3) {\n const payload = JSON.parse(Buffer.from(tokenParts[1], 'base64').toString());\n if (payload.exp) {\n jwtExpiry = payload.exp * 1000;\n }\n }\n } catch {\n // Ignore JWT decode errors\n }\n\n const earliestExpiry = jwtExpiry && jwtExpiry < sessionData.expiresAt \n ? jwtExpiry \n : sessionData.expiresAt;\n const timeUntilEarliestExpiry = earliestExpiry - now;\n\n return timeUntilEarliestExpiry <= REFRESH_THRESHOLD;\n } catch (error) {\n logger.error('Error checking token refresh', error instanceof Error ? error : undefined);\n return false;\n }\n}\n\n/**\n * Auto-refresh tokens if needed\n */\nasync function autoRefreshTokens(request: Request): Promise<void> {\n try {\n const needsRefresh = await shouldRefreshToken(request);\n\n if (needsRefresh) {\n logger.info('Auto-refreshing tokens due to expiry');\n const result = await refreshTokens(request);\n\n if (result.success) {\n logger.info('Auto-refresh completed successfully');\n } else {\n logger.error('Auto-refresh failed', undefined, { error: result.error });\n\n if (result.shouldLogout) {\n logger.error('Refresh token invalid, forcing local application logout');\n const { logout } = await import('./auth.server');\n const logoutRequest = await createAutomaticLogoutRequest(request, 'refresh-failed');\n throw await logout(logoutRequest);\n }\n }\n }\n } catch (error) {\n if (error instanceof Response && error.status >= 300 && error.status < 400) {\n throw error;\n }\n throw error;\n }\n}\n\n/**\n * Identity context check configuration\n */\nexport interface DefaultRouteIdentityContext {\n hasAnyMembership: boolean;\n hasEntitlement?: boolean;\n /** @deprecated Use hasEntitlement or app-specific fields instead. */\n hasSubscription: boolean;\n isOnboarded: boolean;\n}\n\nexport interface IdentityContextChecker<TIdentityContext = DefaultRouteIdentityContext> {\n getIdentityContext: (\n request: Request,\n userId: string,\n forceRefresh?: boolean\n ) => Promise<DefaultRouteIdentityContext & TIdentityContext>;\n}\n\n/**\n * Tenant checker configuration\n */\nexport interface TenantChecker {\n getCurrentTenant: (request: Request) => Promise<string | null>;\n initializeTenant: (request: Request) => Promise<{ headers: Headers; tenantId: string } | null>;\n}\n\n/**\n * Legacy subscription checker configuration\n *\n * @deprecated Put entitlement policy in resolveRoute or app-level route loaders.\n */\nexport interface SubscriptionChecker {\n checkTenantSubscriptionStatus: (request: Request, tenantId: string) => Promise<boolean>;\n}\n\nexport interface RouteProtectionRedirect {\n location: string;\n headers?: HeadersInit;\n status?: number;\n}\n\nexport interface AutomaticLogoutRequestContext {\n request: Request;\n reason: 'expired-no-refresh-token' | 'refresh-failed';\n}\n\nexport interface LoginReturnUrlContext {\n request: Request;\n currentPath: string;\n protection: ProtectionLevel;\n}\n\nexport interface RouteProtectionResolverContext<TIdentityContext = DefaultRouteIdentityContext> {\n request: Request;\n user: UserInfo;\n currentPath: string;\n protection: ProtectionLevel;\n getIdentityContext: (forceRefresh?: boolean) => Promise<TIdentityContext | null>;\n getCurrentTenant: () => Promise<string | null>;\n initializeTenant: () => Promise<{ headers: Headers; tenantId: string } | null>;\n}\n\nexport interface RouteProtectionErrorHandlerContext<TIdentityContext = DefaultRouteIdentityContext>\n extends RouteProtectionResolverContext<TIdentityContext> {\n error: unknown;\n}\n\n/**\n * Route protection configuration\n */\nexport interface RouteProtectionConfig<TIdentityContext = DefaultRouteIdentityContext> {\n identityChecker?: IdentityContextChecker<TIdentityContext>;\n tenantChecker?: TenantChecker;\n subscriptionChecker?: SubscriptionChecker;\n resolveRoute?: (\n context: RouteProtectionResolverContext<TIdentityContext>\n ) => Promise<RouteProtectionRedirect | null | void> | RouteProtectionRedirect | null | void;\n handleRouteError?: (\n context: RouteProtectionErrorHandlerContext<TIdentityContext>\n ) => Promise<RouteProtectionRedirect | null | void> | RouteProtectionRedirect | null | void;\n getLoginReturnUrl?: (context: LoginReturnUrlContext) => string;\n createAutomaticLogoutRequest?: (\n context: AutomaticLogoutRequestContext\n ) => Promise<Request> | Request;\n}\n\nlet protectionConfig: RouteProtectionConfig<any> = {};\n\n/**\n * Configure route protection with identity, tenant, and app policy checkers\n */\nexport function configureRouteProtection<TIdentityContext = DefaultRouteIdentityContext>(\n config: RouteProtectionConfig<TIdentityContext>\n): void {\n protectionConfig = config;\n}\n\n/**\n * Reset route protection to its built-in defaults.\n */\nexport function resetRouteProtectionConfig(): void {\n protectionConfig = {};\n}\n\nfunction createRedirectFromConfig(redirect: RouteProtectionRedirect): Response {\n return createRedirectResponse(redirect.location, {\n headers: redirect.headers,\n status: redirect.status,\n });\n}\n\nfunction getLoginReturnUrl(\n request: Request,\n currentPath: string,\n protection: ProtectionLevel\n): string {\n if (protectionConfig.getLoginReturnUrl) {\n return protectionConfig.getLoginReturnUrl({\n request,\n currentPath,\n protection,\n });\n }\n\n switch (protection) {\n case 'onboarding-required':\n case 'subscription-required':\n case 'public':\n case 'auth':\n default: {\n const url = new URL(request.url);\n return url.pathname === '/' ? '/' : `${url.pathname}${url.search}`;\n }\n }\n}\n\nasync function createAutomaticLogoutRequest(\n request: Request,\n reason: AutomaticLogoutRequestContext['reason']\n): Promise<Request> {\n if (protectionConfig.createAutomaticLogoutRequest) {\n return protectionConfig.createAutomaticLogoutRequest({ request, reason });\n }\n\n const logoutUrl = new URL(request.url);\n logoutUrl.pathname = '/auth/logout';\n logoutUrl.searchParams.set('logout', 'local');\n\n return new Request(logoutUrl.toString(), {\n headers: request.headers,\n });\n}\n\nfunction createRouteResolverContext(\n request: Request,\n user: UserInfo,\n currentPath: string,\n protection: ProtectionLevel\n): RouteProtectionResolverContext<any> {\n let identityContextPromise: Promise<any | null> | null = null;\n let currentTenantPromise: Promise<string | null> | null = null;\n let initializeTenantPromise: Promise<{ headers: Headers; tenantId: string } | null> | null = null;\n\n return {\n request,\n user,\n currentPath,\n protection,\n getIdentityContext: async (forceRefresh = false) => {\n if (!protectionConfig.identityChecker) {\n return null;\n }\n\n if (forceRefresh) {\n return protectionConfig.identityChecker.getIdentityContext(request, user.sub, true);\n }\n\n if (!identityContextPromise) {\n identityContextPromise = protectionConfig.identityChecker.getIdentityContext(request, user.sub);\n }\n\n return identityContextPromise;\n },\n getCurrentTenant: async () => {\n if (!protectionConfig.tenantChecker) {\n return null;\n }\n\n if (!currentTenantPromise) {\n currentTenantPromise = protectionConfig.tenantChecker.getCurrentTenant(request);\n }\n\n return currentTenantPromise;\n },\n initializeTenant: async () => {\n if (!protectionConfig.tenantChecker) {\n return null;\n }\n\n if (!initializeTenantPromise) {\n initializeTenantPromise = protectionConfig.tenantChecker.initializeTenant(request);\n }\n\n return initializeTenantPromise;\n },\n };\n}\n\nasync function applyConfiguredRouteResolution(\n request: Request,\n user: UserInfo,\n currentPath: string,\n protection: ProtectionLevel\n): Promise<boolean> {\n if (!protectionConfig.resolveRoute) {\n return false;\n }\n\n const context = createRouteResolverContext(request, user, currentPath, protection);\n\n try {\n const result = await protectionConfig.resolveRoute(context);\n if (result) {\n throw createRedirectFromConfig(result);\n }\n\n return true;\n } catch (error) {\n if (error instanceof Response && error.status >= 300 && error.status < 400) {\n throw error;\n }\n\n if (!protectionConfig.handleRouteError) {\n throw error;\n }\n\n const recovery = await protectionConfig.handleRouteError({\n ...context,\n error,\n });\n\n if (recovery) {\n throw createRedirectFromConfig(recovery);\n }\n\n throw error;\n }\n}\n\n/**\n * Main route protection function\n */\nexport async function protectRoute<T>(\n request: Request,\n protection: ProtectionLevel,\n loaderFn: (user?: UserInfo) => Promise<T> | T\n): Promise<T> {\n const url = new URL(request.url);\n\n switch (protection) {\n case 'public':\n try {\n await autoRefreshTokens(request);\n const user = await getUser(request);\n return loaderFn(user ?? undefined);\n } catch {\n return loaderFn(undefined);\n }\n\n case 'auth':\n case 'policy':\n try {\n await autoRefreshTokens(request);\n const user = await requireAuth(request);\n await applyConfiguredRouteResolution(request, user, url.pathname, protection);\n return loaderFn(user);\n } catch (error) {\n if (error instanceof Response && error.status >= 300 && error.status < 400) {\n throw error;\n }\n throw await login(request, getLoginReturnUrl(request, url.pathname, protection));\n }\n\n case 'onboarding-required':\n try {\n await autoRefreshTokens(request);\n const user = await requireAuth(request);\n\n await applyConfiguredRouteResolution(\n request,\n user,\n url.pathname,\n 'onboarding-required'\n );\n\n return loaderFn(user);\n } catch (error) {\n if (error instanceof Response && error.status >= 300 && error.status < 400) {\n throw error;\n }\n throw await login(request, getLoginReturnUrl(request, url.pathname, 'onboarding-required'));\n }\n\n case 'subscription-required':\n try {\n await autoRefreshTokens(request);\n const user = await requireAuth(request);\n\n await applyConfiguredRouteResolution(\n request,\n user,\n url.pathname,\n 'subscription-required'\n );\n\n // Initialize tenant if needed\n if (protectionConfig.tenantChecker) {\n const currentTenant = await protectionConfig.tenantChecker.getCurrentTenant(request);\n if (!currentTenant) {\n await protectionConfig.tenantChecker.initializeTenant(request);\n }\n }\n\n return loaderFn(user);\n } catch (error) {\n if (error instanceof Response && error.status >= 300 && error.status < 400) {\n throw error;\n }\n throw await login(request, getLoginReturnUrl(request, url.pathname, 'subscription-required'));\n }\n\n default:\n throw new Error(`Unknown protection level: ${protection}`);\n }\n}\n\n// ============================================================================\n// Convenience Functions\n// ============================================================================\n\n/**\n * Public route (optional auth)\n */\nexport const publicRoute = <T>(\n request: Request,\n loaderFn: (user?: UserInfo) => Promise<T> | T\n) => protectRoute(request, 'public', loaderFn);\n\n/**\n * Auth-protected route\n */\nexport const authRoute = <T>(\n request: Request,\n loaderFn: (user: UserInfo) => Promise<T> | T\n) => protectRoute(request, 'auth', loaderFn as (user?: UserInfo) => Promise<T> | T);\n\n/**\n * Generic policy-protected route. Apps define policy in configureRouteProtection.\n */\nexport const policyRoute = <T>(\n request: Request,\n loaderFn: (user: UserInfo) => Promise<T> | T\n) => protectRoute(request, 'policy', loaderFn as (user?: UserInfo) => Promise<T> | T);\n\n/**\n * Alias for auth-protected routes.\n */\nexport const protectedRoute = authRoute;\n\n/**\n * Setup policy route\n *\n * @deprecated Use policyRoute with configureRouteProtection instead.\n */\nexport const onboardingRoute = <T>(\n request: Request,\n loaderFn: (user: UserInfo) => Promise<T> | T\n) => protectRoute(request, 'onboarding-required', loaderFn as (user?: UserInfo) => Promise<T> | T);\n\n/**\n * Entitlement policy route\n *\n * @deprecated Use policyRoute with configureRouteProtection instead.\n */\nexport const subscriptionRoute = <T>(\n request: Request,\n loaderFn: (user: UserInfo) => Promise<T> | T\n) => protectRoute(request, 'subscription-required', loaderFn as (user?: UserInfo) => Promise<T> | T);\n","/**\n * Token Refresh Server Module\n */\n\nimport { refreshTokens } from './auth.server';\nimport { getAuthSession } from './redis-session-storage.server';\nimport type { TokenRefreshResult } from './types';\nimport { logger } from '../logging';\n\n/**\n * Attempt to refresh the access token\n */\nexport async function attemptTokenRefresh(request: Request): Promise<TokenRefreshResult> {\n try {\n logger.info('Attempting token refresh due to 401 response');\n\n const sessionData = await getAuthSession(request);\n\n if (!sessionData.refreshToken) {\n logger.warn('No refresh token available in session');\n return {\n success: false,\n error: 'No refresh token available',\n shouldLogout: true,\n };\n }\n\n const refreshResult = await refreshTokens(request, sessionData.refreshToken);\n\n if (!refreshResult.success) {\n logger.error('Token refresh failed', undefined, {\n error: refreshResult.error,\n shouldLogout: refreshResult.shouldLogout,\n });\n\n return {\n success: false,\n error: refreshResult.error,\n shouldLogout: refreshResult.shouldLogout,\n };\n }\n\n const tokens = refreshResult.tokens;\n if (!tokens || !tokens.access_token) {\n logger.error('Token refresh succeeded but no access token returned');\n return {\n success: false,\n error: 'No access token in refresh response',\n shouldLogout: true,\n };\n }\n\n logger.info('Token refresh successful');\n\n return {\n success: true,\n newAccessToken: tokens.access_token,\n };\n } catch (error) {\n logger.error('Token refresh attempt failed', error instanceof Error ? error : undefined);\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Unknown error',\n shouldLogout: true,\n };\n }\n}\n\n/**\n * Check if a response indicates token expiration\n */\nexport function isTokenExpiredResponse(response: Response): boolean {\n return response.status === 401;\n}\n\n/**\n * Check if token refresh should be attempted\n */\nexport function shouldAttemptTokenRefresh(response: Response, hasAuth: boolean): boolean {\n return hasAuth && isTokenExpiredResponse(response);\n}\n"],"mappings":";;;;;AAcA,MAAM,oBAAoB,yBAAyB;AAEnD,SAAS,0BAAkC;CACzC,MAAM,aAAa,OAAO,QAAQ,IAAI,0BAA0B;AAChE,QAAO,OAAO,SAAS,WAAW,IAAI,aAAa,IAAI,aAAa;AACrE;;;;AAKD,eAAsB,mBAAmBA,SAAoC;AAC3E,KAAI;EACF,MAAM,cAAc,MAAM,eAAe,QAAQ;AAEjD,OAAK,YAAY,gBAAgB,YAAY,aAAa,WAAW,GAAG;AACtE,OAAI,YAAY,aAAa,KAAK,KAAK,IAAI,YAAY,WAAW;AAChE,WAAO,KAAK,uEAAuE;IACnF,MAAM,EAAE,QAAQ,GAAG,MAAM,OAAO;IAChC,MAAM,gBAAgB,MAAM,6BAA6B,SAAS,2BAA2B;AAC7F,UAAM,MAAM,OAAO,cAAc;GAClC;AACD,UAAO;EACR;AAED,OAAK,YAAY,cAAc,YAAY,YACzC,QAAO;EAGT,MAAM,MAAM,KAAK,KAAK;EAGtB,IAAIC,YAA2B;AAC/B,MAAI;GACF,MAAM,aAAa,YAAY,YAAY,MAAM,IAAI;AACrD,OAAI,WAAW,WAAW,GAAG;IAC3B,MAAM,UAAU,KAAK,MAAM,OAAO,KAAK,WAAW,IAAI,SAAS,CAAC,UAAU,CAAC;AAC3E,QAAI,QAAQ,IACV,aAAY,QAAQ,MAAM;GAE7B;EACF,QAAO,CAEP;EAED,MAAM,iBAAiB,aAAa,YAAY,YAAY,YACxD,YACA,YAAY;EAChB,MAAM,0BAA0B,iBAAiB;AAEjD,SAAO,2BAA2B;CACnC,SAAQ,OAAO;AACd,SAAO,MAAM,gCAAgC,iBAAiB,QAAQ,eAAkB;AACxF,SAAO;CACR;AACF;;;;AAKD,eAAe,kBAAkBD,SAAiC;AAChE,KAAI;EACF,MAAM,eAAe,MAAM,mBAAmB,QAAQ;AAEtD,MAAI,cAAc;AAChB,UAAO,KAAK,uCAAuC;GACnD,MAAM,SAAS,MAAM,cAAc,QAAQ;AAE3C,OAAI,OAAO,QACT,QAAO,KAAK,sCAAsC;QAC7C;AACL,WAAO,MAAM,+BAAkC,EAAE,OAAO,OAAO,MAAO,EAAC;AAEvE,QAAI,OAAO,cAAc;AACvB,YAAO,MAAM,0DAA0D;KACvE,MAAM,EAAE,QAAQ,GAAG,MAAM,OAAO;KAChC,MAAM,gBAAgB,MAAM,6BAA6B,SAAS,iBAAiB;AACnF,WAAM,MAAM,OAAO,cAAc;IAClC;GACF;EACF;CACF,SAAQ,OAAO;AACd,MAAI,iBAAiB,YAAY,MAAM,UAAU,OAAO,MAAM,SAAS,IACrE,OAAM;AAER,QAAM;CACP;AACF;AAyFD,IAAIE,mBAA+C,CAAE;;;;AAKrD,SAAgB,yBACdC,QACM;AACN,oBAAmB;AACpB;;;;AAKD,SAAgB,6BAAmC;AACjD,oBAAmB,CAAE;AACtB;AAED,SAAS,yBAAyBC,UAA6C;AAC7E,QAAO,uBAAuB,SAAS,UAAU;EAC/C,SAAS,SAAS;EAClB,QAAQ,SAAS;CAClB,EAAC;AACH;AAED,SAAS,kBACPJ,SACAK,aACAC,YACQ;AACR,KAAI,iBAAiB,kBACnB,QAAO,iBAAiB,kBAAkB;EACxC;EACA;EACA;CACD,EAAC;AAGJ,SAAQ,YAAR;EACE,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,SAAS;GACP,MAAM,MAAM,IAAI,IAAI,QAAQ;AAC5B,UAAO,IAAI,aAAa,MAAM,OAAO,EAAE,IAAI,SAAS,EAAE,IAAI,OAAO;EAClE;CACF;AACF;AAED,eAAe,6BACbN,SACAO,QACkB;AAClB,KAAI,iBAAiB,6BACnB,QAAO,iBAAiB,6BAA6B;EAAE;EAAS;CAAQ,EAAC;CAG3E,MAAM,YAAY,IAAI,IAAI,QAAQ;AAClC,WAAU,WAAW;AACrB,WAAU,aAAa,IAAI,UAAU,QAAQ;AAE7C,QAAO,IAAI,QAAQ,UAAU,UAAU,EAAE,EACvC,SAAS,QAAQ,QAClB;AACF;AAED,SAAS,2BACPP,SACAQ,MACAH,aACAC,YACqC;CACrC,IAAIG,yBAAqD;CACzD,IAAIC,uBAAsD;CAC1D,IAAIC,0BAAyF;AAE7F,QAAO;EACL;EACA;EACA;EACA;EACA,oBAAoB,OAAO,eAAe,UAAU;AAClD,QAAK,iBAAiB,gBACpB,QAAO;AAGT,OAAI,aACF,QAAO,iBAAiB,gBAAgB,mBAAmB,SAAS,KAAK,KAAK,KAAK;AAGrF,QAAK,uBACH,0BAAyB,iBAAiB,gBAAgB,mBAAmB,SAAS,KAAK,IAAI;AAGjG,UAAO;EACR;EACD,kBAAkB,YAAY;AAC5B,QAAK,iBAAiB,cACpB,QAAO;AAGT,QAAK,qBACH,wBAAuB,iBAAiB,cAAc,iBAAiB,QAAQ;AAGjF,UAAO;EACR;EACD,kBAAkB,YAAY;AAC5B,QAAK,iBAAiB,cACpB,QAAO;AAGT,QAAK,wBACH,2BAA0B,iBAAiB,cAAc,iBAAiB,QAAQ;AAGpF,UAAO;EACR;CACF;AACF;AAED,eAAe,+BACbX,SACAQ,MACAH,aACAC,YACkB;AAClB,MAAK,iBAAiB,aACpB,QAAO;CAGT,MAAM,UAAU,2BAA2B,SAAS,MAAM,aAAa,WAAW;AAElF,KAAI;EACF,MAAM,SAAS,MAAM,iBAAiB,aAAa,QAAQ;AAC3D,MAAI,OACF,OAAM,yBAAyB,OAAO;AAGxC,SAAO;CACR,SAAQ,OAAO;AACd,MAAI,iBAAiB,YAAY,MAAM,UAAU,OAAO,MAAM,SAAS,IACrE,OAAM;AAGR,OAAK,iBAAiB,iBACpB,OAAM;EAGR,MAAM,WAAW,MAAM,iBAAiB,iBAAiB;GACvD,GAAG;GACH;EACD,EAAC;AAEF,MAAI,SACF,OAAM,yBAAyB,SAAS;AAG1C,QAAM;CACP;AACF;;;;AAKD,eAAsB,aACpBN,SACAM,YACAM,UACY;CACZ,MAAM,MAAM,IAAI,IAAI,QAAQ;AAE5B,SAAQ,YAAR;EACE,KAAK,SACH,KAAI;AACF,SAAM,kBAAkB,QAAQ;GAChC,MAAM,OAAO,MAAM,QAAQ,QAAQ;AACnC,UAAO,SAAS,eAAkB;EACnC,QAAO;AACN,UAAO,gBAAmB;EAC3B;EAEH,KAAK;EACL,KAAK,SACH,KAAI;AACF,SAAM,kBAAkB,QAAQ;GAChC,MAAM,OAAO,MAAM,YAAY,QAAQ;AACvC,SAAM,+BAA+B,SAAS,MAAM,IAAI,UAAU,WAAW;AAC7E,UAAO,SAAS,KAAK;EACtB,SAAQ,OAAO;AACd,OAAI,iBAAiB,YAAY,MAAM,UAAU,OAAO,MAAM,SAAS,IACrE,OAAM;AAER,SAAM,MAAM,MAAM,SAAS,kBAAkB,SAAS,IAAI,UAAU,WAAW,CAAC;EACjF;EAEH,KAAK,sBACH,KAAI;AACF,SAAM,kBAAkB,QAAQ;GAChC,MAAM,OAAO,MAAM,YAAY,QAAQ;AAEvC,SAAM,+BACJ,SACA,MACA,IAAI,UACJ,sBACD;AAED,UAAO,SAAS,KAAK;EACtB,SAAQ,OAAO;AACd,OAAI,iBAAiB,YAAY,MAAM,UAAU,OAAO,MAAM,SAAS,IACrE,OAAM;AAER,SAAM,MAAM,MAAM,SAAS,kBAAkB,SAAS,IAAI,UAAU,sBAAsB,CAAC;EAC5F;EAEH,KAAK,wBACH,KAAI;AACF,SAAM,kBAAkB,QAAQ;GAChC,MAAM,OAAO,MAAM,YAAY,QAAQ;AAEvC,SAAM,+BACJ,SACA,MACA,IAAI,UACJ,wBACD;AAGD,OAAI,iBAAiB,eAAe;IAClC,MAAM,gBAAgB,MAAM,iBAAiB,cAAc,iBAAiB,QAAQ;AACpF,SAAK,cACH,OAAM,iBAAiB,cAAc,iBAAiB,QAAQ;GAEjE;AAED,UAAO,SAAS,KAAK;EACtB,SAAQ,OAAO;AACd,OAAI,iBAAiB,YAAY,MAAM,UAAU,OAAO,MAAM,SAAS,IACrE,OAAM;AAER,SAAM,MAAM,MAAM,SAAS,kBAAkB,SAAS,IAAI,UAAU,wBAAwB,CAAC;EAC9F;EAEH,QACE,OAAM,IAAI,OAAO,4BAA4B,WAAW;CAC3D;AACF;;;;AASD,MAAa,cAAc,CACzBZ,SACAY,aACG,aAAa,SAAS,UAAU,SAAS;;;;AAK9C,MAAa,YAAY,CACvBZ,SACAa,aACG,aAAa,SAAS,QAAQ,SAAgD;;;;AAKnF,MAAa,cAAc,CACzBb,SACAa,aACG,aAAa,SAAS,UAAU,SAAgD;;;;AAKrF,MAAa,iBAAiB;;;;;;AAO9B,MAAa,kBAAkB,CAC7Bb,SACAa,aACG,aAAa,SAAS,uBAAuB,SAAgD;;;;;;AAOlG,MAAa,oBAAoB,CAC/Bb,SACAa,aACG,aAAa,SAAS,yBAAyB,SAAgD;;;;;;;AC9dpG,eAAsB,oBAAoBC,SAA+C;AACvF,KAAI;AACF,SAAO,KAAK,+CAA+C;EAE3D,MAAM,cAAc,MAAM,eAAe,QAAQ;AAEjD,OAAK,YAAY,cAAc;AAC7B,UAAO,KAAK,wCAAwC;AACpD,UAAO;IACL,SAAS;IACT,OAAO;IACP,cAAc;GACf;EACF;EAED,MAAM,gBAAgB,MAAM,cAAc,SAAS,YAAY,aAAa;AAE5E,OAAK,cAAc,SAAS;AAC1B,UAAO,MAAM,gCAAmC;IAC9C,OAAO,cAAc;IACrB,cAAc,cAAc;GAC7B,EAAC;AAEF,UAAO;IACL,SAAS;IACT,OAAO,cAAc;IACrB,cAAc,cAAc;GAC7B;EACF;EAED,MAAM,SAAS,cAAc;AAC7B,OAAK,WAAW,OAAO,cAAc;AACnC,UAAO,MAAM,uDAAuD;AACpE,UAAO;IACL,SAAS;IACT,OAAO;IACP,cAAc;GACf;EACF;AAED,SAAO,KAAK,2BAA2B;AAEvC,SAAO;GACL,SAAS;GACT,gBAAgB,OAAO;EACxB;CACF,SAAQ,OAAO;AACd,SAAO,MAAM,gCAAgC,iBAAiB,QAAQ,eAAkB;AACxF,SAAO;GACL,SAAS;GACT,OAAO,iBAAiB,QAAQ,MAAM,UAAU;GAChD,cAAc;EACf;CACF;AACF;;;;AAKD,SAAgB,uBAAuBC,UAA6B;AAClE,QAAO,SAAS,WAAW;AAC5B;;;;AAKD,SAAgB,0BAA0BA,UAAoBC,SAA2B;AACvF,QAAO,WAAW,uBAAuB,SAAS;AACnD"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"server-C96NMFBy.js","names":["DEFAULT_HEADERS: Record<string, string>","getAccessToken: GetAccessTokenFn","getCurrentTenant: GetCurrentTenantFn","logger?: APILogger","factoryOptions: APIConfigFactoryOptions","token: string","request: Request","options: CreateAPIConfigOptions","headers: Record<string, string>","accessToken: string | undefined","request: Request","sessionKey: string","refreshFn: () => Promise<TokenRefreshResult>","logger?: APILogger","init: SpineRequestInit","headersInit?: HeadersInit","init: RequestInit","timeout?: number","value: unknown","response: Response","params: {\n message: string;\n response?: APIResponse<T>;\n config?: ResponseRequestMeta;\n problemDetails?: ProblemDetails;\n shouldLogout?: boolean;\n originalError?: unknown;\n cause?: unknown;\n}","rawResponse: Response","resolvedData?: T","error: unknown","context: FetchResponseContext","retryConfig: Required<Pick<RetryConfig, 'maxRetries' | 'baseDelay' | 'maxDelay'>>","context: FetchErrorContext","options: FetchSetupOptions","client: T","ClientClass: new (configuration?: unknown) => T","createAPIConfig: (request: Request, options?: CreateAPIConfigOptions) => Promise<APIConfig>","ConfigurationClass: new (params?: Record<string, unknown>) => unknown","options: CreateApiClientOptions"],"sources":["../src/api-client/api-config.server.ts","../src/api-client/fetch-client.server.ts"],"sourcesContent":["/**\n * API Configuration Server Module\n *\n * Centralized configuration for all API clients,\n * automatically handling authentication tokens and tenant context.\n */\n\nimport type { APIConfig, CreateAPIConfigOptions, GetAccessTokenFn, GetCurrentTenantFn, APILogger } from './types';\n\n/**\n * Base URL Configuration\n * Single unified base URL for all API operations\n */\nconst DEFAULT_API_BASE_URL = process.env.API_BASE_URL || '';\n\n/**\n * Default Headers\n * Applied to all API requests\n */\nconst DEFAULT_HEADERS: Record<string, string> = {\n Accept: 'application/json, application/problem+json',\n 'Content-Type': 'application/json',\n};\n\n/**\n * API Config Factory Options\n */\nexport interface APIConfigFactoryOptions {\n /**\n * Base URL for API requests\n */\n baseURL?: string;\n /**\n * Default headers applied to all requests\n */\n defaultHeaders?: Record<string, string>;\n /**\n * User agent string\n */\n userAgent?: string;\n /**\n * Default timeout in milliseconds\n */\n timeout?: number;\n /**\n * Header name used for bearer-style auth. Set to null when buildHeaders owns auth.\n * @default 'Authorization'\n */\n authHeaderName?: string | null;\n /**\n * Build an auth header value from the resolved access token.\n * @default token => `Bearer ${token}`\n */\n authHeaderValue?: (accessToken: string) => string;\n /**\n * Header name used for tenant context. Set to null when buildHeaders owns tenancy.\n * @default 'X-Tenant-Id'\n */\n tenantHeaderName?: string | null;\n /**\n * Final app-specific header hook. Returned headers are merged last.\n */\n buildHeaders?: (context: APIHeaderStrategyContext) => Record<string, string>;\n}\n\nexport interface APIHeaderStrategyContext {\n request: Request;\n options: CreateAPIConfigOptions;\n accessToken?: string;\n tenantId: string | null;\n includeAuth: boolean;\n requireTenant: boolean;\n}\n\n/**\n * Create API Config Factory\n *\n * Creates a factory function that generates API configurations\n * with the provided auth and tenant resolvers.\n */\nexport function createAPIConfigFactory(\n getAccessToken: GetAccessTokenFn,\n getCurrentTenant: GetCurrentTenantFn,\n logger?: APILogger,\n factoryOptions: APIConfigFactoryOptions = {}\n) {\n const {\n baseURL = DEFAULT_API_BASE_URL,\n defaultHeaders = DEFAULT_HEADERS,\n userAgent = 'Spine/1.0',\n timeout = 30000,\n authHeaderName = 'Authorization',\n authHeaderValue = (token: string) => `Bearer ${token}`,\n tenantHeaderName = 'X-Tenant-Id',\n buildHeaders,\n } = factoryOptions;\n\n /**\n * Resolve tenant ID from request or options\n */\n async function resolveTenantId(\n request: Request,\n options: CreateAPIConfigOptions\n ): Promise<string | null> {\n // Use explicit tenant ID if provided\n if (options.tenantId) {\n logger?.debug?.('Using explicit tenant ID', { tenantId: options.tenantId });\n return options.tenantId;\n }\n\n // Resolve from current tenant context\n try {\n const tenantId = await getCurrentTenant(request);\n if (tenantId) {\n logger?.debug?.('Resolved tenant from context', { tenantId });\n }\n return tenantId;\n } catch (error) {\n logger?.warn?.('Failed to resolve current tenant', error instanceof Error ? error : undefined);\n return null;\n }\n }\n\n /**\n * Creates unified API configuration for all services.\n *\n * This function automatically:\n * - Adds authentication token using the configured auth header strategy\n * - Injects tenant context using the configured tenant header strategy\n * - Configures base URL from options or environment\n * - Applies default headers\n *\n * @param request - The incoming request object\n * @param options - Configuration options\n * @returns API configuration object\n * @throws Error if required authentication or tenant context is missing\n *\n * @example\n * `\typescript\n * // Standard usage with auth and tenant\n * const config = await createAPIConfig(request);\n * const api = new IdentityApi(config);\n *\n * // Without tenant requirement\n * const config = await createAPIConfig(request, { requireTenant: false });\n *\n * // With explicit tenant override\n * const config = await createAPIConfig(request, { tenantId: 'specific-tenant-id' });\n * `\n */\n async function createAPIConfig(\n request: Request,\n options: CreateAPIConfigOptions = {}\n ): Promise<APIConfig> {\n const {\n requireTenant = true,\n includeAuth = true,\n customHeaders = {},\n } = options;\n\n // Start with default headers\n const headers: Record<string, string> = {\n ...defaultHeaders,\n 'User-Agent': userAgent,\n ...customHeaders,\n };\n\n let accessToken: string | undefined;\n\n // Add authentication if required\n if (includeAuth) {\n const token = await getAccessToken(request);\n if (!token) {\n logger?.error?.('No access token available for API request');\n throw new Error('No access token available');\n }\n\n accessToken = token;\n if (authHeaderName) {\n headers[authHeaderName] = authHeaderValue(token);\n }\n logger?.debug?.('Added authorization header to API config');\n }\n\n // Resolve tenant context\n const tenantId = await resolveTenantId(request, options);\n\n // Add tenant header if required\n if (requireTenant) {\n if (!tenantId) {\n logger?.error?.('Tenant context required but not available');\n throw new Error('Tenant context is required for this operation');\n }\n\n if (tenantHeaderName) {\n headers[tenantHeaderName] = tenantId;\n }\n logger?.debug?.('Added tenant context header', { tenantId });\n }\n\n if (buildHeaders) {\n Object.assign(headers, buildHeaders({\n request,\n options,\n accessToken,\n tenantId,\n includeAuth,\n requireTenant,\n }));\n }\n\n logger?.debug?.('API config created', {\n basePath: baseURL,\n hasAuth: includeAuth,\n hasTenant: !!tenantId,\n requireTenant,\n });\n\n return {\n basePath: baseURL,\n accessToken,\n headers,\n tenantId: tenantId || '',\n baseOptions: {\n timeout,\n },\n };\n }\n\n /**\n * Get the configured API base URL\n */\n function getAPIBaseURL(): string {\n return baseURL;\n }\n\n return {\n createAPIConfig,\n getAPIBaseURL,\n };\n}\n\nexport type APIConfigFactory = ReturnType<typeof createAPIConfigFactory>;\n","/**\n * Fetch-native API client setup for generated OpenAPI clients.\n *\n * This module preserves the shared request behaviors we need around fetch:\n * - request ID enrichment\n * - serialized token refresh on 401\n * - retry with exponential backoff\n * - ProblemDetails-aware error shaping\n *\n * It gives downstream apps a single fetch-native entry point for generated clients.\n */\n\nimport { AsyncLocalStorage } from 'node:async_hooks';\nimport type {\n APIClientError,\n APIConfig,\n APIResponse,\n APILogger,\n CreateAPIConfigOptions,\n EnhancedAPIClient,\n FetchErrorContext,\n FetchMiddleware,\n FetchResponseContext,\n ProblemDetails,\n RetryConfig,\n TokenRefreshFn,\n TokenRefreshResult,\n} from './types';\nimport { isApiClientError } from './types';\nimport { calculateBackoffDelay, fetchWithTimeout, isRetryableError } from './retry-handler';\n\ntype RuntimeApiResponse<T> = {\n raw: Response;\n value(): Promise<T>;\n};\n\ntype GeneratedFetchClient = object;\n\ntype SpineRequestInit = RequestInit & {\n __spineMeta?: {\n requestId?: string;\n retryCount?: number;\n refreshed?: boolean;\n };\n};\n\ntype ResponseRequestMeta = {\n url: string;\n method: string;\n headers: Record<string, string>;\n};\n\nconst refreshPromiseMap = new Map<string, Promise<TokenRefreshResult>>();\nconst responseRequestMeta = new WeakMap<Response, ResponseRequestMeta>();\nconst rawResponseStore = new AsyncLocalStorage<Map<string, RuntimeApiResponse<unknown>>>();\n\nfunction getSessionKeyFromRequest(request: Request): string {\n const cookie = request.headers.get('cookie') || '';\n return `session:${cookie.slice(0, 50)}`;\n}\n\nfunction getOrCreateRefreshPromise(\n sessionKey: string,\n refreshFn: () => Promise<TokenRefreshResult>,\n logger?: APILogger\n): Promise<TokenRefreshResult> {\n const existingPromise = refreshPromiseMap.get(sessionKey);\n if (existingPromise) {\n logger?.info?.('Token refresh already in progress, waiting for existing refresh');\n return existingPromise;\n }\n\n logger?.info?.('Starting new token refresh');\n\n const refreshPromise = refreshFn().finally(() => {\n refreshPromiseMap.delete(sessionKey);\n });\n\n refreshPromiseMap.set(sessionKey, refreshPromise);\n return refreshPromise;\n}\n\nfunction ensureRequestMeta(init: SpineRequestInit): NonNullable<SpineRequestInit['__spineMeta']> {\n if (!init.__spineMeta) {\n init.__spineMeta = {};\n }\n\n return init.__spineMeta;\n}\n\nfunction headersToRecord(headersInit?: HeadersInit): Record<string, string> {\n return Object.fromEntries(new Headers(headersInit).entries());\n}\n\nfunction cloneInit(init: RequestInit): SpineRequestInit {\n const source = init as SpineRequestInit;\n\n return {\n ...source,\n headers: headersToRecord(source.headers),\n __spineMeta: source.__spineMeta ? { ...source.__spineMeta } : undefined,\n };\n}\n\nfunction createTimeoutFetch(timeout?: number): typeof fetch | undefined {\n if (!timeout) {\n return undefined;\n }\n\n return (input, init) => fetchWithTimeout(input, { ...init, timeout });\n}\n\nfunction isProblemDetails(value: unknown): value is ProblemDetails {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'title' in value &&\n 'status' in value &&\n 'detail' in value\n );\n}\n\nasync function parseResponseBody(response: Response): Promise<unknown> {\n const clone = response.clone();\n const contentType = clone.headers.get('content-type') || '';\n\n if (contentType.includes('application/json') || contentType.includes('+json')) {\n try {\n return await clone.json();\n } catch {\n return undefined;\n }\n }\n\n try {\n const text = await clone.text();\n return text || undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction createClientError<T = unknown>(params: {\n message: string;\n response?: APIResponse<T>;\n config?: ResponseRequestMeta;\n problemDetails?: ProblemDetails;\n shouldLogout?: boolean;\n originalError?: unknown;\n cause?: unknown;\n}): APIClientError<T> {\n const error = new Error(params.message) as APIClientError<T>;\n\n error.name = 'APIClientError';\n error.isApiClientError = true;\n error.response = params.response;\n error.config = params.config;\n error.problemDetails = params.problemDetails;\n error.shouldLogout = params.shouldLogout;\n error.originalError = params.originalError;\n error.cause = params.cause;\n\n return error;\n}\n\nasync function toAPIResponse<T>(\n rawResponse: Response,\n resolvedData?: T\n): Promise<APIResponse<T>> {\n const data = resolvedData === undefined ? ((await parseResponseBody(rawResponse)) as T) : resolvedData;\n\n return {\n data,\n status: rawResponse.status,\n statusText: rawResponse.statusText,\n headers: headersToRecord(rawResponse.headers),\n raw: rawResponse,\n };\n}\n\nasync function normalizeClientError(error: unknown): Promise<APIClientError> {\n if (isApiClientError(error)) {\n return error;\n }\n\n if (error && typeof error === 'object' && 'response' in error && (error as { response?: unknown }).response instanceof Response) {\n const response = (error as { response: Response }).response;\n const responseData = await parseResponseBody(response);\n const problemDetails = isProblemDetails(responseData) ? responseData : undefined;\n\n return createClientError({\n message:\n problemDetails?.detail ||\n problemDetails?.title ||\n response.statusText ||\n 'API request failed',\n response: await toAPIResponse(response, responseData),\n config: responseRequestMeta.get(response),\n problemDetails,\n cause: error,\n });\n }\n\n return createClientError({\n message: error instanceof Error ? error.message : 'API request failed',\n cause: error,\n });\n}\n\nfunction isRetryableFetchError(error: unknown): boolean {\n if (isRetryableError(error)) {\n return true;\n }\n\n if (error && typeof error === 'object' && 'cause' in error) {\n return isRetryableError((error as { cause?: unknown }).cause);\n }\n\n return false;\n}\n\nasync function maybeRetryResponse(\n context: FetchResponseContext,\n retryConfig: Required<Pick<RetryConfig, 'maxRetries' | 'baseDelay' | 'maxDelay'>>,\n logger?: APILogger\n): Promise<Response | undefined> {\n const init = cloneInit(context.init);\n const meta = ensureRequestMeta(init);\n\n if (context.response.status === 401 || context.response.status === 409) {\n return undefined;\n }\n\n if (!isRetryableError(context.response)) {\n return undefined;\n }\n\n const retryCount = meta.retryCount ?? 0;\n if (retryCount >= retryConfig.maxRetries) {\n return undefined;\n }\n\n meta.retryCount = retryCount + 1;\n\n const delay = calculateBackoffDelay(meta.retryCount, retryConfig.baseDelay, retryConfig.maxDelay);\n logger?.info?.('Retrying request', {\n url: context.url,\n method: (init.method ?? 'GET').toUpperCase(),\n attempt: meta.retryCount,\n maxRetries: retryConfig.maxRetries,\n delay,\n status: context.response.status,\n });\n\n await new Promise((resolve) => setTimeout(resolve, delay));\n\n return context.fetch(context.url, init);\n}\n\nasync function maybeRetryError(\n context: FetchErrorContext,\n retryConfig: Required<Pick<RetryConfig, 'maxRetries' | 'baseDelay' | 'maxDelay'>>,\n logger?: APILogger\n): Promise<Response | undefined> {\n const init = cloneInit(context.init);\n const meta = ensureRequestMeta(init);\n const retryCount = meta.retryCount ?? 0;\n\n if (!isRetryableFetchError(context.error) || retryCount >= retryConfig.maxRetries) {\n return undefined;\n }\n\n meta.retryCount = retryCount + 1;\n\n const delay = calculateBackoffDelay(meta.retryCount, retryConfig.baseDelay, retryConfig.maxDelay);\n logger?.info?.('Retrying request after fetch failure', {\n url: context.url,\n method: (init.method ?? 'GET').toUpperCase(),\n attempt: meta.retryCount,\n maxRetries: retryConfig.maxRetries,\n delay,\n });\n\n await new Promise((resolve) => setTimeout(resolve, delay));\n\n return context.fetch(context.url, init);\n}\n\nexport interface FetchSetupOptions {\n logger?: APILogger;\n attemptTokenRefresh?: TokenRefreshFn;\n retryConfig?: RetryConfig;\n addRequestId?: boolean;\n}\n\nexport function createFetchMiddleware(\n request: Request,\n options: FetchSetupOptions = {}\n): FetchMiddleware[] {\n const {\n logger,\n attemptTokenRefresh,\n retryConfig = {},\n addRequestId = true,\n } = options;\n\n const normalizedRetryConfig = {\n maxRetries: retryConfig.maxRetries ?? 3,\n baseDelay: retryConfig.baseDelay ?? 1000,\n maxDelay: retryConfig.maxDelay ?? 10000,\n };\n\n return [\n {\n async pre(context) {\n const init = cloneInit(context.init);\n const meta = ensureRequestMeta(init);\n const headers = new Headers(init.headers);\n\n if (addRequestId) {\n const requestId = headers.get('X-Request-Id') || crypto.randomUUID();\n headers.set('X-Request-Id', requestId);\n meta.requestId = requestId;\n }\n\n init.headers = headersToRecord(headers);\n\n logger?.debug?.('API request initiated', {\n method: (init.method ?? 'GET').toUpperCase(),\n url: context.url,\n requestId: meta.requestId,\n });\n\n return {\n url: context.url,\n init,\n };\n },\n\n async post(context) {\n const init = cloneInit(context.init);\n const meta = ensureRequestMeta(init);\n\n responseRequestMeta.set(context.response, {\n url: context.url,\n method: (init.method ?? 'GET').toUpperCase(),\n headers: headersToRecord(init.headers),\n });\n\n if (context.response.status === 401 && attemptTokenRefresh && !meta.refreshed) {\n meta.refreshed = true;\n\n logger?.info?.('Received 401 from backend, attempting token refresh', {\n url: context.url,\n method: (init.method ?? 'GET').toUpperCase(),\n });\n\n const refreshResult = await getOrCreateRefreshPromise(\n getSessionKeyFromRequest(request),\n () => attemptTokenRefresh(request),\n logger\n );\n\n if (refreshResult.success && refreshResult.newAccessToken) {\n const headers = new Headers(init.headers);\n headers.set('Authorization', `Bearer ${refreshResult.newAccessToken}`);\n init.headers = headersToRecord(headers);\n\n logger?.info?.('Token refresh successful, retrying original request');\n\n return context.fetch(context.url, init);\n }\n\n if (refreshResult.shouldLogout) {\n throw createClientError({\n message: 'REFRESH_TOKEN_EXPIRED',\n response: await toAPIResponse(context.response),\n config: responseRequestMeta.get(context.response),\n shouldLogout: true,\n originalError: refreshResult,\n cause: refreshResult,\n });\n }\n }\n\n const retryResponse = await maybeRetryResponse(context, normalizedRetryConfig, logger);\n if (retryResponse) {\n return retryResponse;\n }\n\n if (context.response.status >= 400) {\n const responseData = await parseResponseBody(context.response);\n const problemDetails = isProblemDetails(responseData) ? responseData : undefined;\n\n logger?.error?.('API error response received', undefined, {\n url: context.url,\n method: (init.method ?? 'GET').toUpperCase(),\n status: context.response.status,\n code: problemDetails?.title,\n detail: problemDetails?.detail,\n traceId: problemDetails?.traceId,\n });\n }\n\n return context.response;\n },\n\n async onError(context) {\n const retryResponse = await maybeRetryError(context, normalizedRetryConfig, logger);\n if (retryResponse) {\n return retryResponse;\n }\n\n return undefined;\n },\n },\n ];\n}\n\nexport interface CreateApiClientOptions extends FetchSetupOptions, CreateAPIConfigOptions {\n credentials?: RequestCredentials;\n}\n\nfunction instrumentRawMethods<T extends GeneratedFetchClient>(client: T): void {\n if ((client as { __spineRawMethodsInstrumented?: boolean }).__spineRawMethodsInstrumented) {\n return;\n }\n\n const prototype = Object.getPrototypeOf(client) as Record<string, unknown>;\n\n for (const key of Object.getOwnPropertyNames(prototype)) {\n if (!key.endsWith('Raw')) {\n continue;\n }\n\n const original = prototype[key];\n if (typeof original !== 'function') {\n continue;\n }\n\n Object.defineProperty(client, key, {\n configurable: true,\n enumerable: false,\n writable: true,\n value: async (...args: unknown[]) => {\n const result = await (original as (...innerArgs: unknown[]) => Promise<RuntimeApiResponse<unknown>>).apply(client, args);\n rawResponseStore.getStore()?.set(key, result);\n return result;\n },\n });\n }\n\n (client as { __spineRawMethodsInstrumented?: boolean }).__spineRawMethodsInstrumented = true;\n}\n\nfunction wrapClient<T extends GeneratedFetchClient>(client: T): EnhancedAPIClient<T> {\n instrumentRawMethods(client);\n\n const methodCache = new Map<PropertyKey, unknown>();\n\n return new Proxy(client, {\n get(target, prop, receiver) {\n const value = Reflect.get(target, prop, receiver);\n\n if (typeof prop !== 'string' || typeof value !== 'function' || prop.endsWith('Raw')) {\n return typeof value === 'function' ? value.bind(target) : value;\n }\n\n if (methodCache.has(prop)) {\n return methodCache.get(prop);\n }\n\n const wrapped = async (...args: unknown[]) =>\n rawResponseStore.run(new Map(), async () => {\n try {\n const resolvedData = await (value as (...innerArgs: unknown[]) => Promise<unknown>).apply(target, args);\n const rawResponse = rawResponseStore.getStore()?.get(`${prop}Raw`);\n\n if (rawResponse) {\n return toAPIResponse(rawResponse.raw, resolvedData);\n }\n\n return {\n data: resolvedData,\n status: 200,\n statusText: 'OK',\n headers: {},\n };\n } catch (error) {\n throw await normalizeClientError(error);\n }\n });\n\n methodCache.set(prop, wrapped);\n return wrapped;\n },\n }) as EnhancedAPIClient<T>;\n}\n\nexport async function createApiClient<T extends GeneratedFetchClient>(\n request: Request,\n ClientClass: new (configuration?: unknown) => T,\n createAPIConfig: (request: Request, options?: CreateAPIConfigOptions) => Promise<APIConfig>,\n ConfigurationClass: new (params?: Record<string, unknown>) => unknown,\n options: CreateApiClientOptions = {}\n): Promise<{ client: EnhancedAPIClient<T>; tenantId: string }> {\n const {\n requireTenant,\n includeAuth,\n tenantId,\n customHeaders,\n credentials,\n ...fetchOptions\n } = options;\n\n const config = await createAPIConfig(request, {\n requireTenant,\n includeAuth,\n tenantId,\n customHeaders,\n });\n\n const configuration = new ConfigurationClass({\n basePath: config.basePath,\n accessToken: config.accessToken,\n headers: config.headers,\n credentials: credentials ?? config.credentials,\n middleware: createFetchMiddleware(request, fetchOptions),\n fetchApi: createTimeoutFetch(config.baseOptions?.timeout),\n });\n\n const client = new ClientClass(configuration);\n\n return {\n client: wrapClient(client),\n tenantId: config.tenantId,\n };\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"server-Ca-3DJf2.js","names":["DEFAULT_TENANT_COOKIE_CONFIG: Required<TenantCookieConfig>","tenantCookieConfig: Required<TenantCookieConfig>","cookieHeader: string","name: string","request: Request","tenantId: string","config: TenantCookieConfig","identityContextFetcher: IdentityContextFetcher | null","resolveInitialTenant: NonNullable<TenantResolutionConfig['resolveInitialTenant']>","resolveAvailableTenants: NonNullable<TenantResolutionConfig['resolveAvailableTenants']>","fetcher: IdentityContextFetcher","config: TenantResolutionConfig","request: Request","tenantId: string"],"sources":["../src/tenant/tenant-cookie.server.ts","../src/tenant/tenant.server.ts"],"sourcesContent":["/**\n * Tenant Cookie Server Module\n * \n * Cookie-based tenant storage.\n */\n\nexport type TenantCookieSameSite = 'Strict' | 'Lax' | 'None';\n\nexport interface TenantCookieConfig {\n name?: string;\n maxAge?: number;\n path?: string;\n secure?: boolean;\n httpOnly?: boolean;\n sameSite?: TenantCookieSameSite;\n}\n\nconst DEFAULT_TENANT_COOKIE_CONFIG: Required<TenantCookieConfig> = {\n name: '__spine_tenant',\n maxAge: 60 * 60 * 24 * 365,\n path: '/',\n secure: process.env.NODE_ENV === 'production',\n httpOnly: false,\n sameSite: 'Lax',\n};\n\nlet tenantCookieConfig: Required<TenantCookieConfig> = {\n ...DEFAULT_TENANT_COOKIE_CONFIG,\n};\n\nfunction getTenantCookieConfig(): Required<TenantCookieConfig> {\n return tenantCookieConfig;\n}\n\nfunction readCookieValue(cookieHeader: string, name: string): string | null {\n for (const segment of cookieHeader.split(';')) {\n const [rawName, ...rawValue] = segment.split('=');\n if (rawName?.trim() !== name || rawValue.length === 0) {\n continue;\n }\n\n return decodeURIComponent(rawValue.join('='));\n }\n\n return null;\n}\n\n/**\n * Get active tenant from cookie\n */\nexport async function getActiveTenant(request: Request): Promise<string | null> {\n const config = getTenantCookieConfig();\n const cookies = request.headers.get('Cookie');\n if (!cookies) return null;\n\n return readCookieValue(cookies, config.name);\n}\n\n/**\n * Set active tenant cookie value\n */\nexport async function setActiveTenant(tenantId: string): Promise<string> {\n const config = getTenantCookieConfig();\n const secure = config.secure ? '; Secure' : '';\n const httpOnly = config.httpOnly ? '; HttpOnly' : '';\n return `${config.name}=${encodeURIComponent(tenantId)}; Path=${config.path}${httpOnly}; SameSite=${config.sameSite}; Max-Age=${config.maxAge}${secure}`;\n}\n\n/**\n * Clear active tenant cookie value\n */\nexport async function clearActiveTenant(): Promise<string> {\n const config = getTenantCookieConfig();\n const secure = config.secure ? '; Secure' : '';\n const httpOnly = config.httpOnly ? '; HttpOnly' : '';\n return `${config.name}=; Path=${config.path}${httpOnly}; SameSite=${config.sameSite}; Max-Age=0${secure}`;\n}\n\n/**\n * Configure tenant cookie name\n */\nexport function configureTenantCookie(config: TenantCookieConfig): void {\n tenantCookieConfig = {\n ...tenantCookieConfig,\n ...config,\n };\n}\n\n/**\n * Reset tenant cookie config back to defaults.\n */\nexport function resetTenantCookieConfig(): void {\n tenantCookieConfig = {\n ...DEFAULT_TENANT_COOKIE_CONFIG,\n };\n}\n","/**\n * Tenant Server Module\n * \n * Server-side tenant management.\n */\n\nimport { getAuthSession } from '../auth/redis-session-storage.server';\nimport { getActiveTenant, setActiveTenant, clearActiveTenant } from './tenant-cookie.server';\nimport type { SessionData } from '../auth/types';\n\n/**\n * Identity context fetcher type (to avoid circular dependency)\n */\nexport type IdentityContextFetcher = (request: Request) => Promise<{\n memberships?: Array<{ tenantId?: string; id?: string; [key: string]: unknown }>;\n [key: string]: unknown;\n} | null>;\n\nexport interface TenantResolutionContext {\n request: Request;\n session: SessionData;\n identityContext: Awaited<ReturnType<IdentityContextFetcher>>;\n}\n\nexport interface TenantResolutionConfig {\n identityContextFetcher?: IdentityContextFetcher;\n resolveInitialTenant?: (context: TenantResolutionContext) => Promise<string | null> | string | null;\n resolveAvailableTenants?: (context: TenantResolutionContext) => Promise<string[]> | string[];\n}\n\nlet identityContextFetcher: IdentityContextFetcher | null = null;\nlet resolveInitialTenant: NonNullable<TenantResolutionConfig['resolveInitialTenant']> =\n ({ identityContext }) => {\n const membership = identityContext?.memberships?.[0];\n return membership?.tenantId ?? membership?.id ?? null;\n };\nlet resolveAvailableTenants: NonNullable<TenantResolutionConfig['resolveAvailableTenants']> =\n ({ identityContext }) =>\n identityContext?.memberships\n ?.map((membership) => membership.tenantId ?? membership.id)\n .filter((tenantId): tenantId is string => Boolean(tenantId)) ?? [];\n\n/**\n * Configure identity context fetcher\n */\nexport function configureIdentityContextFetcher(fetcher: IdentityContextFetcher): void {\n identityContextFetcher = fetcher;\n}\n\n/**\n * Configure tenant resolution for custom backend identity contracts.\n */\nexport function configureTenantResolution(config: TenantResolutionConfig): void {\n if (config.identityContextFetcher) {\n identityContextFetcher = config.identityContextFetcher;\n }\n if (config.resolveInitialTenant) {\n resolveInitialTenant = config.resolveInitialTenant;\n }\n if (config.resolveAvailableTenants) {\n resolveAvailableTenants = config.resolveAvailableTenants;\n }\n}\n\nexport function resetTenantResolutionConfig(): void {\n identityContextFetcher = null;\n resolveInitialTenant = ({ identityContext }) => {\n const membership = identityContext?.memberships?.[0];\n return membership?.tenantId ?? membership?.id ?? null;\n };\n resolveAvailableTenants = ({ identityContext }) =>\n identityContext?.memberships\n ?.map((membership) => membership.tenantId ?? membership.id)\n .filter((tenantId): tenantId is string => Boolean(tenantId)) ?? [];\n}\n\n/**\n * Get current active tenant from cookie\n */\nexport async function getCurrentTenant(request: Request): Promise<string | null> {\n try {\n const tenantFromCookie = await getActiveTenant(request);\n if (tenantFromCookie) {\n return tenantFromCookie;\n }\n return null;\n } catch (error) {\n console.error('Error getting current tenant:', error);\n return null;\n }\n}\n\n/**\n * Get tenant from identity context\n */\nexport async function getTenantFromIdentityContext(request: Request): Promise<string | null> {\n try {\n const sessionData = await getAuthSession(request);\n if (!sessionData?.user?.sub) {\n return null;\n }\n\n if (!identityContextFetcher) {\n console.warn('Identity context fetcher not configured');\n return null;\n }\n\n const context = await identityContextFetcher(request);\n return resolveInitialTenant({ request, session: sessionData, identityContext: context });\n } catch (error) {\n console.error('Error getting tenant from identity context:', error);\n return null;\n }\n}\n\n/**\n * Get available tenants from identity context\n */\nexport async function getAvailableTenants(request: Request): Promise<string[]> {\n try {\n const sessionData = await getAuthSession(request);\n if (!sessionData?.user?.sub) {\n return [];\n }\n\n if (!identityContextFetcher) {\n console.warn('Identity context fetcher not configured');\n return [];\n }\n\n const context = await identityContextFetcher(request);\n return resolveAvailableTenants({ request, session: sessionData, identityContext: context });\n } catch (error) {\n console.error('Error getting available tenants:', error);\n return [];\n }\n}\n\n/**\n * Set current active tenant\n */\nexport async function setCurrentTenant(\n request: Request,\n tenantId: string\n): Promise<{ headers: Headers; success: boolean; error?: string }> {\n try {\n const sessionData = await getAuthSession(request);\n\n if (!sessionData.user) {\n return { headers: new Headers(), success: false, error: 'No active session' };\n }\n\n const cookieValue = await setActiveTenant(tenantId);\n const headers = new Headers();\n headers.append('Set-Cookie', cookieValue);\n\n return { headers, success: true };\n } catch (error) {\n console.error('Failed to set current tenant:', error);\n return {\n headers: new Headers(),\n success: false,\n error: error instanceof Error ? error.message : 'Unknown error',\n };\n }\n}\n\n/**\n * Initialize tenant on first login\n */\nexport async function initializeTenant(\n request: Request\n): Promise<{ headers: Headers; tenantId: string } | null> {\n try {\n const sessionData = await getAuthSession(request);\n if (!sessionData?.user) {\n return null;\n }\n\n // Check if tenant is already set in cookie\n const currentTenant = await getCurrentTenant(request);\n if (currentTenant) {\n return null;\n }\n\n // Get tenant from identity context\n const tenantId = await getTenantFromIdentityContext(request);\n\n if (tenantId) {\n const cookieValue = await setActiveTenant(tenantId);\n const headers = new Headers();\n headers.append('Set-Cookie', cookieValue);\n return { headers, tenantId };\n }\n\n return null;\n } catch (error) {\n console.error('Failed to initialize tenant:', error);\n return null;\n }\n}\n\n/**\n * Clear current tenant\n */\nexport async function clearCurrentTenant(): Promise<Headers> {\n const cookieValue = await clearActiveTenant();\n const headers = new Headers();\n headers.append('Set-Cookie', cookieValue);\n return headers;\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"server-Dxsww9v9.js","names":["identityAPIFetcher: IdentityAPIFetcher | null","fetcher: IdentityAPIFetcher","request: Request","error: unknown","userId: string","userId?: string","permissionFetcher: PermissionFetcher | null","fetcher: PermissionFetcher","tenantId: string","context: IdentityContextData","options: ContextToUserInfoOptions","permissions: string[]","newVersion: number"],"sources":["../src/identity/identity-context.server.ts"],"sourcesContent":["/**\n * Identity Context Server Module\n * \n * Server-side identity context management.\n */\n\nimport type { AddressData, IdentityContextData, TenantMembership } from './types';\nimport { isApiClientError } from '../api-client/types';\nimport { logger } from '../logging';\n\n/**\n * Identity API fetcher type (to avoid coupling with specific API client)\n */\nexport type IdentityAPIFetcher = (request: Request) => Promise<IdentityContextData>;\n\nlet identityAPIFetcher: IdentityAPIFetcher | null = null;\n\n/**\n * Configure identity API fetcher\n */\nexport function configureIdentityAPIFetcher(fetcher: IdentityAPIFetcher): void {\n identityAPIFetcher = fetcher;\n}\n\n// In-memory cache for identity context\nconst contextCache = new Map<\n string,\n {\n context: IdentityContextData;\n timestamp: number;\n version: number;\n }\n>();\n\nconst CONTEXT_CACHE_TTL = 5 * 60 * 1000; // 5 minutes\n\n/**\n * Fetch identity context from backend\n */\nexport async function fetchIdentityContext(request: Request): Promise<IdentityContextData> {\n if (!identityAPIFetcher) {\n throw new Error('Identity API fetcher not configured. Call configureIdentityAPIFetcher first.');\n }\n\n try {\n logger.info('Fetching identity context');\n const context = await identityAPIFetcher(request);\n \n logger.debug('Identity context fetched', {\n hasAnyMembership: context.hasAnyMembership,\n hasEntitlement: context.hasEntitlement ?? context.hasSubscription,\n isOnboarded: context.isOnboarded,\n membershipCount: context.memberships?.length || 0,\n });\n\n return context;\n } catch (error: unknown) {\n logger.error('Failed to fetch identity context', error instanceof Error ? error : undefined);\n\n // Check if this is a CurrentUserMissing error\n if (isApiClientError(error) && error.response?.status === 404 && error.problemDetails?.title === 'CurrentUserMissing') {\n logger.error('CurrentUserMissing error detected - session is invalid');\n const invalidSessionError = new Error('INVALID_SESSION');\n (invalidSessionError as Error & { isInvalidSession: boolean }).isInvalidSession = true;\n throw invalidSessionError;\n }\n\n throw error;\n }\n}\n\n/**\n * Get cached identity context or fetch fresh if needed\n */\nexport async function getIdentityContext(\n request: Request,\n userId: string,\n forceRefresh = false\n): Promise<IdentityContextData> {\n const cacheKey = userId;\n const cached = contextCache.get(cacheKey);\n const now = Date.now();\n\n // Return cached if valid and not forcing refresh\n if (!forceRefresh && cached && now - cached.timestamp < CONTEXT_CACHE_TTL) {\n logger.debug('Using cached identity context for user', { userId });\n return cached.context;\n }\n\n // Fetch fresh context\n const context = await fetchIdentityContext(request);\n\n // Cache the result\n contextCache.set(cacheKey, {\n context,\n timestamp: now,\n version: context.contextVersion ?? 0,\n });\n\n logger.debug('Cached fresh identity context for user', { userId });\n return context;\n}\n\n/**\n * Clear identity context cache for user\n */\nexport function clearIdentityContextCache(userId?: string): void {\n if (userId) {\n contextCache.delete(userId);\n logger.debug('Cleared identity context cache for user', { userId });\n } else {\n contextCache.clear();\n logger.debug('Cleared all identity context cache');\n }\n}\n\n/**\n * Permission fetcher type\n */\nexport type PermissionFetcher = (request: Request, tenantId: string) => Promise<string[]>;\n\nlet permissionFetcher: PermissionFetcher | null = null;\n\n/**\n * Configure permission fetcher\n */\nexport function configurePermissionFetcher(fetcher: PermissionFetcher): void {\n permissionFetcher = fetcher;\n}\n\n/**\n * Fetch user permissions for a specific tenant\n */\nexport async function fetchUserPermissions(request: Request, tenantId: string): Promise<string[]> {\n if (!tenantId) {\n logger.warn('No tenant ID provided, skipping permission fetch');\n return [];\n }\n\n if (!permissionFetcher) {\n throw new Error('Permission fetcher not configured. Call configurePermissionFetcher first.');\n }\n\n try {\n return await permissionFetcher(request, tenantId);\n } catch (error) {\n logger.error('Failed to fetch user permissions', error instanceof Error ? error : undefined);\n return [];\n }\n}\n\nexport interface UserContextInfo {\n tenants: string[];\n memberships: TenantMembership[];\n currentTenant?: string;\n isOnboarded: boolean;\n permissions: string[];\n hasEntitlement: boolean;\n /** @deprecated Use hasEntitlement or app-specific fields instead. */\n hasSubscription: boolean;\n userId?: string;\n email?: string;\n firstName?: string | null;\n lastName?: string | null;\n displayName?: string | null;\n profileImageUrl?: string | null;\n phoneNumber?: string | null;\n timeZone?: string;\n addresses?: AddressData[];\n hasOwnerMembership: boolean;\n}\n\nexport interface ContextToUserInfoOptions {\n currentTenant?: string | null;\n request?: Request;\n}\n\n/**\n * Convert raw identity context into a tenant-aware user info shape.\n */\nexport async function contextToUserInfo(\n context: IdentityContextData,\n options: ContextToUserInfoOptions = {}\n): Promise<UserContextInfo> {\n const memberships = context.memberships || [];\n const tenants = memberships.map((membership) => membership.tenantId);\n const resolvedCurrentTenant =\n options.currentTenant && tenants.includes(options.currentTenant)\n ? options.currentTenant\n : tenants[0] || undefined;\n\n let permissions: string[] = [];\n if (resolvedCurrentTenant && options.request) {\n permissions = await fetchUserPermissions(options.request, resolvedCurrentTenant);\n }\n\n return {\n tenants,\n memberships,\n currentTenant: resolvedCurrentTenant,\n isOnboarded: context.isOnboarded ?? false,\n permissions,\n hasEntitlement: context.hasEntitlement ?? context.hasSubscription ?? false,\n hasSubscription: context.hasSubscription ?? context.hasEntitlement ?? false,\n userId: context.userId,\n email: context.email,\n firstName: context.firstName,\n lastName: context.lastName,\n displayName: context.displayName,\n profileImageUrl: context.profileImageUrl,\n phoneNumber: context.phoneNumber,\n timeZone: context.timeZone,\n addresses: context.addresses,\n hasOwnerMembership: context.hasOwnerMembership ?? false,\n };\n}\n\n/**\n * Check if a cached context version differs from the latest version.\n */\nexport function hasContextVersionChanged(userId: string, newVersion: number): boolean {\n const cached = contextCache.get(userId);\n return !cached || cached.version !== newVersion;\n}\n\n/**\n * Mark a cached identity context as stale after a version change.\n */\nexport function updateContextVersion(userId: string, newVersion: number): void {\n const cached = contextCache.get(userId);\n if (!cached) return;\n\n cached.version = newVersion;\n cached.timestamp = 0;\n logger.debug('Marked identity context as stale for user', {\n userId,\n version: newVersion,\n });\n}\n"],"mappings":"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"}
@@ -1,78 +0,0 @@
1
- import { PermissionChecker } from "./permission-service-CvGpwI0d.js";
2
-
3
- //#region src/permissions/route-protection.server.ts
4
- const DEFAULT_UNAUTHORIZED_RESPONSE = () => new Response("Unauthorized", {
5
- status: 401,
6
- statusText: "You must be logged in to access this resource"
7
- });
8
- const DEFAULT_FORBIDDEN_RESPONSE = (details) => {
9
- const message = process.env.NODE_ENV === "development" ? `Missing permissions: ${details.missingPermissions?.join(", ")}` : "You don't have permission to access this resource";
10
- return new Response(message, {
11
- status: 403,
12
- statusText: "Forbidden"
13
- });
14
- };
15
- let permissionRouteProtectionConfig = {};
16
- function getPermissionRouteProtectionConfig() {
17
- if (!permissionRouteProtectionConfig.getSession) throw new Error("Permission route protection session getter not configured. Call configurePermissionRouteProtection first.");
18
- if (!permissionRouteProtectionConfig.resolveContext) throw new Error("Permission route protection context resolver not configured. Call configurePermissionRouteProtection first.");
19
- return {
20
- getSession: permissionRouteProtectionConfig.getSession,
21
- resolveContext: permissionRouteProtectionConfig.resolveContext,
22
- createUnauthorizedResponse: permissionRouteProtectionConfig.createUnauthorizedResponse || DEFAULT_UNAUTHORIZED_RESPONSE,
23
- createForbiddenResponse: permissionRouteProtectionConfig.createForbiddenResponse || DEFAULT_FORBIDDEN_RESPONSE
24
- };
25
- }
26
- function configurePermissionRouteProtection(config) {
27
- permissionRouteProtectionConfig = {
28
- ...permissionRouteProtectionConfig,
29
- ...config
30
- };
31
- }
32
- function resetPermissionRouteProtection() {
33
- permissionRouteProtectionConfig = {};
34
- }
35
- async function getPermissionRouteState(request) {
36
- const config = getPermissionRouteProtectionConfig();
37
- const session = await config.getSession(request);
38
- const context = await config.resolveContext(request, session);
39
- return {
40
- config,
41
- session,
42
- context
43
- };
44
- }
45
- async function requirePermission(request, permission, options = {}) {
46
- const { config, session, context } = await getPermissionRouteState(request);
47
- if (!session.user || !session.accessToken) throw config.createUnauthorizedResponse(request, session);
48
- const checker = new PermissionChecker(context.permissions || []);
49
- const permissionsToCheck = Array.isArray(permission) ? permission : [permission];
50
- const result = checker.hasPermissions(permissionsToCheck, options);
51
- if (!result.hasPermission) throw config.createForbiddenResponse({
52
- request,
53
- permission,
54
- permissionsToCheck,
55
- missingPermissions: result.missingPermissions,
56
- options,
57
- session,
58
- context
59
- });
60
- }
61
- async function checkPermission(request, permission, options = {}) {
62
- try {
63
- await requirePermission(request, permission, options);
64
- return true;
65
- } catch {
66
- return false;
67
- }
68
- }
69
- function withPermission(permission, loader, options) {
70
- return async (args) => {
71
- await requirePermission(args.request, permission, options);
72
- return loader(args);
73
- };
74
- }
75
-
76
- //#endregion
77
- export { checkPermission, configurePermissionRouteProtection, requirePermission, resetPermissionRouteProtection, withPermission };
78
- //# sourceMappingURL=server-hGrElJMG.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"server-hGrElJMG.js","names":["details: ForbiddenPermissionDetails","permissionRouteProtectionConfig: PermissionRouteProtectionConfig","config: PermissionRouteProtectionConfig","request: Request","permission: PermissionCode | PermissionCode[]","options: PermissionCheckOptions","loader: (args: any) => Promise<TLoaderData>","options?: PermissionCheckOptions","args: any"],"sources":["../src/permissions/route-protection.server.ts"],"sourcesContent":["import { PermissionChecker } from './permission-service';\nimport type { PermissionCheckOptions, PermissionCode } from './types';\n\nexport interface PermissionRouteSession {\n user?: {\n sub?: string;\n email?: string | null;\n } | null;\n accessToken?: string | null;\n}\n\nexport interface PermissionRouteContext {\n permissions: PermissionCode[];\n currentTenant?: string | null;\n}\n\nexport interface ForbiddenPermissionDetails {\n request: Request;\n permission: PermissionCode | PermissionCode[];\n permissionsToCheck: PermissionCode[];\n missingPermissions?: PermissionCode[];\n options: PermissionCheckOptions;\n session: PermissionRouteSession;\n context: PermissionRouteContext;\n}\n\nexport interface PermissionRouteProtectionConfig {\n getSession?: (request: Request) => Promise<PermissionRouteSession>;\n resolveContext?: (\n request: Request,\n session: PermissionRouteSession\n ) => Promise<PermissionRouteContext>;\n createUnauthorizedResponse?: (\n request: Request,\n session: PermissionRouteSession\n ) => Response;\n createForbiddenResponse?: (details: ForbiddenPermissionDetails) => Response;\n}\n\nconst DEFAULT_UNAUTHORIZED_RESPONSE = () =>\n new Response('Unauthorized', {\n status: 401,\n statusText: 'You must be logged in to access this resource',\n });\n\nconst DEFAULT_FORBIDDEN_RESPONSE = (details: ForbiddenPermissionDetails) => {\n const message =\n process.env.NODE_ENV === 'development'\n ? `Missing permissions: ${details.missingPermissions?.join(', ')}`\n : \"You don't have permission to access this resource\";\n\n return new Response(message, {\n status: 403,\n statusText: 'Forbidden',\n });\n};\n\nlet permissionRouteProtectionConfig: PermissionRouteProtectionConfig = {};\n\nfunction getPermissionRouteProtectionConfig(): Required<PermissionRouteProtectionConfig> {\n if (!permissionRouteProtectionConfig.getSession) {\n throw new Error(\n 'Permission route protection session getter not configured. Call configurePermissionRouteProtection first.'\n );\n }\n\n if (!permissionRouteProtectionConfig.resolveContext) {\n throw new Error(\n 'Permission route protection context resolver not configured. Call configurePermissionRouteProtection first.'\n );\n }\n\n return {\n getSession: permissionRouteProtectionConfig.getSession,\n resolveContext: permissionRouteProtectionConfig.resolveContext,\n createUnauthorizedResponse:\n permissionRouteProtectionConfig.createUnauthorizedResponse || DEFAULT_UNAUTHORIZED_RESPONSE,\n createForbiddenResponse:\n permissionRouteProtectionConfig.createForbiddenResponse || DEFAULT_FORBIDDEN_RESPONSE,\n };\n}\n\nexport function configurePermissionRouteProtection(\n config: PermissionRouteProtectionConfig\n): void {\n permissionRouteProtectionConfig = {\n ...permissionRouteProtectionConfig,\n ...config,\n };\n}\n\nexport function resetPermissionRouteProtection(): void {\n permissionRouteProtectionConfig = {};\n}\n\nasync function getPermissionRouteState(request: Request): Promise<{\n config: Required<PermissionRouteProtectionConfig>;\n session: PermissionRouteSession;\n context: PermissionRouteContext;\n}> {\n const config = getPermissionRouteProtectionConfig();\n const session = await config.getSession(request);\n const context = await config.resolveContext(request, session);\n\n return {\n config,\n session,\n context,\n };\n}\n\nexport async function requirePermission(\n request: Request,\n permission: PermissionCode | PermissionCode[],\n options: PermissionCheckOptions = {}\n): Promise<void> {\n const { config, session, context } = await getPermissionRouteState(request);\n\n if (!session.user || !session.accessToken) {\n throw config.createUnauthorizedResponse(request, session);\n }\n\n const checker = new PermissionChecker(context.permissions || []);\n const permissionsToCheck = Array.isArray(permission) ? permission : [permission];\n const result = checker.hasPermissions(permissionsToCheck, options);\n\n if (!result.hasPermission) {\n throw config.createForbiddenResponse({\n request,\n permission,\n permissionsToCheck,\n missingPermissions: result.missingPermissions,\n options,\n session,\n context,\n });\n }\n}\n\nexport async function checkPermission(\n request: Request,\n permission: PermissionCode | PermissionCode[],\n options: PermissionCheckOptions = {}\n): Promise<boolean> {\n try {\n await requirePermission(request, permission, options);\n return true;\n } catch {\n return false;\n }\n}\n\nexport function withPermission<TLoaderData>(\n permission: PermissionCode | PermissionCode[],\n loader: (args: any) => Promise<TLoaderData>,\n options?: PermissionCheckOptions\n) {\n return async (args: any): Promise<TLoaderData> => {\n await requirePermission(args.request, permission, options);\n return loader(args);\n };\n}\n"],"mappings":";;;AAuCA,MAAM,gCAAgC,MACpC,IAAI,SAAS,gBAAgB;CAC3B,QAAQ;CACR,YAAY;AACb;AAEH,MAAM,6BAA6B,CAACA,YAAwC;CAC1E,MAAM,UACJ,QAAQ,IAAI,aAAa,iBACpB,uBAAuB,QAAQ,oBAAoB,KAAK,KAAK,CAAC,IAC/D;AAEN,QAAO,IAAI,SAAS,SAAS;EAC3B,QAAQ;EACR,YAAY;CACb;AACF;AAED,IAAIC,kCAAmE,CAAE;AAEzE,SAAS,qCAAgF;AACvF,MAAK,gCAAgC,WACnC,OAAM,IAAI,MACR;AAIJ,MAAK,gCAAgC,eACnC,OAAM,IAAI,MACR;AAIJ,QAAO;EACL,YAAY,gCAAgC;EAC5C,gBAAgB,gCAAgC;EAChD,4BACE,gCAAgC,8BAA8B;EAChE,yBACE,gCAAgC,2BAA2B;CAC9D;AACF;AAED,SAAgB,mCACdC,QACM;AACN,mCAAkC;EAChC,GAAG;EACH,GAAG;CACJ;AACF;AAED,SAAgB,iCAAuC;AACrD,mCAAkC,CAAE;AACrC;AAED,eAAe,wBAAwBC,SAIpC;CACD,MAAM,SAAS,oCAAoC;CACnD,MAAM,UAAU,MAAM,OAAO,WAAW,QAAQ;CAChD,MAAM,UAAU,MAAM,OAAO,eAAe,SAAS,QAAQ;AAE7D,QAAO;EACL;EACA;EACA;CACD;AACF;AAED,eAAsB,kBACpBA,SACAC,YACAC,UAAkC,CAAE,GACrB;CACf,MAAM,EAAE,QAAQ,SAAS,SAAS,GAAG,MAAM,wBAAwB,QAAQ;AAE3E,MAAK,QAAQ,SAAS,QAAQ,YAC5B,OAAM,OAAO,2BAA2B,SAAS,QAAQ;CAG3D,MAAM,UAAU,IAAI,kBAAkB,QAAQ,eAAe,CAAE;CAC/D,MAAM,qBAAqB,MAAM,QAAQ,WAAW,GAAG,aAAa,CAAC,UAAW;CAChF,MAAM,SAAS,QAAQ,eAAe,oBAAoB,QAAQ;AAElE,MAAK,OAAO,cACV,OAAM,OAAO,wBAAwB;EACnC;EACA;EACA;EACA,oBAAoB,OAAO;EAC3B;EACA;EACA;CACD,EAAC;AAEL;AAED,eAAsB,gBACpBF,SACAC,YACAC,UAAkC,CAAE,GAClB;AAClB,KAAI;AACF,QAAM,kBAAkB,SAAS,YAAY,QAAQ;AACrD,SAAO;CACR,QAAO;AACN,SAAO;CACR;AACF;AAED,SAAgB,eACdD,YACAE,QACAC,SACA;AACA,QAAO,OAAOC,SAAoC;AAChD,QAAM,kBAAkB,KAAK,SAAS,YAAY,QAAQ;AAC1D,SAAO,OAAO,KAAK;CACpB;AACF"}
package/dist/server.js DELETED
@@ -1,20 +0,0 @@
1
- import { LOG_LEVEL_PRIORITY, LogLevel, Logger, createLogger, defaultLoggerConfig, logger } from "./logging-DviiAkLx.js";
2
- import { PermissionChecker } from "./permission-service-CvGpwI0d.js";
3
- import { PERMISSION_MODULES, PermissionInitializer, ProtectedAction, ProtectedButton, ProtectedLink, ProtectedSection, getPermissionChecker, getPermissions, initializePermissions, useAllPermissions, useHasModulePermission, useHasPermission, useHasPermissions, useModulePermission, usePermission, usePermissionChecker, usePermissionStore, usePermissions } from "./permissions-_n1hIG7Q.js";
4
- import { evaluateAccessRequirement } from "./access-CGrsDMwu.js";
5
- import { TenantProvider, configureTenantClient, configureTenantEndpoints, getTenantCookieName, initializeTenantStore, normalizeTenantDataPayload, normalizeTenantSwitchPayload, resetTenantClientConfig, useCurrentOrganization, useCurrentTenant, useCurrentTenantData, useOrganization, useTenant, useTenantData, useTenantStore, useTenantSwitcher } from "./tenant-DfLemb6n.js";
6
- import { IdentityContextProvider, configureIdentityEndpoints, configureIdentityStore, initialIdentityContext, normalizeIdentityContextPayload, normalizeIdentityPermissionsPayload, resetIdentityStoreConfig, useHasEntitlement, useHasSubscription, useIdentityContext, useIdentityStore, useIsOnboarded, useUserPermissions, useUserTenants } from "./identity-C5un-xAs.js";
7
- import { isApiClientError } from "./types-BaaE6wjY.js";
8
- import { ApiError, ErrorCodes, ErrorHandler, calculateBackoffDelay, createRetryFetch, fetchWithTimeout, handleApiError, isRetryableError, withRetry } from "./api-client-CgZUMukO.js";
9
- import { IdentitySignalRClient, SignalRClient, cleanupSignalR, createIdentitySignalRClient, getIdentitySignalRClient, initializeSignalR } from "./signalr-k-70VHKo.js";
10
- import { cachePresets, createQueryClient, createQueryKeyFactory, invalidationHelpers } from "./query-client-Bh3TyB8W.js";
11
- import "./src-B9rDUgSb.js";
12
- import { AUTH_ERROR_COOKIE_PREFIX, cleanupExpiredOAuthStates, closeRedisConnection, createAuthSession, createOAuthState, deleteOAuthState, destroyAuthSession, destroyAuthSessionById, destroyAuthSessionsByIdentitySession, destroyAuthSessionsBySessionState, destroyAuthSessionsBySid, destroyAuthSessionsForUser, getAuthSession, getAuthSessionById, getCurrentAuthSessionId, getOAuthState, isSessionValid, listAuthSessionDataForUser, listAuthSessionsForUser, listCurrentUserAuthSessions, requireAuthSession, updateAuthSession } from "./redis-session-storage.server-v2VcpFsQ.js";
13
- import { clearAuthErrorHeaders, clearAuthServerCache, configureAuthClaimMapping, extractUserInfo, getAccessToken, getAuthError, getUser, handleBackChannelLogout, handleCallback, handleFrontChannelLogout, hasAuthError, login, logout, refreshTokens, requireAuth, resetAuthClaimMapping } from "./auth.server-D5oiz9aF.js";
14
- import { attemptTokenRefresh, authRoute, configureRouteProtection, isTokenExpiredResponse, onboardingRoute, policyRoute, protectRoute, protectedRoute, publicRoute, resetRouteProtectionConfig, shouldAttemptTokenRefresh, shouldRefreshToken, subscriptionRoute } from "./server-B6PCxc-T.js";
15
- import { clearActiveTenant, clearCurrentTenant, configureIdentityContextFetcher, configureTenantCookie, configureTenantResolution, getActiveTenant, getAvailableTenants, getCurrentTenant, getTenantFromIdentityContext, initializeTenant, resetTenantCookieConfig, resetTenantResolutionConfig, setActiveTenant, setCurrentTenant } from "./server-Ca-3DJf2.js";
16
- import { clearIdentityContextCache, configureIdentityAPIFetcher, configurePermissionFetcher, contextToUserInfo, fetchIdentityContext, fetchUserPermissions, getIdentityContext, hasContextVersionChanged, updateContextVersion } from "./server-Dxsww9v9.js";
17
- import { checkPermission, configurePermissionRouteProtection, requirePermission, resetPermissionRouteProtection, withPermission } from "./server-hGrElJMG.js";
18
- import { createAPIConfigFactory, createApiClient, createFetchMiddleware } from "./server-C96NMFBy.js";
19
-
20
- export { AUTH_ERROR_COOKIE_PREFIX, ApiError, ErrorCodes, ErrorHandler, IdentityContextProvider, IdentitySignalRClient, LOG_LEVEL_PRIORITY, LogLevel, Logger, PERMISSION_MODULES, PermissionChecker, PermissionInitializer, ProtectedAction, ProtectedButton, ProtectedLink, ProtectedSection, SignalRClient, TenantProvider, attemptTokenRefresh, authRoute, cachePresets, calculateBackoffDelay, checkPermission, cleanupExpiredOAuthStates, cleanupSignalR, clearActiveTenant, clearAuthErrorHeaders, clearAuthServerCache, clearCurrentTenant, clearIdentityContextCache, closeRedisConnection, configureAuthClaimMapping, configureIdentityAPIFetcher, configureIdentityContextFetcher, configureIdentityEndpoints, configureIdentityStore, configurePermissionFetcher, configurePermissionRouteProtection, configureRouteProtection, configureTenantClient, configureTenantCookie, configureTenantEndpoints, configureTenantResolution, contextToUserInfo, createAPIConfigFactory, createApiClient, createAuthSession, createFetchMiddleware, createIdentitySignalRClient, createLogger, createOAuthState, createQueryClient, createQueryKeyFactory, createRetryFetch, defaultLoggerConfig, deleteOAuthState, destroyAuthSession, destroyAuthSessionById, destroyAuthSessionsByIdentitySession, destroyAuthSessionsBySessionState, destroyAuthSessionsBySid, destroyAuthSessionsForUser, evaluateAccessRequirement, extractUserInfo, fetchIdentityContext, fetchUserPermissions, fetchWithTimeout, getAccessToken, getActiveTenant, getAuthError, getAuthSession, getAuthSessionById, getAvailableTenants, getCurrentAuthSessionId, getCurrentTenant, getIdentityContext, getIdentitySignalRClient, getOAuthState, getPermissionChecker, getPermissions, getTenantCookieName, getTenantFromIdentityContext, getUser, handleApiError, handleBackChannelLogout, handleCallback, handleFrontChannelLogout, hasAuthError, hasContextVersionChanged, initialIdentityContext, initializePermissions, initializeSignalR, initializeTenant, initializeTenantStore, invalidationHelpers, isApiClientError, isRetryableError, isSessionValid, isTokenExpiredResponse, listAuthSessionDataForUser, listAuthSessionsForUser, listCurrentUserAuthSessions, logger, login, logout, normalizeIdentityContextPayload, normalizeIdentityPermissionsPayload, normalizeTenantDataPayload, normalizeTenantSwitchPayload, onboardingRoute, policyRoute, protectRoute, protectedRoute, publicRoute, refreshTokens, requireAuth, requireAuthSession, requirePermission, resetAuthClaimMapping, resetIdentityStoreConfig, resetPermissionRouteProtection, resetRouteProtectionConfig, resetTenantClientConfig, resetTenantCookieConfig, resetTenantResolutionConfig, setActiveTenant, setCurrentTenant, shouldAttemptTokenRefresh, shouldRefreshToken, subscriptionRoute, updateAuthSession, updateContextVersion, useAllPermissions, useCurrentOrganization, useCurrentTenant, useCurrentTenantData, useHasEntitlement, useHasModulePermission, useHasPermission, useHasPermissions, useHasSubscription, useIdentityContext, useIdentityStore, useIsOnboarded, useModulePermission, useOrganization, usePermission, usePermissionChecker, usePermissionStore, usePermissions, useTenant, useTenantData, useTenantStore, useTenantSwitcher, useUserPermissions, useUserTenants, withPermission, withRetry };
@@ -1,3 +0,0 @@
1
- import { IdentitySignalRClient, SignalRClient, cleanupSignalR, createIdentitySignalRClient, getIdentitySignalRClient, initializeSignalR } from "../signalr-k-70VHKo.js";
2
-
3
- export { IdentitySignalRClient, SignalRClient, cleanupSignalR, createIdentitySignalRClient, getIdentitySignalRClient, initializeSignalR };
@@ -1 +0,0 @@
1
- {"version":3,"file":"signalr-k-70VHKo.js","names":["DEFAULT_CONFIG: Required<Omit<SignalRClientConfig, 'hubPath' | 'baseUrl'>>","config: SignalRClientConfig","accessToken: string | null","event: unknown","eventName: string","handler: SignalREventHandler<T>","methodName: string","message: string","level: 'info' | 'warn' | 'error'","options: Omit<SignalRClientConfig, 'hubPath'>","handler: IdentityContextChangeHandler","identityClientInstance: IdentitySignalRClient | null"],"sources":["../src/signalr/signalr-client.ts"],"sourcesContent":["/**\n * SignalR Client for Real-time Updates\n * \n * This client connects to SignalR hub and listens for real-time events.\n * Supports identity context changes, permissions updates, etc.\n */\n\nimport { HubConnection, HubConnectionBuilder, LogLevel } from '@microsoft/signalr';\nimport type {\n SignalRClientConfig,\n SignalRConnectionState,\n SignalREventHandler,\n IdentityContextChangedEvent,\n IdentityContextChangeHandler,\n} from './types';\n\n/**\n * Default configuration\n */\nconst DEFAULT_CONFIG: Required<Omit<SignalRClientConfig, 'hubPath' | 'baseUrl'>> = {\n maxReconnectAttempts: 5,\n reconnectDelay: 1000,\n verbose: false,\n autoReconnectOnVisibility: true,\n};\n\n/**\n * SignalR Client Class\n * \n * Generic SignalR client that can be used for any hub connection.\n */\nexport class SignalRClient {\n private connection: HubConnection | null = null;\n private accessToken: string | null = null;\n private isConnecting = false;\n private reconnectAttempts = 0;\n private config: Required<Omit<SignalRClientConfig, 'baseUrl'>> & { baseUrl?: string };\n private eventHandlers: Map<string, Set<SignalREventHandler>> = new Map();\n private visibilityHandler: (() => void) | null = null;\n\n constructor(config: SignalRClientConfig) {\n this.config = {\n ...DEFAULT_CONFIG,\n ...config,\n };\n\n // Auto-reconnect on page visibility change\n if (typeof document !== 'undefined' && this.config.autoReconnectOnVisibility) {\n this.visibilityHandler = () => {\n if (document.visibilityState === 'visible' && this.connection?.state === 'Disconnected') {\n this.log('🔄 Page became visible, attempting reconnection...');\n this.connect(this.accessToken);\n }\n };\n document.addEventListener('visibilitychange', this.visibilityHandler);\n }\n }\n\n /**\n * Connect to SignalR hub\n */\n async connect(accessToken: string | null): Promise<void> {\n if (!accessToken) {\n this.log('⚠️ No access token provided for SignalR connection', 'warn');\n return;\n }\n\n if (this.isConnecting) {\n this.log('🔄 Connection already in progress...');\n return;\n }\n\n if (this.connection?.state === 'Connected') {\n this.log('✅ Already connected');\n return;\n }\n\n this.accessToken = accessToken;\n this.isConnecting = true;\n\n try {\n const hubUrl = this.buildHubUrl();\n\n this.connection = new HubConnectionBuilder()\n .withUrl(hubUrl, {\n accessTokenFactory: () => accessToken,\n })\n .withAutomaticReconnect({\n nextRetryDelayInMilliseconds: (retryContext) => {\n // Exponential backoff: 1s, 2s, 4s, 8s, 16s\n const delay = Math.min(\n this.config.reconnectDelay * Math.pow(2, retryContext.previousRetryCount),\n 16000\n );\n this.log(`🔄 Reconnect attempt ${retryContext.previousRetryCount + 1} in ${delay}ms`);\n return delay;\n },\n })\n .configureLogging(this.config.verbose ? LogLevel.Information : LogLevel.Warning)\n .build();\n\n // Setup event handlers\n this.setupConnectionHandlers();\n\n // Connect\n this.log(`🔄 Connecting to SignalR hub: ${hubUrl}`);\n await this.connection.start();\n\n this.log(`✅ Connected successfully (ID: ${this.connection.connectionId})`);\n this.reconnectAttempts = 0;\n } catch (error) {\n this.log(`❌ Connection failed: ${error}`, 'error');\n this.reconnectAttempts++;\n\n // Retry with exponential backoff\n if (this.reconnectAttempts <= this.config.maxReconnectAttempts) {\n const delay = Math.min(\n this.config.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1),\n 30000\n );\n this.log(\n `🔄 Retrying in ${delay}ms (attempt ${this.reconnectAttempts}/${this.config.maxReconnectAttempts})`\n );\n\n setTimeout(() => {\n this.connect(accessToken);\n }, delay);\n } else {\n this.log('❌ Max reconnection attempts reached', 'error');\n }\n } finally {\n this.isConnecting = false;\n }\n }\n\n /**\n * Build hub URL\n */\n private buildHubUrl(): string {\n let baseUrl = this.config.baseUrl;\n\n if (!baseUrl && typeof window !== 'undefined') {\n baseUrl = window.location.origin;\n }\n\n if (!baseUrl) {\n throw new Error('SignalR baseUrl is required outside browser runtimes');\n }\n\n return `${baseUrl}${this.config.hubPath}`;\n }\n\n /**\n * Setup connection lifecycle handlers\n */\n private setupConnectionHandlers(): void {\n if (!this.connection) return;\n\n this.connection.onreconnecting((error) => {\n this.log(`🔄 Reconnecting... ${error?.message || ''}`);\n });\n\n this.connection.onreconnected((connectionId) => {\n this.log(`✅ Reconnected (ID: ${connectionId})`);\n this.reconnectAttempts = 0;\n });\n\n this.connection.onclose((error) => {\n this.log(`🔌 Connection closed: ${error?.message || 'No error'}`);\n\n // Auto-reconnect if we have a token and it wasn't a manual disconnect\n if (this.accessToken && error) {\n setTimeout(() => {\n this.connect(this.accessToken);\n }, 5000);\n }\n });\n\n // Re-register all event handlers\n this.eventHandlers.forEach((handlers, eventName) => {\n this.connection!.on(eventName, (event: unknown) => {\n handlers.forEach((handler) => {\n try {\n handler(event);\n } catch (err) {\n this.log(`❌ Error in ${eventName} handler: ${err}`, 'error');\n }\n });\n });\n });\n }\n\n /**\n * Disconnect from SignalR hub\n */\n async disconnect(): Promise<void> {\n if (this.connection) {\n this.log('🔌 Disconnecting...');\n await this.connection.stop();\n this.connection = null;\n this.accessToken = null;\n this.reconnectAttempts = 0;\n }\n }\n\n /**\n * Register event handler\n */\n on<T = unknown>(eventName: string, handler: SignalREventHandler<T>): () => void {\n // Add to handlers map\n if (!this.eventHandlers.has(eventName)) {\n this.eventHandlers.set(eventName, new Set());\n }\n this.eventHandlers.get(eventName)!.add(handler as SignalREventHandler);\n\n // If already connected, register immediately\n if (this.connection) {\n this.connection.on(eventName, handler);\n }\n\n // Return unsubscribe function\n return () => {\n this.eventHandlers.get(eventName)?.delete(handler as SignalREventHandler);\n this.connection?.off(eventName, handler);\n };\n }\n\n /**\n * Invoke hub method\n */\n async invoke<T = void>(methodName: string, ...args: unknown[]): Promise<T> {\n if (!this.connection || this.connection.state !== 'Connected') {\n throw new Error('SignalR not connected');\n }\n return this.connection.invoke<T>(methodName, ...args);\n }\n\n /**\n * Get current connection state\n */\n get connectionState(): SignalRConnectionState {\n return (this.connection?.state as SignalRConnectionState) || 'Disconnected';\n }\n\n /**\n * Check if connected\n */\n get isConnected(): boolean {\n return this.connection?.state === 'Connected';\n }\n\n /**\n * Get connection ID\n */\n get connectionId(): string | null {\n return this.connection?.connectionId || null;\n }\n\n /**\n * Cleanup resources\n */\n dispose(): void {\n if (this.visibilityHandler && typeof document !== 'undefined') {\n document.removeEventListener('visibilitychange', this.visibilityHandler);\n }\n this.disconnect();\n this.eventHandlers.clear();\n }\n\n /**\n * Log message\n */\n private log(message: string, level: 'info' | 'warn' | 'error' = 'info'): void {\n if (!this.config.verbose && level === 'info') return;\n\n const prefix = '[SignalR]';\n switch (level) {\n case 'warn':\n console.warn(prefix, message);\n break;\n case 'error':\n console.error(prefix, message);\n break;\n default:\n console.log(prefix, message);\n }\n }\n}\n\n/**\n * Create Identity SignalR Client\n * \n * Factory function to create a SignalR client for identity hub.\n */\nexport function createIdentitySignalRClient(\n options: Omit<SignalRClientConfig, 'hubPath'> = {}\n): SignalRClient {\n return new SignalRClient({\n ...options,\n hubPath: '/hubs/identity',\n });\n}\n\n/**\n * Identity SignalR Client\n * \n * Specialized client for identity context changes.\n */\nexport class IdentitySignalRClient extends SignalRClient {\n private identityHandlers: IdentityContextChangeHandler[] = [];\n\n constructor(options: Omit<SignalRClientConfig, 'hubPath'> = {}) {\n super({\n ...options,\n hubPath: '/hubs/identity',\n });\n\n // Register identity context change handler\n this.on<IdentityContextChangedEvent>('IdentityContextChanged', (event) => {\n this.identityHandlers.forEach((handler) => {\n try {\n handler(event);\n } catch (error) {\n console.error('Error in identity context change handler:', error);\n }\n });\n });\n }\n\n /**\n * Add handler for identity context changes\n */\n onIdentityContextChanged(handler: IdentityContextChangeHandler): () => void {\n this.identityHandlers.push(handler);\n\n return () => {\n const index = this.identityHandlers.indexOf(handler);\n if (index > -1) {\n this.identityHandlers.splice(index, 1);\n }\n };\n }\n}\n\n/**\n * Singleton identity client instance\n */\nlet identityClientInstance: IdentitySignalRClient | null = null;\n\n/**\n * Get or create singleton identity SignalR client\n */\nexport function getIdentitySignalRClient(\n options: Omit<SignalRClientConfig, 'hubPath'> = {}\n): IdentitySignalRClient {\n if (!identityClientInstance) {\n identityClientInstance = new IdentitySignalRClient(options);\n }\n return identityClientInstance;\n}\n\n/**\n * Initialize SignalR with access token\n */\nexport async function initializeSignalR(\n accessToken: string | null,\n options: Omit<SignalRClientConfig, 'hubPath'> = {}\n): Promise<IdentitySignalRClient> {\n if (typeof window === 'undefined') {\n throw new Error('SignalR can only be initialized on the client');\n }\n\n const client = getIdentitySignalRClient(options);\n await client.connect(accessToken);\n return client;\n}\n\n/**\n * Cleanup SignalR connection\n */\nexport async function cleanupSignalR(): Promise<void> {\n if (identityClientInstance) {\n await identityClientInstance.disconnect();\n identityClientInstance.dispose();\n identityClientInstance = null;\n }\n}\n"],"mappings":"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"}
File without changes
@@ -1,3 +0,0 @@
1
- import { TenantProvider, configureTenantClient, configureTenantEndpoints, getTenantCookieName, initializeTenantStore, normalizeTenantDataPayload, normalizeTenantSwitchPayload, resetTenantClientConfig, useCurrentOrganization, useCurrentTenant, useCurrentTenantData, useOrganization, useTenant, useTenantData, useTenantStore, useTenantSwitcher } from "../tenant-DfLemb6n.js";
2
-
3
- export { TenantProvider, configureTenantClient, configureTenantEndpoints, getTenantCookieName, initializeTenantStore, normalizeTenantDataPayload, normalizeTenantSwitchPayload, resetTenantClientConfig, useCurrentOrganization, useCurrentTenant, useCurrentTenantData, useOrganization, useTenant, useTenantData, useTenantStore, useTenantSwitcher };
@@ -1,5 +0,0 @@
1
- import "../logging-DviiAkLx.js";
2
- import "../redis-session-storage.server-v2VcpFsQ.js";
3
- import { clearActiveTenant, clearCurrentTenant, configureIdentityContextFetcher, configureTenantCookie, configureTenantResolution, getActiveTenant, getAvailableTenants, getCurrentTenant, getTenantFromIdentityContext, initializeTenant, resetTenantCookieConfig, resetTenantResolutionConfig, setActiveTenant, setCurrentTenant } from "../server-Ca-3DJf2.js";
4
-
5
- export { clearActiveTenant, clearCurrentTenant, configureIdentityContextFetcher, configureTenantCookie, configureTenantResolution, getActiveTenant, getAvailableTenants, getCurrentTenant, getTenantFromIdentityContext, initializeTenant, resetTenantCookieConfig, resetTenantResolutionConfig, setActiveTenant, setCurrentTenant };
@@ -1 +0,0 @@
1
- {"version":3,"file":"tenant-DfLemb6n.js","names":["customFetchTenantData: TenantClientConfig['fetchTenantData'] | null","customSwitchTenant: TenantClientConfig['switchTenant'] | null","config: TenantClientConfig","tenantDataMap: Map<string, TenantData>","value: unknown","response: Response","payload: unknown","currentTenant: string | null","availableTenants: string[]","memberships?: TenantMembership[]","value","tenantId: string","value: TenantContextType"],"sources":["../src/tenant/tenant-store.ts","../src/tenant/tenant-context.tsx"],"sourcesContent":["/**\n * Tenant Store (Zustand)\n * \n * Client-side state management for tenant data.\n */\n\nimport { create } from 'zustand';\nimport { devtools } from 'zustand/middleware';\nimport type { OrganizationData, TenantData, TenantMembership } from './types';\n\nexport interface TenantDataFetcherContext {\n tenantId: string;\n endpoint: string;\n fetch: typeof fetch;\n}\n\nexport interface TenantSwitchContext {\n tenantId: string;\n endpoint: string;\n fetch: typeof fetch;\n currentTenant: string | null;\n}\n\nexport interface TenantSwitchResult {\n success: boolean;\n tenantData?: TenantData | null;\n reload?: boolean;\n error?: unknown;\n}\n\nexport interface TenantClientConfig {\n tenantDataEndpoint?: string;\n /** @deprecated Use tenantDataEndpoint instead. */\n organizationEndpoint?: string;\n tenantSwitchEndpoint?: string;\n tenantCookieName?: string;\n fetchTenantData?: (context: TenantDataFetcherContext) => Promise<TenantData | null>;\n switchTenant?: (context: TenantSwitchContext) => Promise<TenantSwitchResult>;\n reloadOnSwitch?: boolean;\n}\n\nexport interface TenantState {\n // State\n currentTenant: string | null;\n availableTenants: string[];\n memberships: Map<string, TenantMembership>;\n tenantData: Map<string, TenantData>;\n /** @deprecated Use tenantData instead. */\n organizations: Map<string, OrganizationData>;\n isLoading: boolean;\n isSwitching: boolean;\n\n // Actions\n setCurrentTenant: (tenantId: string | null) => void;\n setAvailableTenants: (tenants: string[]) => void;\n setMemberships: (memberships: TenantMembership[]) => void;\n setTenantData: (tenantId: string, data: TenantData) => void;\n addTenantData: (tenantId: string, data: TenantData) => void;\n /** @deprecated Use setTenantData instead. */\n setOrganization: (tenantId: string, data: OrganizationData) => void;\n /** @deprecated Use addTenantData instead. */\n addOrganization: (tenantId: string, data: OrganizationData) => void;\n setLoading: (loading: boolean) => void;\n setSwitching: (switching: boolean) => void;\n\n // Async actions\n fetchTenantData: (tenantId: string) => Promise<void>;\n /** @deprecated Use fetchTenantData instead. */\n fetchOrganization: (tenantId: string) => Promise<void>;\n switchTenant: (tenantId: string) => Promise<void>;\n\n // Getters\n getMembership: (tenantId: string) => TenantMembership | null;\n getCurrentMembership: () => TenantMembership | null;\n getTenantName: (tenantId: string) => string;\n getTenantData: (tenantId: string) => TenantData | null;\n getCurrentTenantData: () => TenantData | null;\n /** @deprecated Use getTenantData instead. */\n getOrganization: (tenantId: string) => OrganizationData | null;\n /** @deprecated Use getCurrentTenantData instead. */\n getCurrentOrganization: () => OrganizationData | null;\n}\n\n/**\n * Tenant client endpoints and browser-visible tenant cookie (configurable).\n */\nlet tenantDataEndpoint = '/api/tenant/data';\nlet tenantSwitchEndpoint = '/api/tenant/switch';\nlet tenantCookieName = '__spine_tenant';\nlet customFetchTenantData: TenantClientConfig['fetchTenantData'] | null = null;\nlet customSwitchTenant: TenantClientConfig['switchTenant'] | null = null;\nlet reloadOnSwitch = true;\n\n/**\n * Configure tenant client behavior for any backend contract.\n */\nexport function configureTenantClient(config: TenantClientConfig): void {\n if (config.tenantDataEndpoint || config.organizationEndpoint) {\n tenantDataEndpoint = config.tenantDataEndpoint ?? config.organizationEndpoint ?? tenantDataEndpoint;\n }\n if (config.tenantSwitchEndpoint) {\n tenantSwitchEndpoint = config.tenantSwitchEndpoint;\n }\n if (config.tenantCookieName) {\n tenantCookieName = config.tenantCookieName;\n }\n if (config.fetchTenantData) {\n customFetchTenantData = config.fetchTenantData;\n }\n if (config.switchTenant) {\n customSwitchTenant = config.switchTenant;\n }\n if (config.reloadOnSwitch !== undefined) {\n reloadOnSwitch = config.reloadOnSwitch;\n }\n}\n\n/**\n * Configure tenant store endpoints.\n *\n * @deprecated Use configureTenantClient for endpoint and contract customization.\n */\nexport function configureTenantEndpoints(config: TenantClientConfig): void {\n configureTenantClient(config);\n}\n\nexport function getTenantCookieName(): string {\n return tenantCookieName;\n}\n\nexport function resetTenantClientConfig(): void {\n tenantDataEndpoint = '/api/tenant/data';\n tenantSwitchEndpoint = '/api/tenant/switch';\n tenantCookieName = '__spine_tenant';\n customFetchTenantData = null;\n customSwitchTenant = null;\n reloadOnSwitch = true;\n}\n\nfunction asOrganizationMap(tenantDataMap: Map<string, TenantData>): Map<string, OrganizationData> {\n return tenantDataMap as unknown as Map<string, OrganizationData>;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return Boolean(value) && typeof value === 'object' && !Array.isArray(value);\n}\n\nasync function readJsonResponse(response: Response): Promise<unknown> {\n if (response.status === 204) {\n return null;\n }\n\n const text = await response.text();\n return text.trim().length > 0 ? JSON.parse(text) : null;\n}\n\nexport function normalizeTenantDataPayload(payload: unknown): TenantData | null {\n if (!isRecord(payload)) {\n return null;\n }\n\n if (payload.success === false) {\n return null;\n }\n\n const nestedData = payload.data ?? payload.tenant ?? payload.workspace ?? payload.account;\n if (isRecord(nestedData)) {\n return nestedData as TenantData;\n }\n\n return payload as TenantData;\n}\n\nexport function normalizeTenantSwitchPayload(payload: unknown): TenantSwitchResult {\n if (payload === null || payload === undefined) {\n return { success: true };\n }\n\n if (!isRecord(payload)) {\n return { success: false, error: payload };\n }\n\n const success = payload.success ?? payload.ok ?? payload.switched;\n const nestedData = payload.data ?? payload.tenant ?? payload.workspace ?? payload.account;\n\n return {\n success: success === undefined ? true : Boolean(success),\n tenantData: isRecord(nestedData) ? nestedData as TenantData : null,\n reload: typeof payload.reload === 'boolean' ? payload.reload : undefined,\n error: payload.error,\n };\n}\n\nasync function defaultFetchTenantData({ tenantId, endpoint, fetch: fetchFn }: TenantDataFetcherContext): Promise<TenantData | null> {\n const response = await fetchFn(`${endpoint}/${encodeURIComponent(tenantId)}`);\n if (!response.ok) {\n return null;\n }\n\n return normalizeTenantDataPayload(await readJsonResponse(response));\n}\n\nasync function defaultSwitchTenant({ tenantId, endpoint, fetch: fetchFn }: TenantSwitchContext): Promise<TenantSwitchResult> {\n const response = await fetchFn(endpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ tenantId }),\n });\n\n if (!response.ok) {\n return { success: false, error: response.status };\n }\n\n return normalizeTenantSwitchPayload(await readJsonResponse(response));\n}\n\nexport const useTenantStore = create<TenantState>()(\n devtools(\n (set, get) => ({\n // Initial state\n currentTenant: null,\n availableTenants: [],\n memberships: new Map(),\n tenantData: new Map(),\n organizations: new Map(),\n isLoading: false,\n isSwitching: false,\n\n // Setters\n setCurrentTenant: (tenantId) => set({ currentTenant: tenantId }),\n\n setAvailableTenants: (tenants) => {\n set({ availableTenants: tenants });\n\n // Fetch tenant data for all tenants\n const { tenantData, fetchTenantData } = get();\n tenants.forEach((tenantId) => {\n if (!tenantData.has(tenantId)) {\n fetchTenantData(tenantId);\n }\n });\n },\n\n setMemberships: (memberships) => {\n const membershipMap = new Map<string, TenantMembership>();\n memberships.forEach((membership) => {\n membershipMap.set(membership.tenantId, membership);\n });\n set({ memberships: membershipMap });\n },\n\n setTenantData: (tenantId, data) =>\n set((state) => ({\n tenantData: new Map(state.tenantData).set(tenantId, data),\n organizations: asOrganizationMap(new Map(state.tenantData).set(tenantId, data)),\n })),\n\n addTenantData: (tenantId, data) => {\n set((state) => {\n const newTenantData = new Map(state.tenantData);\n newTenantData.set(tenantId, data);\n return {\n tenantData: newTenantData,\n organizations: asOrganizationMap(newTenantData),\n };\n });\n },\n\n setOrganization: (tenantId, data) => get().setTenantData(tenantId, data),\n addOrganization: (tenantId, data) => get().addTenantData(tenantId, data),\n\n setLoading: (loading) => set({ isLoading: loading }),\n setSwitching: (switching) => set({ isSwitching: switching }),\n\n // Async actions\n fetchTenantData: async (tenantId) => {\n try {\n const tenantData = await (customFetchTenantData ?? defaultFetchTenantData)({\n tenantId,\n endpoint: tenantDataEndpoint,\n fetch,\n });\n\n if (tenantData) {\n get().setTenantData(tenantId, tenantData);\n }\n } catch (error) {\n console.error('Failed to fetch tenant data:', tenantId, error);\n }\n },\n\n fetchOrganization: async (tenantId) => get().fetchTenantData(tenantId),\n\n switchTenant: async (tenantId) => {\n const { availableTenants } = get();\n\n if (!availableTenants.includes(tenantId)) {\n console.error('Tenant not available:', tenantId);\n return;\n }\n\n set({ isSwitching: true });\n\n try {\n const result = await (customSwitchTenant ?? defaultSwitchTenant)({\n tenantId,\n endpoint: tenantSwitchEndpoint,\n fetch,\n currentTenant: get().currentTenant,\n });\n\n if (result.success) {\n set({ currentTenant: tenantId });\n\n if (result.tenantData) {\n get().setTenantData(tenantId, result.tenantData);\n }\n\n // Fetch tenant data if not already loaded\n const { tenantData, fetchTenantData } = get();\n if (!tenantData.has(tenantId)) {\n await fetchTenantData(tenantId);\n }\n\n set({ isSwitching: false });\n\n if ((result.reload ?? reloadOnSwitch) && typeof window !== 'undefined') {\n window.location.reload();\n }\n } else {\n console.error('Failed to switch tenant:', result.error ?? tenantId);\n set({ isSwitching: false });\n }\n } catch (error) {\n console.error('Failed to switch tenant:', error);\n set({ isSwitching: false });\n }\n },\n\n // Getters\n getMembership: (tenantId) => {\n const { memberships } = get();\n return memberships.get(tenantId) || null;\n },\n\n getCurrentMembership: () => {\n const { currentTenant, memberships } = get();\n if (!currentTenant) return null;\n return memberships.get(currentTenant) || null;\n },\n\n getTenantName: (tenantId) => {\n const { memberships, tenantData } = get();\n const membership = memberships.get(tenantId);\n if (membership?.tenantName) {\n return membership.tenantName;\n }\n if (membership?.organizationName) {\n return membership.organizationName;\n }\n return tenantData.get(tenantId)?.name || tenantId;\n },\n\n getTenantData: (tenantId) => {\n const { tenantData } = get();\n return tenantData.get(tenantId) || null;\n },\n\n getCurrentTenantData: () => {\n const { currentTenant, tenantData } = get();\n if (!currentTenant) return null;\n return tenantData.get(currentTenant) || null;\n },\n\n getOrganization: (tenantId) => {\n return get().getTenantData(tenantId) as OrganizationData | null;\n },\n\n getCurrentOrganization: () => {\n return get().getCurrentTenantData() as OrganizationData | null;\n },\n }),\n { name: 'TenantStore' }\n )\n);\n\n/**\n * Initialize store with server data\n */\nexport function initializeTenantStore(\n currentTenant: string | null,\n availableTenants: string[],\n memberships?: TenantMembership[]\n) {\n const store = useTenantStore.getState();\n store.setCurrentTenant(currentTenant);\n store.setAvailableTenants(availableTenants);\n if (memberships) {\n store.setMemberships(memberships);\n }\n}\n","/**\n * Tenant Context Provider\n * \n * React context for tenant management.\n */\n\nimport { createContext, useContext, useEffect, type ReactNode } from 'react';\nimport { getTenantCookieName, useTenantStore } from './tenant-store';\nimport type { OrganizationData, TenantContextType, TenantData, TenantMembership } from './types';\n\nconst TenantContext = createContext<TenantContextType | null>(null);\n\ninterface TenantProviderProps {\n children: ReactNode;\n initialTenant?: string | null;\n initialTenants?: string[];\n initialMemberships?: TenantMembership[];\n onTenantChange?: () => void;\n}\n\nexport function TenantProvider({\n children,\n initialTenant = null,\n initialTenants = [],\n initialMemberships = [],\n onTenantChange,\n}: TenantProviderProps) {\n const currentTenant = useTenantStore((state) => state.currentTenant);\n const availableTenants = useTenantStore((state) => state.availableTenants);\n const memberships = useTenantStore((state) => state.memberships);\n const tenantData = useTenantStore((state) => state.tenantData);\n const organizations = useTenantStore((state) => state.organizations);\n const isLoading = useTenantStore((state) => state.isLoading || state.isSwitching);\n const currentTenantData = useTenantStore((state) => state.getCurrentTenantData());\n const currentMembership = useTenantStore((state) => state.getCurrentMembership());\n\n const setCurrentTenant = useTenantStore((state) => state.setCurrentTenant);\n const setAvailableTenants = useTenantStore((state) => state.setAvailableTenants);\n const setMemberships = useTenantStore((state) => state.setMemberships);\n const switchTenantAction = useTenantStore((state) => state.switchTenant);\n const fetchTenantData = useTenantStore((state) => state.fetchTenantData);\n const getTenantData = useTenantStore((state) => state.getTenantData);\n const getOrganization = useTenantStore((state) => state.getOrganization);\n const getMembership = useTenantStore((state) => state.getMembership);\n const getTenantName = useTenantStore((state) => state.getTenantName);\n\n // Initialize store with initial values\n useEffect(() => {\n if (initialTenant) {\n setCurrentTenant(initialTenant);\n }\n if (initialTenants.length > 0) {\n setAvailableTenants(initialTenants);\n }\n if (initialMemberships.length > 0) {\n setMemberships(initialMemberships);\n }\n }, [\n initialTenant,\n initialTenants.join(','),\n initialMemberships.map((membership) => membership.tenantId).join(','),\n setCurrentTenant,\n setAvailableTenants,\n setMemberships,\n ]);\n\n // Read tenant from cookie (client-side)\n useEffect(() => {\n const getCookieTenant = () => {\n const cookies = document.cookie.split(';');\n const cookieName = getTenantCookieName();\n const tenantCookie = cookies.find((cookie) =>\n cookie.trim().startsWith(`${cookieName}=`)\n );\n\n if (tenantCookie) {\n const value = tenantCookie.split('=')[1];\n return decodeURIComponent(value);\n }\n\n return null;\n };\n\n if (!currentTenant) {\n const cookieTenant = getCookieTenant();\n if (cookieTenant && availableTenants.includes(cookieTenant)) {\n setCurrentTenant(cookieTenant);\n }\n }\n }, [currentTenant, availableTenants, setCurrentTenant]);\n\n // Switch tenant handler\n const switchTenant = (tenantId: string) => {\n switchTenantAction(tenantId).then(() => {\n if (onTenantChange) {\n onTenantChange();\n }\n });\n };\n\n // Refresh tenants (handled by identity context)\n const refreshTenants = () => {\n console.log('Tenant refresh requested - should be handled by identity context');\n };\n\n // Refresh tenant data\n const refreshTenantData = () => {\n if (currentTenant) {\n fetchTenantData(currentTenant);\n }\n };\n\n const refreshOrganization = refreshTenantData;\n\n // Load tenant data when tenant is set\n useEffect(() => {\n if (currentTenant && !currentTenantData) {\n refreshTenantData();\n }\n }, [currentTenant, currentTenantData]);\n\n const currentOrganization = currentTenantData as OrganizationData | null;\n\n const value: TenantContextType = {\n currentTenant,\n availableTenants,\n memberships,\n currentTenantData,\n tenantData,\n currentMembership,\n isLoading,\n switchTenant,\n refreshTenants,\n refreshTenantData,\n getTenantData,\n getMembership,\n getTenantName,\n currentOrganization,\n organizations,\n refreshOrganization,\n getOrganization,\n };\n\n return (\n <TenantContext.Provider value={value}>\n {children}\n </TenantContext.Provider>\n );\n}\n\n/**\n * Hook for using tenant context\n */\nexport function useTenant(): TenantContextType {\n const context = useContext(TenantContext);\n\n if (!context) {\n throw new Error('useTenant must be used within a TenantProvider');\n }\n\n return context;\n}\n\n/**\n * Hook for current tenant only\n */\nexport function useCurrentTenant(): string | null {\n const { currentTenant } = useTenant();\n return currentTenant;\n}\n\n/**\n * Hook for tenant switching\n */\nexport function useTenantSwitcher() {\n const { switchTenant, isLoading, availableTenants } = useTenant();\n return { switchTenant, isLoading, availableTenants };\n}\n\n/**\n * Hook for current tenant data\n */\nexport function useCurrentTenantData(): TenantData | null {\n const { currentTenantData } = useTenant();\n return currentTenantData;\n}\n\n/**\n * Hook for tenant data operations\n */\nexport function useTenantData() {\n const { currentTenantData, refreshTenantData, isLoading } = useTenant();\n\n return {\n tenantData: currentTenantData,\n refreshTenantData,\n isLoading,\n };\n}\n\n/**\n * Hook for current organization\n *\n * @deprecated Use useCurrentTenantData instead.\n */\nexport function useCurrentOrganization(): OrganizationData | null {\n const { currentOrganization } = useTenant();\n return currentOrganization;\n}\n\n/**\n * Hook for organization operations\n *\n * @deprecated Use useTenantData instead.\n */\nexport function useOrganization() {\n const { currentOrganization, refreshOrganization, isLoading } = useTenant();\n\n return {\n organization: currentOrganization,\n refreshOrganization,\n isLoading,\n };\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"types-BaaE6wjY.js","names":["error: unknown"],"sources":["../src/api-client/types.ts"],"sourcesContent":["/**\n * API Client Types\n */\n\n// Re-export Logger from logging to avoid duplication\nexport type { LoggerConfig as Logger } from '../logging/types';\n\nexport interface APIConfig {\n basePath: string;\n accessToken?: string;\n headers: Record<string, string>;\n tenantId: string;\n credentials?: RequestCredentials;\n baseOptions?: {\n timeout?: number;\n };\n}\n\nexport interface CreateAPIConfigOptions {\n requireTenant?: boolean;\n includeAuth?: boolean;\n tenantId?: string;\n customHeaders?: Record<string, string>;\n}\n\nexport interface ProblemDetails {\n type: string;\n title: string;\n status: number;\n detail: string;\n traceId: string;\n requestId?: string;\n instance?: string;\n meta?: {\n errors?: ValidationError[];\n [key: string]: unknown;\n };\n}\n\nexport interface ValidationError {\n field: string;\n message: string;\n code?: string;\n}\n\nexport interface RetryConfig {\n maxRetries?: number;\n baseDelay?: number;\n maxDelay?: number;\n retryCondition?: (error: unknown) => boolean;\n}\n\nexport interface APIRequestConfig {\n url?: string;\n method?: string;\n headers?: Record<string, string>;\n}\n\nexport interface APIResponse<T = unknown> {\n data: T;\n status: number;\n statusText: string;\n headers: Record<string, string>;\n raw?: Response;\n}\n\nexport interface APIClientError<T = unknown> extends Error {\n isApiClientError: true;\n response?: APIResponse<T>;\n config?: APIRequestConfig;\n problemDetails?: ProblemDetails;\n shouldLogout?: boolean;\n originalError?: unknown;\n cause?: unknown;\n}\n\nexport function isApiClientError(error: unknown): error is APIClientError {\n return (\n typeof error === 'object' &&\n error !== null &&\n 'isApiClientError' in error &&\n (error as { isApiClientError?: boolean }).isApiClientError === true\n );\n}\n\nexport type FetchAPI = typeof fetch;\n\nexport interface FetchRequestContext {\n fetch: FetchAPI;\n url: string;\n init: RequestInit;\n}\n\nexport interface FetchResponseContext extends FetchRequestContext {\n response: Response;\n}\n\nexport interface FetchErrorContext extends FetchRequestContext {\n error: unknown;\n response?: Response;\n}\n\nexport interface FetchMiddleware {\n pre?: (context: FetchRequestContext) => Promise<{ url: string; init: RequestInit } | void>;\n post?: (context: FetchResponseContext) => Promise<Response | void>;\n onError?: (context: FetchErrorContext) => Promise<Response | void>;\n}\n\nexport type EnhancedAPIClient<T> = {\n [K in keyof T]: T[K] extends (...args: infer A) => Promise<infer R>\n ? K extends `${string}Raw`\n ? T[K]\n : (...args: A) => Promise<APIResponse<R>>\n : T[K];\n};\n\n/**\n * API Logger interface for dependency injection (simplified)\n */\nexport interface APILogger {\n debug?: (message: string, context?: Record<string, unknown>) => void;\n info?: (message: string, context?: Record<string, unknown>) => void;\n warn?: (message: string, error?: Error, context?: Record<string, unknown>) => void;\n error?: (message: string, error?: Error, context?: Record<string, unknown>) => void;\n}\n\n/**\n * Function type for getting access token from request\n */\nexport type GetAccessTokenFn = (request: Request) => Promise<string | null>;\n\n/**\n * Function type for getting current tenant from request\n */\nexport type GetCurrentTenantFn = (request: Request) => Promise<string | null>;\n\n/**\n * Token refresh result\n */\nexport interface TokenRefreshResult {\n success: boolean;\n newAccessToken?: string;\n shouldLogout?: boolean;\n error?: string;\n}\n\n/**\n * Function type for attempting token refresh\n */\nexport type TokenRefreshFn = (request: Request) => Promise<TokenRefreshResult>;\n"],"mappings":";AA4EA,SAAgB,iBAAiBA,OAAyC;AACxE,eACS,UAAU,YACjB,UAAU,QACV,sBAAsB,SACrB,MAAyC,qBAAqB;AAElE"}