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
| Field | Type | Description |
|---|---|---|
refreshEndpoint | string | Path 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 | () => void | Called after a refresh attempt fails. Navigate to the login screen here. |
Retry options
| Field | Type | Default | Description |
|---|---|---|---|
maxAttempts | number | — | Total attempts including the first. 3 means one initial request plus two retries. |
backoff | 'exponential' | 'linear' | 'none' | — | Delay strategy between retries. |
backoffDelayMs | number | — | Base delay in ms. Doubles on each attempt for exponential. |
Offline options
| Field | Type | Description |
|---|---|---|
queueMutations | boolean | When 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 key | Port | Factory (Expo) | Factory (dev) |
|---|---|---|---|
biometrics | BiometricProvider | createExpoBiometrics | same name from ./dev |
camera | CameraProvider | createExpoCamera | same name from ./dev |
storage | SecureStorageProvider | createExpoSecureStore | same name from ./dev |
haptics | HapticsProvider | createExpoHaptics | same name from ./dev |
notifications | NotificationProvider | createExpoNotifications | same name from ./dev |
purchases | PurchaseProvider | createExpoPurchases | same name from ./dev |
permissions | PermissionsProvider | createExpoPermissions | same name from ./dev |
share | ShareProvider | createExpoSharing | same name from ./dev |
clipboard | ClipboardProvider | createExpoClipboard | same name from ./dev |
keychain | KeychainProvider | (no Expo adapter) | createInMemoryKeychain from ./testing |
location | LocationProvider | createExpoLocation | same name from ./dev |
mediaLibrary | MediaLibraryProvider | createExpoMediaLibrary | same name from ./dev |
map | MapProvider | createExpoMap | same name from ./dev |
webBrowser | WebBrowserProvider | createExpoWebBrowser | same name from ./dev |
contacts | ContactsProvider | createExpoContacts | same name from ./dev |
review | ReviewProvider | createExpoReview | same name from ./dev |
Features config
const config = defineConfig({
theme: myTheme,
features: {
deepLinks: {
prefixes: ['myapp://', 'https://myapp.example.com'],
},
analytics: true,
crashReporting: true,
},
});
| Field | Type | Description |
|---|---|---|
deepLinks.prefixes | string[] | URL schemes and universal link domains the app handles. Consumed by useDeepLink(). |
analytics | boolean | Enable analytics event tracking. |
crashReporting | boolean | Enable 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(ifapiwas 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();
}
| Field | Type | Description |
|---|---|---|
config | Readonly<LimestoneConfig> | The frozen config object |
store | Store<AppState> | Observable app state |
adapters | AdaptersConfig | All registered adapters |
apiClient | ApiClient | null | Null 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.