Ports Overview
What ports are
Ports are TypeScript interfaces that define what a native capability must provide. The SDK’s core logic depends only on these interfaces, never on specific implementations. Adapters live in src/adapters/ and satisfy the interfaces at runtime via defineConfig.
The deletion test
Remove any adapter directory under src/adapters/. The core still compiles. No adapter is on the import path of any component, hook, store, or form. This is the single mechanical invariant the architecture enforces.
All 16 ports
| Port | File | Purpose |
|---|---|---|
BiometricProvider | biometrics.ts | Fingerprint / Face ID authentication |
CameraProvider | camera.ts | Take pictures via device camera |
SecureStorageProvider | storage.ts | Encrypted key-value storage |
HapticsProvider | haptics.ts | Tactile feedback (impact, notification, selection) |
NotificationProvider | notifications.ts | Local + push notifications, badge count |
PurchaseProvider | purchases.ts | In-app purchases and subscriptions |
PermissionsProvider | permissions.ts | Check and request OS permissions |
ShareProvider | share.ts | Native share sheet |
ClipboardProvider | clipboard.ts | Read/write system clipboard |
KeychainProvider | keychain.ts | Secure credential storage (keychain / keystore) |
LocationProvider | location.ts | GPS position, watch updates, foreground/background |
MediaLibraryProvider | media-library.ts | Pick images/videos, save to library, list albums |
MapProvider | map.ts | Render a native map with markers |
WebBrowserProvider | web-browser.ts | In-app browser (SFSafariViewController / Chrome Custom Tabs) |
ContactsProvider | contacts.ts | Read device contacts, search, request permission |
ReviewProvider | review.ts | Trigger native app-review prompt |
All 16 are defined in src/ports/ and re-exported from the main barrel as export type.
Interface definitions
BiometricProvider
interface BiometricProvider {
authenticate(options?: {
promptMessage?: string;
cancelLabel?: string;
fallbackLabel?: string;
}): Promise<BiometricResult>;
isAvailable(): Promise<BiometricAvailability>;
}
CameraProvider
interface CameraProvider {
takePicture(options?: {
quality?: number;
base64?: boolean;
facing?: 'front' | 'back';
}): Promise<CameraResult>;
requestPermission(): Promise<PermissionStatus>;
}
NotificationProvider
interface NotificationProvider {
requestPermission(): Promise<PermissionStatus>;
getToken(): Promise<string>;
schedule(content: NotificationContent, options?: ScheduleOptions): Promise<string>;
cancel(notificationId: string): Promise<void>;
cancelAll(): Promise<void>;
onReceived(handler: (n: NotificationContent) => void): () => void;
onTapped(handler: (n: NotificationContent) => void): () => void;
getBadgeCount(): Promise<number>;
setBadgeCount(count: number): Promise<void>;
}
PurchaseProvider
interface PurchaseProvider {
getProducts(ids: string[]): Promise<Product[]>;
purchase(productId: string): Promise<PurchaseResult>;
restore(): Promise<PurchaseResult[]>;
getActiveSubscriptions(): Promise<PurchaseResult[]>;
onPurchaseUpdate(handler: (result: PurchaseResult) => void): () => void;
}
Build your own adapter
Implement the port interface, then wire the result into defineConfig. No base class, no registration — the interface contract is the only requirement.
Example: custom CameraProvider using react-native-vision-camera
import type { CameraProvider, CameraResult, PermissionStatus } from '@objectifthunes/limestone-sdk';
import { Camera, useCameraDevice } from 'react-native-vision-camera';
export function createVisionCamera(): CameraProvider {
return {
async requestPermission(): Promise<PermissionStatus> {
const status = await Camera.requestCameraPermission();
return status === 'granted' ? 'granted' : 'denied';
},
async takePicture(options): Promise<CameraResult> {
// Vision Camera requires a ref — wire this to your camera screen
throw new Error('takePicture must be called from a mounted Camera component');
},
};
}
Wire it in limestone.config.ts
import { defineConfig } from '@objectifthunes/limestone-sdk';
import { createVisionCamera } from './adapters/vision-camera';
const config = defineConfig({
theme: myTheme,
api: {
baseUrl: 'https://api.example.com',
auth: {
refreshEndpoint: '/auth/refresh',
onUnauthorized: () => router.push('/login'),
},
},
adapters: {
camera: createVisionCamera(),
// other adapters...
},
});
The rest of the app — useCamera(), components, hooks — requires no changes. The core only sees the CameraProvider interface.