Configuration

defineConfig

defineConfig validates all fields with Zod and returns a frozen LimestoneConfig. Pass the return value directly to <LimestoneProvider config={...}>. Do not mutate it after creation.

import { defineConfig } from '@objectifthunes/limestone-sdk';

const config = defineConfig({
  theme: myTheme,
  darkTheme: myDarkTheme,       // optional
  api: { ... },
  adapters: { ... },
  features: { ... },
});

The full type signature of the argument:

import type {
  LimestoneConfig,
  ApiConfig,
  ApiAuthConfig,
  ApiRetryConfig,
  ApiOfflineConfig,
  AdaptersConfig,
  FeaturesConfig,
} from '@objectifthunes/limestone-sdk';

Theme config

Pass the return value of defineTheme as theme. An optional darkTheme is used when the device color scheme is dark.

import { defineConfig, defineTheme } from '@objectifthunes/limestone-sdk';

const lightTheme = defineTheme({ name: 'light', mode: 'light', tokens: { ... } });
const darkTheme  = defineTheme({ name: 'dark',  mode: 'dark',  tokens: { ... } });

const config = defineConfig({
  theme: lightTheme,
  darkTheme,
});

LimestoneProvider reads the device color scheme via useColorScheme and activates darkTheme automatically when present.

API client config

All api fields are optional. Omit api entirely if your app does not call a back-end.

const config = defineConfig({
  theme: myTheme,
  api: {
    baseUrl: 'https://api.example.com',

    // Auth — optional
    auth: {
      refreshEndpoint: '/auth/refresh',
      tokenStorage: 'secure-store',    // 'secure-store' | 'keychain'
      onUnauthorized: () => router.push('/login'),
    },

    // Retry — optional
    retry: {
      maxAttempts: 3,
      backoff: 'exponential',          // 'exponential' | 'linear' | 'none'
      backoffDelayMs: 500,
    },

    // Offline — optional
    offline: {
      queueMutations: true,
    },

    // Request defaults — optional
    headers: { 'X-App-Version': '2.0.0' },
    timeout: 10_000,
  },
});

Auth options

FieldTypeDescription
refreshEndpointstringPath to POST for a new access token. The current refresh token is sent in the body.
tokenStorage'secure-store' | 'keychain'Which adapter stores the token pair. Requires the corresponding adapter in adapters.
onUnauthorized() => voidCalled after a refresh attempt fails. Navigate to the login screen here.

Retry options

FieldTypeDefaultDescription
maxAttemptsnumberTotal attempts including the first. 3 means one initial request plus two retries.
backoff'exponential' | 'linear' | 'none'Delay strategy between retries.
backoffDelayMsnumberBase delay in ms. Doubles on each attempt for exponential.

Offline options

FieldTypeDescription
queueMutationsbooleanWhen true, failed POST/PUT/PATCH/DELETE requests are queued to SecureStorageProvider and replayed when connectivity returns.

Adapters config

Each adapter key maps to the corresponding port interface. Wire the Expo adapters in production and the dev adapters in Expo Go / simulators.

import { defineConfig } from '@objectifthunes/limestone-sdk';
import { createExpoBiometrics } from '@objectifthunes/limestone-sdk/expo-biometrics';
import { createExpoCamera } from '@objectifthunes/limestone-sdk/expo-camera';
import { createExpoSecureStore } from '@objectifthunes/limestone-sdk/expo-secure-store';
import { createExpoHaptics } from '@objectifthunes/limestone-sdk/expo-haptics';
import { createExpoNotifications } from '@objectifthunes/limestone-sdk/expo-notifications';
import { createExpoPermissions } from '@objectifthunes/limestone-sdk/expo-permissions';
import { createExpoSharing } from '@objectifthunes/limestone-sdk/expo-sharing';
import { createExpoClipboard } from '@objectifthunes/limestone-sdk/expo-clipboard';
import { createExpoLocation } from '@objectifthunes/limestone-sdk/expo-location';
import { createExpoWebBrowser } from '@objectifthunes/limestone-sdk/expo-web-browser';
import { createExpoReview } from '@objectifthunes/limestone-sdk/expo-review';

const config = defineConfig({
  theme: myTheme,
  adapters: {
    biometrics:   createExpoBiometrics(),
    camera:       createExpoCamera(),
    storage:      createExpoSecureStore(),
    haptics:      createExpoHaptics(),
    notifications: createExpoNotifications(),
    permissions:  createExpoPermissions(),
    share:        createExpoSharing(),
    clipboard:    createExpoClipboard(),
    location:     createExpoLocation(),
    webBrowser:   createExpoWebBrowser(),
    review:       createExpoReview(),
  },
});

All adapter keys are optional. Register only the ones your app uses. Hooks that require an unregistered adapter throw at runtime with a descriptive error.

Full adapter key reference

Config keyPortFactory (Expo)Factory (dev)
biometricsBiometricProvidercreateExpoBiometricssame name from ./dev
cameraCameraProvidercreateExpoCamerasame name from ./dev
storageSecureStorageProvidercreateExpoSecureStoresame name from ./dev
hapticsHapticsProvidercreateExpoHapticssame name from ./dev
notificationsNotificationProvidercreateExpoNotificationssame name from ./dev
purchasesPurchaseProvidercreateExpoPurchasessame name from ./dev
permissionsPermissionsProvidercreateExpoPermissionssame name from ./dev
shareShareProvidercreateExpoSharingsame name from ./dev
clipboardClipboardProvidercreateExpoClipboardsame name from ./dev
keychainKeychainProvider(no Expo adapter)createInMemoryKeychain from ./testing
locationLocationProvidercreateExpoLocationsame name from ./dev
mediaLibraryMediaLibraryProvidercreateExpoMediaLibrarysame name from ./dev
mapMapProvidercreateExpoMapsame name from ./dev
webBrowserWebBrowserProvidercreateExpoWebBrowsersame name from ./dev
contactsContactsProvidercreateExpoContactssame name from ./dev
reviewReviewProvidercreateExpoReviewsame name from ./dev

Features config

const config = defineConfig({
  theme: myTheme,
  features: {
    deepLinks: {
      prefixes: ['myapp://', 'https://myapp.example.com'],
    },
    analytics: true,
    crashReporting: true,
  },
});
FieldTypeDescription
deepLinks.prefixesstring[]URL schemes and universal link domains the app handles. Consumed by useDeepLink().
analyticsbooleanEnable analytics event tracking.
crashReportingbooleanEnable crash reporting.

LimestoneProvider

Place LimestoneProvider at the root of the component tree, above all navigation and screen components.

// app/_layout.tsx (Expo Router) or App.tsx
import React from 'react';
import { LimestoneProvider } from '@objectifthunes/limestone-sdk';
import { config } from '../config';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <LimestoneProvider config={config}>
      {children}
    </LimestoneProvider>
  );
}

LimestoneProvider creates and owns:

  • A Store<AppState> (observable, shared across all hooks)
  • An ApiClient (if api was provided in config)
  • A ThemeProvider (activates light or dark theme based on device color scheme)

Access the context directly when needed:

import { useLimestone } from '@objectifthunes/limestone-sdk';

function MyComponent() {
  const { config, store, adapters, apiClient } = useLimestone();
}
FieldTypeDescription
configReadonly<LimestoneConfig>The frozen config object
storeStore<AppState>Observable app state
adaptersAdaptersConfigAll registered adapters
apiClientApiClient | nullNull when no api config was provided

Dev vs production config

// config.ts
import { defineConfig } from '@objectifthunes/limestone-sdk';
import { myTheme } from './theme';

const isDev = process.env.EXPO_PUBLIC_ENV === 'development';

async function createAdapters() {
  if (isDev) {
    const dev = await import('@objectifthunes/limestone-sdk/dev');
    return {
      biometrics:   dev.createExpoBiometrics(),
      camera:       dev.createExpoCamera(),
      storage:      dev.createExpoSecureStore(),
      haptics:      dev.createExpoHaptics(),
    };
  }

  const [
    { createExpoBiometrics },
    { createExpoCamera },
    { createExpoSecureStore },
    { createExpoHaptics },
  ] = await Promise.all([
    import('@objectifthunes/limestone-sdk/expo-biometrics'),
    import('@objectifthunes/limestone-sdk/expo-camera'),
    import('@objectifthunes/limestone-sdk/expo-secure-store'),
    import('@objectifthunes/limestone-sdk/expo-haptics'),
  ]);

  return {
    biometrics: createExpoBiometrics(),
    camera:     createExpoCamera(),
    storage:    createExpoSecureStore(),
    haptics:    createExpoHaptics(),
  };
}

export async function buildConfig() {
  const adapters = await createAdapters();
  return defineConfig({ theme: myTheme, adapters });
}

The dev adapters run entirely in memory — no native modules, no Expo Go restrictions, no simulator quirks.