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.takePhoto()andimagePicker.pick()return local device URIs (file://…on device, simulator paths on sim).- Persist URI strings in your Lakebed DB via mutations — there is no built-in image CDN or blob upload API in v0.
- Use
files.read/files.writefor local file content when needed.
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.