Capabilities

import {
  camera, barcodeScanner, imagePicker, location, haptics, clipboard,
  notifications, biometrics, secureStorage, files, filePicker, share, linking
} from "lakebed-native/capabilities";

Each method returns CapabilityResult<T>:

type CapabilityResult<T> =
  | { ok: true; data: T }
  | { ok: false; code: CapabilityErrorCode; message: string };

Error codes: unsupported_platform, permission_denied, cancelled, fallback_requested, unavailable_hardware, temporarily_unavailable, authentication_failed, runtime_mismatch, unknown.

Storage model (v0)

Camera — happy / denied / simulator

import { camera } from "lakebed-native/capabilities";

const result = await camera.takePhoto();
if (result.ok) {
  // result.data: { uri: string; width?: number; height?: number }
  await savePhoto.mutate({ uri: result.data.uri });
} else if (result.code === "permission_denied") {
  // User denied camera — show settings hint
} else if (result.code === "unsupported_platform") {
  // Expo modules missing (web preview, wrong shell)
}

Simulator: iOS simulator supports camera UI with a stub image when Expo modules are present; Android emulator may return permission_denied or unsupported_platform without a camera.

Image picker

const result = await imagePicker.pick();
// same result shape as camera.takePhoto()

QR / barcode scanner

import { barcodeScanner } from "lakebed-native/capabilities";

const result = await barcodeScanner.scan();
if (result.ok) {
  const { type, data } = result.data; // defaults to QR codes
} else if (result.code === "permission_denied") {
  // User denied camera access
} else if (result.code === "unavailable_hardware") {
  // Scanner UI unavailable on this device / OS
}

barcodeScanner.scan({ barcodeTypes: ["qr", "code128"], timeoutMs: 30000 }) opens the native scanner UI. timeoutMs must be positive and defaults to 60000 so a dismissed scanner does not leave callers blocked indefinitely. Only one live scanner can run at a time. barcodeScanner.scanFromImage(uri) scans a local image URI. Both use the shell's existing expo-camera dependency.

Location — happy / denied

import { location } from "lakebed-native/capabilities";

const result = await location.getCurrent();
if (result.ok) {
  const { latitude, longitude, accuracy } = result.data;
} else if (result.code === "permission_denied") {
  // Foreground location denied
}

Simulator: iOS Simulator → Features → Location → Custom Location. Android emulator: Extended controls → Location.

Other capabilities

Module Methods Notes
haptics impact("light" | "medium" | "heavy") No-op on simulator
clipboard set(text), get()
notifications requestPermission(), scheduleLocal({ title, body?, data?, seconds? }), cancelScheduled(id), getPushToken(projectId?) getPushToken returns an Expo push token; delivery is still app/server-owned
biometrics getAvailability(), authenticate(options?) Face ID / Touch ID / Android biometrics when enrolled
secureStorage set(key, value), get(key) Keychain / Keystore
files read(uri), write(uri, content) Local paths only
filePicker pick({ type?, multiple?, copyToCacheDirectory? }) Returns local document URIs
share text(message) Native share sheet
linking open(url) External URLs

Template with camera + location

npx lakebed-native new my-notes --template camera-note
# or location-checkin for GPS-only

See templates/camera-note/app/index.tsx for a full UI example.