* feat(companion): add ESLint with Prettier integration
- Add ESLint with eslint-config-expo/flat for SDK 55
- Integrate Prettier via eslint-plugin-prettier
- Configure environment-specific settings for Node.js config files and browser extension
- Add lint script to package.json
- Fix Prettier formatting issues in AvailabilityTab.tsx and types/index.ts
* fix(companion): use bun lockfile instead of npm
- Remove package-lock.json that was accidentally committed
- Update bun.lock with ESLint dependencies
* fix(companion): fix all ESLint errors (16 total)
- Fix unescaped entities in JSX (react/no-unescaped-entities):
- app/(tabs)/(event-types)/index.tsx: escape quotes in delete confirmation
- components/LoginScreen.tsx: escape apostrophe in "Don't have an account"
- components/NetworkStatusBanner.tsx: escape apostrophes in offline message
- components/booking-modals/BookingModals.tsx: escape apostrophe in rejection message
- components/event-type-detail/tabs/AdvancedTab.tsx: escape quotes and apostrophes
- components/screens/AvailabilityListScreen.tsx: escape quotes in delete confirmation
- Fix react-hooks/rules-of-hooks in RescheduleScreen.tsx:
- Move useMemo calls before conditional early return to ensure hooks
are called in the same order on every render
* fix(companion): fix all ESLint warnings (68 total)
- Fix import/no-named-as-default warnings by using named imports
- Fix @typescript-eslint/no-unused-vars warnings by removing unused imports/variables
- Fix react-hooks/exhaustive-deps warnings with eslint-disable comments
- Convert unused error variables in catch blocks to bare catch
- Remove unused imports (useRouter, Alert, useSafeAreaInsets, etc.)
- All ESLint checks now pass with 0 errors and 0 warnings
* feat(companion): add pre-commit lint check with --max-warnings=0
- Add companion lint check to root lint-staged.config.mjs
- Runs 'bun run lint -- --fix --max-warnings=0' for companion files
- Follows same pattern as apps/packages lint check
- Supports SKIP_WARNINGS=1 env var to bypass warning check if needed
- Commit will fail if any ESLint errors or warnings are present
* fix(companion): restore state variables and imports removed during ESLint fixes
- Restore locationAddress, locationLink, locationPhone state variables in event-type-detail.tsx (still used in fetchEventTypeData)
- Restore Alert import in BookingListScreen.tsx (still used for report booking)
- Add eslint-disable comments for the restored state variables since they're only used by setters
* test
* test
* fix eslint pre commit check
* fix: In React Native, HTML entities like ' are rendered literally as text, not decoded like in web browsers
* refactor(companion): remove ESLint and Prettier config in preparation for Biome
* feat(companion): add Biome for linting and formatting
- Install @biomejs/biome with exact version pinning (2.3.10)
- Add biome.json with formatting settings matching previous Prettier config
- Add lint, format, check, and check:ci scripts to package.json
- Update lint-staged to use Biome instead of Prettier
- Remove Prettier and prettier-plugin-tailwindcss dependencies
- Delete .prettierrc.js configuration file
* style(companion): apply Biome formatting and fix lint-staged config
- Update lint-staged to use 'biome format' instead of 'biome check'
(pre-commit should only format, not lint)
- Apply Biome formatting to all companion files
* feat(companion): configure Biome for strict linting with pre-commit enforcement
- Apply Biome unsafe fixes (unused imports, optional chaining, etc.)
- Configure biome.json rules to disable overly strict rules for existing code:
- noExplicitAny, noArrayIndexKey, useIterableCallbackReturn (suspicious)
- useExhaustiveDependencies, noUnusedFunctionParameters (correctness)
- noStaticOnlyClass (complexity)
- noNonNullAssertion (style)
- noStaticElementInteractions (a11y)
- Update lint-staged to use 'biome check --write --error-on-warnings'
(pre-commit now runs both formatting AND linting, fails on warnings)
* fix(companion): enable noNonNullAssertion rule and fix all violations
- Enable noNonNullAssertion rule in biome.json (set to 'error')
- Fix 7 violations across the codebase:
- hooks/useBookings.ts: Add runtime check before API call
- hooks/useEventTypes.ts: Add runtime check before API call
- hooks/useSchedules.ts: Add runtime check before API call
- extension/entrypoints/content.ts: Add null checks for cache and getAttribute
* fix(companion): enable noStaticOnlyClass and noStaticElementInteractions rules
- Enable noStaticOnlyClass rule in biome.json
- Enable noStaticElementInteractions rule in biome.json
- Convert CalComAPIService from static class to object literal pattern
- Convert WebAuthService from static class to object literal pattern
- Add role="tooltip" to Tooltip.web.tsx for accessibility
* fix(companion): resolve TypeScript type errors in useEffect dependencies and remove unused state property
* fix(companion): enable noUnusedFunctionParameters rule and fix 9 violations
* fix(companion): enable noUnusedVariables rule and fix 4 violations
* fix(companion): enable useIterableCallbackReturn rule and fix 11 violations
* fix(companion): enable useExhaustiveDependencies rule and fix 7 violations
* fix(companion): enable noArrayIndexKey rule and fix 10 violations
* fix(companion): enable noExplicitAny rule and fix all violations
- Enable noExplicitAny rule in biome.json
- Replace any types with proper specific types across all files:
- calcom.ts: Add proper type definitions for API responses and inputs
- event-types.types.ts: Add BookingLimitsCount, BookingLimitsDuration, ConfirmationPolicy types
- buildPartialUpdatePayload.ts: Fix type mismatches for booking limits functions
- booking-actions.ts: Add proper types for booking action handlers
- BookingDetailScreen.tsx, MarkNoShowScreen.tsx: Add types for attendee data
- AvailabilityListItem.ios.tsx, BookingListItem.ios.tsx: Import SFSymbols7_0 type
- extension/content.ts: Fix slot types and __cleanup invocation issues
- Remove unused imports and interfaces
* address cubics comments
* add useHookAtTopLevel rule
203 lines
6.5 KiB
TypeScript
203 lines
6.5 KiB
TypeScript
/**
|
|
* Cache Configuration for Cal.com Companion App
|
|
*
|
|
* This module provides centralized cache configuration with environment variable support.
|
|
* All cache durations are configurable via EXPO_PUBLIC_ prefixed environment variables.
|
|
*/
|
|
|
|
// Helper to parse environment variable to number with fallback
|
|
const getEnvNumber = (key: string, fallback: number): number => {
|
|
const value = process.env[key];
|
|
if (value === undefined || value === "") {
|
|
return fallback;
|
|
}
|
|
const parsed = parseInt(value, 10);
|
|
return Number.isNaN(parsed) ? fallback : parsed;
|
|
};
|
|
|
|
// Convert minutes to milliseconds
|
|
// -1 means "never stale" (Infinity)
|
|
const minutesToMs = (minutes: number): number => {
|
|
if (minutes < 0) return Infinity;
|
|
return minutes * 60 * 1000;
|
|
};
|
|
|
|
/**
|
|
* Default cache durations in minutes
|
|
* Use -1 to indicate "never stale" (Infinity) - data only refreshes on manual reload or mutations
|
|
*/
|
|
const DEFAULT_STALE_TIME_MINUTES = 5;
|
|
const DEFAULT_BOOKINGS_STALE_TIME_MINUTES = 5;
|
|
const DEFAULT_EVENT_TYPES_STALE_TIME_MINUTES = -1; // Never stale - only refresh on mutations
|
|
const DEFAULT_SCHEDULES_STALE_TIME_MINUTES = -1; // Never stale - only refresh on mutations
|
|
const DEFAULT_USER_PROFILE_STALE_TIME_MINUTES = -1; // Never stale - only refresh on manual reload
|
|
const DEFAULT_GC_TIME_MINUTES = 1440; // Keep cached data for 24 hours (full day offline support)
|
|
|
|
/**
|
|
* Cache configuration object with all settings
|
|
*/
|
|
export const CACHE_CONFIG = {
|
|
/**
|
|
* Default stale time for all queries (in milliseconds)
|
|
* Data older than this is considered stale and will be refetched in background
|
|
*/
|
|
defaultStaleTime: minutesToMs(
|
|
getEnvNumber("EXPO_PUBLIC_CACHE_STALE_TIME_MINUTES", DEFAULT_STALE_TIME_MINUTES)
|
|
),
|
|
|
|
/**
|
|
* Garbage collection time (in milliseconds)
|
|
* Unused cache entries are removed after this duration
|
|
*/
|
|
gcTime: minutesToMs(getEnvNumber("EXPO_PUBLIC_CACHE_GC_TIME_MINUTES", DEFAULT_GC_TIME_MINUTES)),
|
|
|
|
/**
|
|
* Resource-specific cache configurations
|
|
*
|
|
* Stale time determines when data is considered "stale" and should be refetched:
|
|
* - Bookings: 5 min - moderate refresh rate since bookings can change externally
|
|
* - Event Types: Infinity - only refresh on mutations (create/update/delete) or manual pull-to-refresh
|
|
* - Schedules: Infinity - only refresh on mutations (create/update/delete) or manual pull-to-refresh
|
|
* - User Profile: Infinity - only refresh on manual pull-to-refresh (rarely changes)
|
|
*/
|
|
bookings: {
|
|
staleTime: minutesToMs(
|
|
getEnvNumber(
|
|
"EXPO_PUBLIC_BOOKINGS_CACHE_STALE_TIME_MINUTES",
|
|
DEFAULT_BOOKINGS_STALE_TIME_MINUTES
|
|
)
|
|
),
|
|
},
|
|
|
|
eventTypes: {
|
|
/** Infinity = never stale, only refreshes on mutations or manual reload */
|
|
staleTime: minutesToMs(
|
|
getEnvNumber(
|
|
"EXPO_PUBLIC_EVENT_TYPES_CACHE_STALE_TIME_MINUTES",
|
|
DEFAULT_EVENT_TYPES_STALE_TIME_MINUTES
|
|
)
|
|
),
|
|
},
|
|
|
|
schedules: {
|
|
/** Infinity = never stale, only refreshes on mutations or manual reload */
|
|
staleTime: minutesToMs(
|
|
getEnvNumber(
|
|
"EXPO_PUBLIC_SCHEDULES_CACHE_STALE_TIME_MINUTES",
|
|
DEFAULT_SCHEDULES_STALE_TIME_MINUTES
|
|
)
|
|
),
|
|
},
|
|
|
|
userProfile: {
|
|
/** Infinity = never stale, only refreshes on manual reload */
|
|
staleTime: minutesToMs(
|
|
getEnvNumber(
|
|
"EXPO_PUBLIC_USER_PROFILE_CACHE_STALE_TIME_MINUTES",
|
|
DEFAULT_USER_PROFILE_STALE_TIME_MINUTES
|
|
)
|
|
),
|
|
},
|
|
|
|
/**
|
|
* Refetch behavior configuration
|
|
*/
|
|
refetch: {
|
|
onWindowFocus: true,
|
|
onReconnect: true,
|
|
onMount: false,
|
|
},
|
|
|
|
/**
|
|
* Retry configuration for failed queries
|
|
*/
|
|
retry: {
|
|
count: 3,
|
|
delay: (attemptIndex: number) => Math.min(1000 * 2 ** attemptIndex, 30000),
|
|
},
|
|
|
|
/**
|
|
* Persistence configuration
|
|
*/
|
|
persistence: {
|
|
/** Key prefix for persisted cache in storage */
|
|
storageKey: "cal-companion-query-cache",
|
|
/** Maximum age of persisted cache before it's discarded (24 hours) */
|
|
maxAge: 24 * 60 * 60 * 1000,
|
|
/** Throttle time for persisting cache to storage (1 second) */
|
|
throttleTime: 1000,
|
|
},
|
|
} as const;
|
|
|
|
/**
|
|
* Query key factory for consistent cache key generation
|
|
* Using array-based keys enables granular cache invalidation
|
|
*/
|
|
export const queryKeys = {
|
|
// Bookings
|
|
bookings: {
|
|
all: ["bookings"] as const,
|
|
lists: () => [...queryKeys.bookings.all, "list"] as const,
|
|
list: (filters: Record<string, unknown>) => [...queryKeys.bookings.lists(), filters] as const,
|
|
details: () => [...queryKeys.bookings.all, "detail"] as const,
|
|
detail: (uid: string) => [...queryKeys.bookings.details(), uid] as const,
|
|
},
|
|
|
|
// Event Types
|
|
eventTypes: {
|
|
all: ["eventTypes"] as const,
|
|
lists: () => [...queryKeys.eventTypes.all, "list"] as const,
|
|
list: (filters?: Record<string, unknown>) =>
|
|
filters
|
|
? ([...queryKeys.eventTypes.lists(), filters] as const)
|
|
: queryKeys.eventTypes.lists(),
|
|
details: () => [...queryKeys.eventTypes.all, "detail"] as const,
|
|
detail: (id: number) => [...queryKeys.eventTypes.details(), id] as const,
|
|
},
|
|
|
|
// Schedules (Availability)
|
|
schedules: {
|
|
all: ["schedules"] as const,
|
|
lists: () => [...queryKeys.schedules.all, "list"] as const,
|
|
list: (filters?: Record<string, unknown>) =>
|
|
filters ? ([...queryKeys.schedules.lists(), filters] as const) : queryKeys.schedules.lists(),
|
|
details: () => [...queryKeys.schedules.all, "detail"] as const,
|
|
detail: (id: number) => [...queryKeys.schedules.details(), id] as const,
|
|
},
|
|
|
|
// User Profile
|
|
userProfile: {
|
|
all: ["userProfile"] as const,
|
|
current: () => [...queryKeys.userProfile.all, "current"] as const,
|
|
},
|
|
|
|
// Conferencing
|
|
conferencing: {
|
|
all: ["conferencing"] as const,
|
|
options: () => [...queryKeys.conferencing.all, "options"] as const,
|
|
},
|
|
|
|
// Webhooks
|
|
webhooks: {
|
|
all: ["webhooks"] as const,
|
|
global: () => [...queryKeys.webhooks.all, "global"] as const,
|
|
eventType: (eventTypeId: number) =>
|
|
[...queryKeys.webhooks.all, "eventType", eventTypeId] as const,
|
|
},
|
|
|
|
// Private Links
|
|
privateLinks: {
|
|
all: ["privateLinks"] as const,
|
|
eventType: (eventTypeId: number) => [...queryKeys.privateLinks.all, eventTypeId] as const,
|
|
},
|
|
} as const;
|
|
|
|
/**
|
|
* Type exports for query keys
|
|
*/
|
|
export type QueryKeys = typeof queryKeys;
|
|
export type BookingQueryKeys = typeof queryKeys.bookings;
|
|
export type EventTypeQueryKeys = typeof queryKeys.eventTypes;
|
|
export type ScheduleQueryKeys = typeof queryKeys.schedules;
|
|
export type UserProfileQueryKeys = typeof queryKeys.userProfile;
|