# Teardown Documentation (Complete) For module-specific documentation, use: - /docs/llms/react-native.txt - React Native SDK - /docs/llms/cli.txt - CLI - /docs/llms/navigation.txt - Navigation - /docs/llms/queries.txt - Queries --- # Configuration (/docs/cli/config) The `teardown.config.ts` file is the central configuration for your React Native app. It defines app metadata, platform settings, and plugins. Basic Structure [#basic-structure] ```typescript import { defineConfig } from "@teardown/cli"; export default defineConfig({ name: "My App", slug: "myapp", version: "1.0.0", ios: { ... }, android: { ... }, plugins: [ ... ], navigation: { ... }, }); ``` Core Options [#core-options] | Option | Type | Required | Description | | --------- | ------ | -------- | ----------------------------------------- | | `name` | string | Yes | Display name of the app | | `slug` | string | Yes | URL-safe identifier (used for deep links) | | `version` | string | Yes | Semantic version (e.g., "1.0.0") | iOS Configuration [#ios-configuration] ```typescript ios: { bundleIdentifier: "com.example.myapp", buildNumber: 1, deploymentTarget: "15.0", infoPlist: { // Additional Info.plist entries }, entitlements: { // App entitlements }, associatedDomains: ["applinks:myapp.com"], } ``` iOS Options [#ios-options] | Option | Type | Default | Description | | ------------------- | --------- | -------- | -------------------------------------- | | `bundleIdentifier` | string | Required | iOS bundle ID | | `buildNumber` | number | `1` | Build number for App Store | | `deploymentTarget` | string | `"15.0"` | Minimum iOS version | | `infoPlist` | object | `{}` | Additional Info.plist entries | | `entitlements` | object | `{}` | App entitlements | | `associatedDomains` | string\[] | `[]` | Associated domains for universal links | | `usesAppleSignIn` | boolean | `false` | Enable Sign in with Apple | | `backgroundColor` | string | - | Launch screen background color | | `icon` | string | - | Path to app icon | Android Configuration [#android-configuration] ```typescript android: { packageName: "com.example.myapp", versionCode: 1, minSdkVersion: 24, targetSdkVersion: 34, permissions: [ "INTERNET", "CAMERA", ], intentFilters: [ ... ], } ``` Android Options [#android-options] | Option | Type | Default | Description | | ------------------- | --------- | -------- | ----------------------------- | | `packageName` | string | Required | Android package name | | `versionCode` | number | `1` | Version code for Play Store | | `minSdkVersion` | number | `24` | Minimum Android SDK version | | `targetSdkVersion` | number | `34` | Target Android SDK version | | `compileSdkVersion` | number | `34` | Compile SDK version | | `permissions` | string\[] | `[]` | Android permissions | | `intentFilters` | object\[] | `[]` | Intent filters for deep links | | `backgroundColor` | string | - | Splash screen background | | `icon` | string | - | Path to app icon | Plugins [#plugins] Add native capabilities: ```typescript plugins: [ // Simple string format "camera", "location", // With configuration ["camera", { cameraPermission: "Take photos for your profile", }], ] ``` See [Plugins](/docs/cli/plugins) for all available plugins. Navigation [#navigation] Configure file-based navigation: ```typescript navigation: { routesDir: "./src/_routes", generatedDir: "./.teardown", verbose: false, } ``` | Option | Type | Default | Description | | -------------- | ------- | ----------------- | ------------------------- | | `routesDir` | string | `"./src/_routes"` | Route files directory | | `generatedDir` | string | `"./.teardown"` | Generated types directory | | `verbose` | boolean | `false` | Verbose logging | Full Example [#full-example] ```typescript import { defineConfig } from "@teardown/cli"; export default defineConfig({ name: "My Awesome App", slug: "myawesomeapp", version: "1.2.0", ios: { bundleIdentifier: "com.mycompany.myawesomeapp", buildNumber: 12, deploymentTarget: "15.0", associatedDomains: [ "applinks:myawesomeapp.com", "webcredentials:myawesomeapp.com", ], infoPlist: { ITSAppUsesNonExemptEncryption: false, }, }, android: { packageName: "com.mycompany.myawesomeapp", versionCode: 12, minSdkVersion: 24, targetSdkVersion: 34, permissions: [ "INTERNET", "ACCESS_NETWORK_STATE", ], intentFilters: [{ action: "VIEW", autoVerify: true, data: [{ scheme: "https", host: "myawesomeapp.com", pathPrefix: "/", }], category: ["BROWSABLE", "DEFAULT"], }], }, plugins: [ "camera", ["location", { locationPermission: "Find nearby features", backgroundLocation: false, }], "push-notifications", "biometrics", ["firebase", { googleServicesFile: "./GoogleService-Info.plist", googleServicesJsonFile: "./google-services.json", }], ], navigation: { routesDir: "./src/_routes", generatedDir: "./.teardown", }, }); ``` Validation [#validation] Validate your configuration: ```bash teardown validate ``` Shows errors and warnings with suggestions: ``` Configuration valid! Summary: Name: My Awesome App Slug: myawesomeapp Version: 1.2.0 iOS: com.mycompany.myawesomeapp (build 12) Android: com.mycompany.myawesomeapp (version code 12) Plugins: 5 ``` Environment Variables [#environment-variables] Reference environment variables in config: ```typescript import { defineConfig } from "@teardown/cli"; export default defineConfig({ name: "My App", slug: "myapp", version: process.env.APP_VERSION || "1.0.0", ios: { bundleIdentifier: process.env.IOS_BUNDLE_ID || "com.example.myapp", }, }); ``` Config File Formats [#config-file-formats] The CLI looks for config files in this order: 1. `teardown.config.ts` 2. `teardown.config.js` 3. `teardown.config.mjs` 4. `launchpad.config.ts` (legacy) Type Safety [#type-safety] The `defineConfig` helper provides full TypeScript intellisense: ```typescript import { defineConfig } from "@teardown/cli"; export default defineConfig({ // Full autocomplete for all options // Type errors for invalid values }); ``` Related [#related] * [init](/docs/cli/init) - Initialize project * [prebuild](/docs/cli/prebuild) - Generate native projects * [Plugins](/docs/cli/plugins) - Available plugins # dev (/docs/cli/dev) The `dev` command starts the Metro bundler for development. Use this when you want to run the bundler separately from the build process. Usage [#usage] ```bash teardown dev [options] ``` Options [#options] | Option | Description | Default | | ----------------------- | ---------------------------------------------- | ------- | | `-p, --port ` | Port to run Metro on | `8081` | | `--reset-cache` | Reset the Metro cache | `false` | | `--platform ` | Platform to target: `ios`, `android`, or `all` | `all` | | `--verbose` | Enable verbose logging | `false` | Examples [#examples] Start Development Server [#start-development-server] ```bash teardown dev ``` Custom Port [#custom-port] ```bash teardown dev --port 8082 ``` Reset Cache [#reset-cache] ```bash teardown dev --reset-cache ``` Useful when you see stale code or bundler issues. Verbose Output [#verbose-output] ```bash teardown dev --verbose ``` Shows detailed logging including route generation. Features [#features] Interactive Mode [#interactive-mode] Metro runs in interactive mode with keyboard shortcuts: | Key | Action | | --- | ------------------- | | `r` | Reload the app | | `d` | Open developer menu | | `i` | Run on iOS | | `a` | Run on Android | | `j` | Open debugger | Route Pre-Generation [#route-pre-generation] If `@teardown/navigation-metro` is installed, routes are pre-generated before Metro starts: ``` Generated route types Starting Metro bundler... ``` Metro Config Check [#metro-config-check] The CLI checks for `metro.config.js` and suggests optimizations: ``` Tip: Add @teardown/metro-config for 36x faster builds ``` When to Use [#when-to-use] Use `teardown dev` when: * You want to start Metro separately from building * You're running the app from Xcode or Android Studio directly * You have Metro running in one terminal while developing Use `teardown run` when: * You want to build and run in one command * You're starting fresh development * You want automatic device management Metro Configuration [#metro-configuration] With @teardown/metro-config [#with-teardownmetro-config] For optimized builds, use the Teardown Metro config: ```js // metro.config.js const { getDefaultConfig } = require("@react-native/metro-config"); const { withTeardown } = require("@teardown/metro-config"); const config = getDefaultConfig(__dirname); module.exports = withTeardown(config); ``` With Navigation [#with-navigation] Add navigation type generation: ```js // metro.config.js const { getDefaultConfig } = require("@react-native/metro-config"); const { withTeardown } = require("@teardown/metro-config"); const { withTeardownNavigation } = require("@teardown/navigation-metro"); const config = getDefaultConfig(__dirname); module.exports = withTeardownNavigation(withTeardown(config)); ``` Troubleshooting [#troubleshooting] Port Already in Use [#port-already-in-use] ``` Port 8081 is already in use ``` Use a different port: ```bash teardown dev --port 8082 ``` Or find and kill the existing process: ```bash lsof -i :8081 kill -9 ``` Stale Bundle [#stale-bundle] If changes aren't appearing: ```bash teardown dev --reset-cache ``` No metro.config.js [#no-metroconfigjs] ``` No metro.config.js found. Using default React Native config. ``` This is fine for basic usage. For optimizations, create a metro config. Navigation Types Not Generating [#navigation-types-not-generating] Ensure `@teardown/navigation-metro` is installed and routes directory exists: ```bash bun add -D @teardown/navigation-metro mkdir -p src/_routes ``` Related [#related] * [run](/docs/cli/run) - Build and run the app * [Navigation](/docs/navigation) - Navigation setup # CLI (/docs/cli) The Teardown CLI (`@teardown/cli`) is a development toolkit for React Native apps. It handles project initialization, native code generation, and running your app on devices and simulators. > ⚠️ **Early Development** - The Teardown SDKs and APIs are under active development. Expect frequent breaking changes. Not recommended for production apps. Features [#features] * **Project initialization** - Bootstrap new React Native projects with best practices * **Native code generation** - Generate iOS and Android native projects from configuration * **Plugin system** - Extend your app with camera, location, biometrics, and more * **Device management** - Build and run on simulators and physical devices * **Metro integration** - Start the bundler with optimized settings Installation [#installation] Install globally: ```bash bun add -g @teardown/cli ``` Or use directly with npx: ```bash npx @teardown/cli init MyApp ``` Quick Start [#quick-start] 1. Initialize a New Project [#1-initialize-a-new-project] ```bash teardown init MyApp cd MyApp ``` This creates: * `teardown.config.ts` - App configuration * `src/` - Source code with routes and components * Config files for Metro, Babel, and React Native 2. Generate Native Projects [#2-generate-native-projects] ```bash teardown prebuild ``` Creates `ios/` and `android/` directories with native code. 3. Run on Device [#3-run-on-device] ```bash # Run on iOS simulator teardown run ios # Run on Android emulator teardown run android ``` Commands Overview [#commands-overview] | Command | Description | | -------------------------------- | ------------------------------------ | | [`init`](/docs/cli/init) | Initialize a new project | | [`prebuild`](/docs/cli/prebuild) | Generate native iOS/Android projects | | [`run`](/docs/cli/run) | Build and run on device/simulator | | [`dev`](/docs/cli/dev) | Start the Metro bundler | | [`plugins`](/docs/cli/plugins) | List and manage plugins | | [`validate`](/docs/cli/config) | Validate configuration | | [`upgrade`](/docs/cli/upgrade) | Upgrade Teardown packages | Typical Workflow [#typical-workflow] ```bash # 1. Create new project teardown init MyApp cd MyApp # 2. Configure plugins in teardown.config.ts # (add camera, location, etc.) # 3. Generate native code teardown prebuild # 4. Run on iOS teardown run ios # 5. Make changes, auto-reload # (Metro handles hot reloading) # 6. Add more native features # (edit config, run prebuild again) ``` Requirements [#requirements] * Node.js 18+ * Bun (recommended) or npm * Xcode (for iOS development) * Android Studio (for Android development) * CocoaPods (installed automatically if needed) Next Steps [#next-steps] * [init](/docs/cli/init) - Initialize a new project * [prebuild](/docs/cli/prebuild) - Generate native projects * [run](/docs/cli/run) - Run on devices * [Configuration](/docs/cli/config) - Configure your app # init (/docs/cli/init) The `init` command creates a new Teardown React Native project with all necessary configuration files and folder structure. Usage [#usage] ```bash teardown init [options] ``` Arguments [#arguments] | Argument | Description | | -------- | ------------------- | | `name` | App name (required) | Options [#options] | Option | Description | Default | | ---------------- | ---------------------------- | ------- | | `-f, --force` | Overwrite existing files | `false` | | `--skip-src` | Skip generating src folder | `false` | | `--skip-git` | Skip generating .gitignore | `false` | | `--skip-install` | Skip installing dependencies | `false` | | `-v, --verbose` | Show detailed output | `false` | Examples [#examples] Basic Initialization [#basic-initialization] ```bash teardown init MyApp ``` Creates a new project with: * `teardown.config.ts` - App configuration * `src/` folder with routes and components * Config files (metro, babel, react-native) * `.gitignore` * Dependencies installed Force Overwrite [#force-overwrite] ```bash teardown init MyApp --force ``` Overwrites existing files if present. Skip Steps [#skip-steps] ```bash # Initialize without installing dependencies teardown init MyApp --skip-install # Initialize without src folder teardown init MyApp --skip-src # Initialize without .gitignore teardown init MyApp --skip-git ``` Generated Files [#generated-files] teardown.config.ts [#teardownconfigts] Main configuration file: ```typescript import { defineConfig } from "@teardown/cli"; export default defineConfig({ name: "MyApp", slug: "myapp", version: "1.0.0", ios: { bundleIdentifier: "com.example.myapp", buildNumber: 1, deploymentTarget: "15.0", }, android: { packageName: "com.example.myapp", versionCode: 1, minSdkVersion: 24, targetSdkVersion: 34, }, plugins: [], }); ``` src/ Folder Structure [#src-folder-structure] ``` src/ ├── _routes/ │ ├── _layout.tsx # Root layout │ └── index.tsx # Home screen ├── app/ │ └── index.tsx # Router setup ├── components/ # Shared components ├── core/ # Core utilities └── providers/ └── app.provider.tsx # App providers ``` Config Files [#config-files] | File | Purpose | | ------------------------ | ------------------------------- | | `metro.config.js` | Metro bundler configuration | | `babel.config.js` | Babel transpiler configuration | | `react-native.config.js` | React Native CLI configuration | | `Gemfile` | Ruby dependencies for CocoaPods | | `dev-client.config.ts` | Development client settings | .gitignore [#gitignore] Includes patterns for: * `ios/` and `android/` (generated native code) * `node_modules/` * `.teardown/` (generated types) * Build artifacts * Environment files What's Next [#whats-next] After initialization: 1. **Configure plugins** - Add native capabilities in `teardown.config.ts` ```typescript export default defineConfig({ // ... plugins: [ "camera", "location", ], }); ``` 2. **Generate native projects** ```bash teardown prebuild ``` 3. **Run on device** ```bash teardown run ios # or teardown run android ``` Troubleshooting [#troubleshooting] Config Already Exists [#config-already-exists] ``` Configuration file already exists ``` Use `--force` to overwrite, or manually update the existing config. Dependencies Failed to Install [#dependencies-failed-to-install] If `bun install` fails, try: ```bash teardown init MyApp --skip-install cd MyApp bun install # or npm install ``` Existing src/ Folder [#existing-src-folder] The command preserves existing `src/` if present. Use `--force` to overwrite. # Plugins (/docs/cli/plugins) Plugins add native capabilities to your app without manual native code changes. Configure plugins in `teardown.config.ts` and they're applied during prebuild. Using Plugins [#using-plugins] List Available Plugins [#list-available-plugins] ```bash teardown plugins list ``` Get Plugin Info [#get-plugin-info] ```bash teardown plugins info camera ``` Shows: * Plugin name and description * Supported platforms * Configuration options * Usage examples Configuration [#configuration] Add plugins to your `teardown.config.ts`: ```typescript import { defineConfig } from "@teardown/cli"; export default defineConfig({ // ... plugins: [ // Simple string format "camera", "location", // With configuration ["camera", { cameraPermission: "We need camera access to scan documents", }], ["location", { locationPermission: "We need your location for navigation", backgroundLocation: true, }], ], }); ``` Available Plugins [#available-plugins] Camera [#camera] Camera access for photos and videos. ```typescript plugins: [ ["camera", { cameraPermission: "Take photos for your profile", microphonePermission: "Record video with audio", }], ] ``` | Option | Type | Description | | ---------------------- | ------ | --------------------------------- | | `cameraPermission` | string | iOS camera permission message | | `microphonePermission` | string | iOS microphone permission message | Location [#location] Location services and GPS access. ```typescript plugins: [ ["location", { locationPermission: "Find nearby restaurants", backgroundLocation: false, }], ] ``` | Option | Type | Description | | -------------------- | ------- | -------------------------- | | `locationPermission` | string | Permission message | | `backgroundLocation` | boolean | Enable background location | Push Notifications [#push-notifications] Push notification support. ```typescript plugins: [ ["push-notifications", { enableBadge: true, enableSound: true, }], ] ``` | Option | Type | Description | | ------------- | ------- | ------------------------ | | `enableBadge` | boolean | Show badge on app icon | | `enableSound` | boolean | Play notification sounds | Biometrics [#biometrics] Face ID / Touch ID authentication. ```typescript plugins: [ ["biometrics", { faceIDPermission: "Authenticate securely with Face ID", }], ] ``` | Option | Type | Description | | ------------------ | ------ | ------------------------------ | | `faceIDPermission` | string | iOS Face ID permission message | Deep Linking [#deep-linking] URL scheme and universal link handling. ```typescript plugins: [ ["deep-linking", { scheme: "myapp", associatedDomains: ["applinks:myapp.com"], }], ] ``` | Option | Type | Description | | ------------------- | --------- | ---------------------- | | `scheme` | string | Custom URL scheme | | `associatedDomains` | string\[] | iOS associated domains | Firebase [#firebase] Firebase SDK integration. ```typescript plugins: [ ["firebase", { googleServicesFile: "./GoogleService-Info.plist", googleServicesJsonFile: "./google-services.json", }], ] ``` | Option | Type | Description | | ------------------------ | ------ | --------------------------- | | `googleServicesFile` | string | Path to iOS config file | | `googleServicesJsonFile` | string | Path to Android config file | Contacts [#contacts] Access to device contacts. ```typescript plugins: [ ["contacts", { contactsPermission: "Invite friends to the app", }], ] ``` Calendar [#calendar] Calendar access. ```typescript plugins: [ ["calendar", { calendarPermission: "Schedule events in your calendar", }], ] ``` Photo Library [#photo-library] Photo library access. ```typescript plugins: [ ["photo-library", { photoLibraryPermission: "Select photos to upload", savePhotosPermission: "Save photos to your library", }], ] ``` Bluetooth [#bluetooth] Bluetooth connectivity. ```typescript plugins: [ ["bluetooth", { bluetoothPermission: "Connect to Bluetooth devices", }], ] ``` Sign in with Apple [#sign-in-with-apple] Apple authentication. ```typescript plugins: [ "sign-in-with-apple", ] ``` No configuration options required. Plugin Processing [#plugin-processing] During `teardown prebuild`, plugins: 1. **Add dependencies** - Native libraries are added to Podfile/build.gradle 2. **Configure permissions** - Permission strings added to Info.plist/AndroidManifest 3. **Modify native files** - Necessary code changes applied 4. **Generate code** - Any required boilerplate generated Plugin Order [#plugin-order] Plugins are processed in order. Some plugins may depend on others: ```typescript plugins: [ // Firebase should come before push notifications if using FCM "firebase", "push-notifications", // Independent plugins can be in any order "camera", "location", ] ``` Troubleshooting [#troubleshooting] Unknown Plugin [#unknown-plugin] ``` Unknown plugin: xyz ``` Check available plugins: ```bash teardown plugins list ``` Plugin Conflicts [#plugin-conflicts] If two plugins conflict: 1. Check plugin documentation for compatibility 2. Try reordering plugins 3. Run `teardown prebuild --clean` to start fresh Permissions Not Working [#permissions-not-working] After adding a permission plugin: ```bash teardown prebuild --clean teardown run ios ``` iOS may cache permissions - delete the app and reinstall. Related [#related] * [Configuration](/docs/cli/config) - Full config reference * [prebuild](/docs/cli/prebuild) - Generate native projects # prebuild (/docs/cli/prebuild) The `prebuild` command generates native iOS and Android projects based on your `teardown.config.ts` configuration. Usage [#usage] ```bash teardown prebuild [options] ``` Options [#options] | Option | Description | Default | | --------------------------- | ------------------------------------------- | ------- | | `-p, --platform ` | Target platform: `ios`, `android`, or `all` | `all` | | `-c, --clean` | Clean native directories before prebuild | `false` | | `-d, --dry-run` | Preview changes without applying | `false` | | `-v, --verbose` | Enable verbose logging | `false` | | `--skip-validation` | Skip configuration validation | `false` | Examples [#examples] Build All Platforms [#build-all-platforms] ```bash teardown prebuild ``` Generates both `ios/` and `android/` directories. Build Single Platform [#build-single-platform] ```bash # iOS only teardown prebuild --platform ios # Android only teardown prebuild --platform android ``` Clean Build [#clean-build] ```bash teardown prebuild --clean ``` Removes existing native directories before generating new ones. Preview Changes [#preview-changes] ```bash teardown prebuild --dry-run ``` Shows what would be generated without actually creating files. What Gets Generated [#what-gets-generated] iOS (ios/) [#ios-ios] * Xcode project and workspace * `Podfile` with dependencies * `Info.plist` configuration * App icons and launch screen * Native modules from plugins Android (android/) [#android-android] * Gradle project structure * `AndroidManifest.xml` * `build.gradle` files * App icons and resources * Native modules from plugins Plugin Processing [#plugin-processing] Plugins modify the native projects during prebuild: ```typescript // teardown.config.ts export default defineConfig({ plugins: [ "camera", ["location", { permission: "We need your location for navigation" }], ], }); ``` Each plugin: 1. Adds required native dependencies 2. Configures permissions 3. Modifies native files as needed Build Pipeline [#build-pipeline] The prebuild runs these phases: 1. **Validate** - Check configuration 2. **Clean** (if `--clean`) - Remove existing native dirs 3. **Generate** - Create native project structure 4. **Configure** - Apply settings from config 5. **Plugins** - Run plugin modifications 6. **Finalize** - Complete setup Incremental Builds [#incremental-builds] Subsequent prebuilds are incremental by default: * Existing files are updated, not recreated * Plugin changes are applied * Use `--clean` for a fresh start After Prebuild [#after-prebuild] iOS [#ios] Install CocoaPods dependencies: ```bash cd ios && pod install ``` Or let `teardown run ios` handle it automatically. Android [#android] No additional steps needed. Gradle syncs automatically. Troubleshooting [#troubleshooting] Validation Errors [#validation-errors] ``` Failed to load configuration ``` Check your `teardown.config.ts` for errors: ```bash teardown validate ``` Plugin Errors [#plugin-errors] ``` Unknown plugin: xyz ``` Use `teardown plugins list` to see available plugins. Clean Build Issues [#clean-build-issues] If you see strange errors after changes: ```bash teardown prebuild --clean ``` iOS Pods Not Found [#ios-pods-not-found] After prebuild, if pods are missing: ```bash cd ios && pod install --repo-update ``` Best Practices [#best-practices] 1. **Version control** - Add `ios/` and `android/` to `.gitignore` 2. **Clean builds** - Use `--clean` when updating plugins or major config 3. **Dry run first** - Preview changes with `--dry-run` on production configs 4. **Check logs** - Use `--verbose` to debug issues Related [#related] * [Configuration](/docs/cli/config) - Configure your app * [Plugins](/docs/cli/plugins) - Available plugins * [run](/docs/cli/run) - Run on devices # run (/docs/cli/run) The `run` command builds your app and launches it on a device or simulator. Usage [#usage] ```bash teardown run [options] ``` Arguments [#arguments] | Argument | Description | | ---------- | ----------------------------- | | `platform` | `ios` or `android` (required) | Options [#options] | Option | Description | Default | | -------------------------- | --------------------------------------- | ------- | | `-d, --device ` | Specific device name or ID | - | | `--no-picker` | Skip device picker, use first available | `false` | | `--release` | Run in release mode | `false` | | `--port ` | Metro bundler port | `8081` | | `--no-bundler` | Skip starting Metro bundler | `false` | | `--configuration ` | iOS build configuration | `Debug` | | `--scheme ` | iOS Xcode scheme to build | - | | `--variant ` | Android build variant | `debug` | | `--clean` | Clean build before running | `false` | Examples [#examples] Run on iOS Simulator [#run-on-ios-simulator] ```bash teardown run ios ``` Shows a device picker if multiple simulators are available. Run on Android Emulator [#run-on-android-emulator] ```bash teardown run android ``` Run on Specific Device [#run-on-specific-device] ```bash # By name teardown run ios --device "iPhone 15 Pro" # By UDID teardown run ios --device "ABCD1234-5678-90EF-GHIJ-KLMNOPQRSTUV" ``` Skip Device Picker [#skip-device-picker] ```bash # Use first available device teardown run ios --no-picker ``` Release Build [#release-build] ```bash teardown run ios --release ``` Custom Metro Port [#custom-metro-port] ```bash teardown run ios --port 8082 ``` Without Metro Bundler [#without-metro-bundler] ```bash # Useful when Metro is already running teardown run ios --no-bundler ``` Clean Build [#clean-build] ```bash teardown run ios --clean ``` What Happens [#what-happens] The `run` command: 1. **Checks for native project** - Auto-runs `prebuild` if needed 2. **Installs CocoaPods** (iOS) - If pods aren't installed 3. **Starts Metro bundler** - In background (unless `--no-bundler` or `--release`) 4. **Detects devices** - Lists available devices/simulators 5. **Shows device picker** - Unless `--device` or `--no-picker` 6. **Boots simulator** (if needed) - For iOS simulators 7. **Builds the app** - Using Xcode or Gradle 8. **Installs the app** - On selected device 9. **Launches the app** - Via deep link with bundler URL 10. **Attaches Metro** - Brings bundler to foreground Device Picker [#device-picker] When multiple devices are available, an interactive picker appears: ``` ? Select a device ❯ iPhone 15 Pro (Booted) iPhone 15 (Shutdown) iPhone SE (3rd generation) (Shutdown) iPad Pro (Shutdown) ``` Use arrow keys to navigate, Enter to select. iOS-Specific Options [#ios-specific-options] Scheme [#scheme] Use a specific Xcode scheme: ```bash teardown run ios --scheme MyApp-Development ``` Configuration [#configuration] Specify build configuration: ```bash teardown run ios --configuration Release ``` Android-Specific Options [#android-specific-options] Variant [#variant] Specify build variant: ```bash teardown run android --variant release teardown run android --variant stagingDebug ``` Auto-Prebuild [#auto-prebuild] If native projects don't exist, `run` automatically triggers `prebuild`: ```bash # This works even without ios/ folder teardown run ios # → Runs prebuild first, then builds and runs ``` CocoaPods Installation [#cocoapods-installation] For iOS, if Pods aren't installed, the CLI handles it: ``` Installing CocoaPods dependencies... (45s) - installing React-Core ``` Progress is shown with current activity and elapsed time. Troubleshooting [#troubleshooting] No Devices Found [#no-devices-found] ``` No iOS simulators found ``` * Open Xcode and install simulators: Xcode → Settings → Platforms * For physical devices, connect via USB and trust the computer Build Failed [#build-failed] Check the error output. Common fixes: ```bash # Clean and rebuild teardown run ios --clean # Or rebuild from scratch teardown prebuild --clean teardown run ios ``` Metro Already Running [#metro-already-running] ``` Port 8081 is already in use ``` Either: * Stop the existing Metro process * Use a different port: `teardown run ios --port 8082` * Skip bundler: `teardown run ios --no-bundler` CocoaPods Failed [#cocoapods-failed] If pod install fails: ```bash cd ios pod install --repo-update ``` Or delete and reinstall: ```bash rm -rf ios/Pods ios/Podfile.lock cd ios && pod install ``` App Won't Launch [#app-wont-launch] If the app builds but doesn't launch: ```bash # Try with verbose logging teardown run ios --verbose ``` Check that your app scheme matches your URL scheme in `teardown.config.ts`. Related [#related] * [dev](/docs/cli/dev) - Start Metro bundler only * [prebuild](/docs/cli/prebuild) - Generate native projects # upgrade (/docs/cli/upgrade) The `upgrade` command updates Teardown packages to the latest versions and refreshes configuration files. Usage [#usage] ```bash teardown upgrade [options] ``` Options [#options] | Option | Description | Default | | ---------------- | -------------------------------------------------- | ------- | | `-f, --force` | Force upgrade config files even if they exist | `false` | | `--skip-install` | Skip running bun install after upgrade | `false` | | `--skip-config` | Skip upgrading config files | `false` | | `--dry-run` | Show what would be upgraded without making changes | `false` | Examples [#examples] Standard Upgrade [#standard-upgrade] ```bash teardown upgrade ``` Upgrades all Teardown packages and config files. Preview Changes [#preview-changes] ```bash teardown upgrade --dry-run ``` Shows what would be upgraded without making changes. Force Config Updates [#force-config-updates] ```bash teardown upgrade --force ``` Overwrites config files even if they've been modified. Packages Only [#packages-only] ```bash teardown upgrade --skip-config ``` Updates packages without touching config files. What Gets Upgraded [#what-gets-upgraded] Packages [#packages] The following packages are checked and updated: | Package | Description | | ---------------------------- | ----------------------- | | `@teardown/cli` | CLI tool | | `@teardown/dev-client` | Development client | | `@teardown/navigation` | Navigation library | | `@teardown/metro-config` | Metro configuration | | `@teardown/navigation-metro` | Navigation Metro plugin | Config Files [#config-files] If not skipped, these files are updated: | File | Purpose | | ------------------------ | ------------------------------- | | `metro.config.js` | Metro bundler configuration | | `babel.config.js` | Babel transpiler configuration | | `react-native.config.js` | React Native CLI configuration | | `Gemfile` | Ruby dependencies for CocoaPods | Upgrade Process [#upgrade-process] 1. **Check versions** - Compares installed vs. latest versions 2. **Update package.json** - Sets new version numbers 3. **Update config files** - Refreshes with latest templates 4. **Run install** - Installs updated packages Output [#output] ``` Checking for updates... Packages to update: @teardown/cli: ^2.0.70 → ^2.0.79 @teardown/navigation: ^2.0.70 → ^2.0.79 Config files to update: metro.config.js babel.config.js Updating package.json... Updating config files... Running bun install... Upgrade complete! ``` After Upgrading [#after-upgrading] Rebuild Native Projects [#rebuild-native-projects] Major version upgrades may require rebuilding: ```bash teardown prebuild --clean ``` Clear Metro Cache [#clear-metro-cache] Clear bundler cache after significant updates: ```bash teardown dev --reset-cache ``` Check for Breaking Changes [#check-for-breaking-changes] Review the changelog for breaking changes: ```bash # Check package changelog on npm npm info @teardown/cli changelog ``` Handling Conflicts [#handling-conflicts] Modified Config Files [#modified-config-files] If you've customized config files: ``` Config files modified locally: metro.config.js (modified) Use --force to overwrite, or --skip-config to preserve. ``` Options: * Use `--force` to overwrite with new templates * Use `--skip-config` to keep your changes * Manually merge changes Version Mismatches [#version-mismatches] Keep Teardown packages in sync: ```bash # All packages should use the same version teardown upgrade ``` Mixing versions may cause compatibility issues. Troubleshooting [#troubleshooting] Upgrade Failed [#upgrade-failed] If the upgrade fails: ```bash # Check current versions npm ls @teardown/cli # Manual update bun add @teardown/cli@latest ``` Install Failed [#install-failed] If `bun install` fails after upgrade: ```bash # Try manual install teardown upgrade --skip-install bun install # Or clear and reinstall rm -rf node_modules bun.lock bun install ``` Config Syntax Errors [#config-syntax-errors] If updated config files have errors: ```bash # Regenerate with force teardown upgrade --force # Or reinitialize teardown init YourAppName --force ``` Related [#related] * [Configuration](/docs/cli/config) - Config file reference * [init](/docs/cli/init) - Initialize project # Introduction (/docs/introduction) Welcome to Teardown. We build better tools for React Native Engineers to build better products and ship products faster. Packages [#packages] Force Updates [#force-updates] The [`@teardown/force-updates`](/docs/react-native) SDK provides device identity, force updates, logging, and analytics for React Native and Expo applications. * [Getting Started](/docs/react-native/getting-started) - Installation and setup * [Core Concepts](/docs/react-native/core-concepts) - Architecture overview * [Adapters](/docs/react-native/adapters) - Platform adapters for storage, device info, and notifications Navigation [#navigation] The [`@teardown/navigation`](/docs/navigation) package provides type-safe, file-based navigation for React Native applications. * [Getting Started](/docs/navigation/getting-started) - Setup guide * [File-Based Routing](/docs/navigation/file-routing) - Route conventions * [Type-Safe Navigation](/docs/navigation/type-safe-nav) - Using typed hooks CLI [#cli] The [`@teardown/cli`](/docs/cli) is a development toolkit for React Native apps, handling project initialization, native code generation, and device management. * [init](/docs/cli/init) - Initialize a new project * [prebuild](/docs/cli/prebuild) - Generate native projects * [run](/docs/cli/run) - Build and run on devices Queries [#queries] The [`@teardown/queries`](/docs/queries) package provides a lightweight TanStack Query wrapper for type-safe, composable data fetching. * [Core Concepts](/docs/queries/core-concepts) - QueryClient architecture * [Queries](/docs/queries/queries) - Standard and infinite queries * [Mutations](/docs/queries/mutations) - Mutations and cache invalidation # API Reference (/docs/navigation/api-reference) Hooks [#hooks] useTypedNavigation [#usetypednavigation] Returns a type-safe navigation object. ```typescript function useTypedNavigation(): TypedNavigation; ``` TypedNavigation Interface [#typednavigation-interface] ```typescript interface TypedNavigation { navigate( ...args: ParamsFor extends undefined ? [path: T] : [path: T, params: ParamsFor] ): void; push( ...args: ParamsFor extends undefined ? [path: T] : [path: T, params: ParamsFor] ): void; replace( ...args: ParamsFor extends undefined ? [path: T] : [path: T, params: ParamsFor] ): void; goBack(): void; canGoBack(): boolean; reset(state: NavigationState): void; setParams(params: Partial>): void; getParent(): TypedNavigation | undefined; isFocused(): boolean; } ``` useTypedParams [#usetypedparams] Returns the current route's parameters. ```typescript function useTypedParams(): ParamsFor; ``` **Example:** ```tsx const { userId } = useTypedParams<"/users/:userId">(); ``` useTypedRoute [#usetypedroute] Returns the full route object. ```typescript function useTypedRoute(): TypedRoute; interface TypedRoute { name: string; params: ParamsFor; key: string; } ``` *** Primitives [#primitives] defineScreen [#definescreen] Defines a screen component with configuration. ```typescript function defineScreen(config: ScreenConfig): ScreenDefinition; ``` ScreenConfig [#screenconfig] ```typescript interface ScreenConfig { component: ComponentType; options?: ScreenOptions | ((props: { route: { params?: TParams }; navigation: unknown }) => ScreenOptions); params?: TParams; listeners?: { focus?: () => void; blur?: () => void; beforeRemove?: (e: { preventDefault: () => void }) => void; }; } ``` ScreenOptions [#screenoptions] ```typescript interface ScreenOptions { title?: string; headerShown?: boolean; headerTitle?: string | (() => React.ReactNode); headerLeft?: () => React.ReactNode; headerRight?: () => React.ReactNode; headerStyle?: object; headerTitleStyle?: object; headerBackTitle?: string; headerBackTitleVisible?: boolean; presentation?: "card" | "modal" | "transparentModal" | "containedModal" | "fullScreenModal" | "formSheet"; animation?: "default" | "fade" | "fade_from_bottom" | "flip" | "simple_push" | "slide_from_bottom" | "slide_from_right" | "slide_from_left" | "none"; gestureEnabled?: boolean; gestureDirection?: "horizontal" | "vertical"; animationDuration?: number; // Tab options tabBarLabel?: string; tabBarIcon?: (props: { focused: boolean; color: string; size: number }) => React.ReactNode; tabBarBadge?: string | number; // Drawer options drawerLabel?: string; drawerIcon?: (props: { focused: boolean; color: string; size: number }) => React.ReactNode; } ``` defineLayout [#definelayout] Defines a navigator layout. ```typescript function defineLayout(config: T): LayoutDefinition; ``` LayoutConfig Types [#layoutconfig-types] ```typescript type LayoutConfig = StackLayoutConfig | TabsLayoutConfig | DrawerLayoutConfig; interface StackLayoutConfig { type: "stack"; initialRouteName?: string; screenOptions?: StackScreenOptions; } interface TabsLayoutConfig { type: "tabs"; initialRouteName?: string; screenOptions?: TabScreenOptions; tabBar?: ComponentType; screens?: Record; } interface DrawerLayoutConfig { type: "drawer"; initialRouteName?: string; screenOptions?: DrawerScreenOptions; drawerContent?: ComponentType; screens?: Record; } ``` createParamSchema [#createparamschema] Creates a parameter validation schema using Zod. ```typescript function createParamSchema(shape: T): ParamSchema; ``` **Example:** ```tsx import { z } from "zod"; const params = createParamSchema({ userId: z.string().uuid(), tab: z.enum(["profile", "settings"]).optional(), }); ``` *** Router [#router] createTeardownRouter [#createteardownrouter] Creates a router component from a generated route tree. ```typescript function createTeardownRouter( options: TeardownRouterOptions ): ComponentType; ``` TeardownRouterOptions [#teardownrouteroptions] ```typescript interface TeardownRouterOptions { routeTree: T; initialRouteName?: string; linking?: LinkingOptions>; theme?: Theme; notFoundComponent?: ComponentType; navigationContainerProps?: Omit; } ``` **Example:** ```tsx import { createTeardownRouter } from "@teardown/navigation"; import { routeTree } from "../.teardown/routeTree.generated"; import { linking } from "../.teardown/linking.generated"; export const Router = createTeardownRouter({ routeTree, linking, initialRouteName: "index", }); ``` *** Utilities [#utilities] buildUrl [#buildurl] Builds a URL from a route path and parameters. ```typescript function buildUrl(path: T, params?: Record): string; ``` **Example:** ```typescript buildUrl("/users/:userId", { userId: "123" }); // "/users/123" buildUrl("/search/:query?", { query: "react" }); // "/search/react" buildUrl("/search/:query?"); // "/search" ``` matchPath [#matchpath] Matches a URL against a route pattern. ```typescript function matchPath(pattern: string, path: string): MatchResult | null; interface MatchResult { params: Record; path: string; } ``` parsePath [#parsepath] Parses a URL path into segments. ```typescript function parsePath(path: string): ParsedPath; interface ParsedPath { segments: string[]; params: Record; } ``` pathToScreenName [#pathtoscreenname] Converts a URL path to a React Navigation screen name. ```typescript function pathToScreenName(path: string): string; ``` **Example:** ```typescript pathToScreenName("/users/:userId"); // "users/[userId]" pathToScreenName("/"); // "index" ``` *** Type Utilities [#type-utilities] ParamsFor [#paramsfor] Extracts parameter types for a route. ```typescript type ParamsFor = RegisteredRouteParams[T]; ``` RegisteredRoutePath [#registeredroutepath] Union of all registered route paths. ```typescript type RegisteredRoutePath = keyof RegisteredRouteParams; ``` InferParams [#inferparams] Infers parameter types from a route path string. ```typescript type InferParams = /* inferred param object type */; ``` **Example:** ```typescript type Params = InferParams<"/users/:userId/posts/:postId">; // { userId: string; postId: string } ``` *** Metro Plugin [#metro-plugin] withTeardownNavigation [#withteardownnavigation] Wraps a Metro configuration with navigation support. ```typescript function withTeardownNavigation( config: MetroConfig, options?: TeardownNavigationOptions ): MetroConfig; ``` TeardownNavigationOptions [#teardownnavigationoptions] ```typescript interface TeardownNavigationOptions { routesDir?: string; // Default: "./src/_routes" generatedDir?: string; // Default: "./.teardown" prefixes?: string[]; // Default: [] verbose?: boolean; // Default: false autoTemplate?: boolean; // Default: true usePolling?: boolean; // Default: false } ``` Generator [#generator] Class for generating route type files. ```typescript class Generator { constructor(config: GeneratorConfig); run(event: GeneratorEvent): Promise; } ``` startRouteWatcher [#startroutewatcher] Starts watching for route file changes. ```typescript function startRouteWatcher(options: WatcherOptions): void; ``` stopRouteWatcher [#stoproutewatcher] Stops the route file watcher. ```typescript function stopRouteWatcher(): void; ``` *** Generated Files [#generated-files] The Metro plugin generates these files in `.teardown/`: | File | Export | Description | | ------------------------ | ------------- | --------------------------- | | `routes.generated.ts` | `RouteParams` | Route parameter interface | | `routeTree.generated.ts` | `routeTree` | Navigator tree structure | | `linking.generated.ts` | `linking` | Deep linking configuration | | `register.d.ts` | - | Type augmentation for hooks | | `manifest.json` | - | Route metadata | # Deep Linking (/docs/navigation/deep-linking) Deep linking allows users to open specific screens in your app via URLs. The navigation library automatically generates deep link configuration from your route structure. How It Works [#how-it-works] The Metro plugin generates a `linking.generated.ts` file with your app's deep link configuration: ``` .teardown/ ├── linking.generated.ts # Deep link config └── ... ``` This file maps URL paths to your screens automatically. Setup [#setup] 1. Configure URL Prefixes [#1-configure-url-prefixes] Set your app's URL schemes in the Metro config: ```js // metro.config.js const { withTeardownNavigation } = require("@teardown/navigation-metro"); module.exports = withTeardownNavigation(config, { prefixes: [ "myapp://", // Custom URL scheme "https://myapp.com", // Universal links ], }); ``` Or in `teardown.config.ts`: ```typescript export default { name: "MyApp", slug: "myapp", // Creates "myapp://" prefix automatically // ... }; ``` 2. Use the Generated Config [#2-use-the-generated-config] Pass the linking config to your router: ```tsx import { createTeardownRouter } from "@teardown/navigation"; import { routeTree } from "../.teardown/routeTree.generated"; import { linking } from "../.teardown/linking.generated"; export const Router = createTeardownRouter({ routeTree, linking, }); ``` 3. Configure Native Apps [#3-configure-native-apps] iOS [#ios] Add URL schemes to your `Info.plist` or `teardown.config.ts`: ```typescript // teardown.config.ts export default { slug: "myapp", ios: { bundleIdentifier: "com.example.myapp", // URL scheme is derived from slug: myapp:// }, }; ``` For universal links, configure your associated domains: ```typescript export default { ios: { associatedDomains: ["applinks:myapp.com"], }, }; ``` Android [#android] Add intent filters in `teardown.config.ts`: ```typescript export default { slug: "myapp", android: { packageName: "com.example.myapp", // URL scheme is derived from slug: myapp:// }, }; ``` URL to Screen Mapping [#url-to-screen-mapping] Routes are automatically mapped to URL paths: | File | URL | | ----------------------------- | --------------------------- | | `index.tsx` | `myapp://` | | `settings.tsx` | `myapp://settings` | | `users/[userId].tsx` | `myapp://users/123` | | `posts/[postId]/comments.tsx` | `myapp://posts/42/comments` | Testing Deep Links [#testing-deep-links] iOS Simulator [#ios-simulator] ```bash xcrun simctl openurl booted "myapp://users/123" ``` Android Emulator [#android-emulator] ```bash adb shell am start -W -a android.intent.action.VIEW -d "myapp://users/123" ``` React Native CLI [#react-native-cli] ```bash npx uri-scheme open "myapp://users/123" --ios npx uri-scheme open "myapp://users/123" --android ``` Handling Initial URL [#handling-initial-url] The router automatically handles the initial URL when the app is opened via a deep link. You can also access it programmatically: ```tsx import { Linking } from "react-native"; // Get the URL that opened the app const url = await Linking.getInitialURL(); // Listen for URL events while app is running Linking.addEventListener("url", ({ url }) => { console.log("Received URL:", url); }); ``` Custom Linking Config [#custom-linking-config] Override or extend the generated linking config: ```tsx import { createTeardownRouter } from "@teardown/navigation"; import { routeTree } from "../.teardown/routeTree.generated"; import { linking as generatedLinking } from "../.teardown/linking.generated"; // Extend with custom configuration const linking = { ...generatedLinking, prefixes: [ ...generatedLinking.prefixes, "https://myapp.com", ], // Add custom path mappings config: { ...generatedLinking.config, screens: { ...generatedLinking.config?.screens, // Custom screen mapping specialRoute: "special/:id", }, }, }; export const Router = createTeardownRouter({ routeTree, linking, }); ``` Universal Links (iOS) [#universal-links-ios] For HTTPS links that open your app: 1. Configure associated domains in `teardown.config.ts`: ```typescript export default { ios: { associatedDomains: ["applinks:myapp.com"], }, }; ``` 2. Host an `apple-app-site-association` file at: `https://myapp.com/.well-known/apple-app-site-association` ```json { "applinks": { "apps": [], "details": [{ "appID": "TEAMID.com.example.myapp", "paths": ["*"] }] } } ``` App Links (Android) [#app-links-android] For HTTPS links that open your app on Android: 1. Configure in `teardown.config.ts`: ```typescript export default { android: { intentFilters: [{ action: "VIEW", autoVerify: true, data: [{ scheme: "https", host: "myapp.com", pathPrefix: "/", }], category: ["BROWSABLE", "DEFAULT"], }], }, }; ``` 2. Host a Digital Asset Links file at: `https://myapp.com/.well-known/assetlinks.json` ```json [{ "relation": ["delegate_permission/common.handle_all_urls"], "target": { "namespace": "android_app", "package_name": "com.example.myapp", "sha256_cert_fingerprints": ["YOUR_CERT_FINGERPRINT"] } }] ``` Debugging [#debugging] Enable verbose logging to debug deep link issues: ```js // metro.config.js module.exports = withTeardownNavigation(config, { verbose: true, }); ``` Check the generated `linking.generated.ts` to verify your routes are mapped correctly. Next Steps [#next-steps] * [API Reference](/docs/navigation/api-reference) - Full API documentation # File-Based Routing (/docs/navigation/file-routing) Routes are defined by creating files in your `src/_routes/` directory. The file path determines the URL path. File Naming Conventions [#file-naming-conventions] Basic Routes [#basic-routes] | File | URL Path | | ------------------- | ---------------- | | `index.tsx` | `/` | | `about.tsx` | `/about` | | `settings.tsx` | `/settings` | | `users/index.tsx` | `/users` | | `users/profile.tsx` | `/users/profile` | Dynamic Routes [#dynamic-routes] Use brackets for dynamic segments: | File | URL Path | Example | | ----------------------------- | ------------------------- | ------------------- | | `[id].tsx` | `/:id` | `/123` | | `users/[userId].tsx` | `/users/:userId` | `/users/abc` | | `posts/[postId]/comments.tsx` | `/posts/:postId/comments` | `/posts/5/comments` | Optional Parameters [#optional-parameters] Use double brackets for optional segments: | File | URL Path | Matches | | ------------------- | -------------- | ----------------------------- | | `[[id]].tsx` | `/:id?` | `/` or `/123` | | `users/[[tab]].tsx` | `/users/:tab?` | `/users` or `/users/settings` | Catch-All Routes [#catch-all-routes] Use spread syntax for catch-all segments: | File | URL Path | Matches | | -------------------- | --------- | -------------------------------- | | `[...slug].tsx` | `/*` | `/a`, `/a/b`, `/a/b/c` | | `docs/[...path].tsx` | `/docs/*` | `/docs/intro`, `/docs/api/hooks` | Special Files [#special-files] Layout Files (_layout.tsx) [#layout-files-_layouttsx] Define navigator configuration for a directory: ```tsx // src/_routes/_layout.tsx import { defineLayout } from "@teardown/navigation"; export default defineLayout({ type: "stack", screenOptions: { headerShown: true, }, }); ``` Screen Files [#screen-files] All other `.tsx` files define screens: ```tsx // src/_routes/settings.tsx import { defineScreen } from "@teardown/navigation"; function SettingsScreen() { return Settings; } export default defineScreen({ component: SettingsScreen, options: { title: "Settings" }, }); ``` Route Groups [#route-groups] Use parentheses to group routes without affecting the URL: ``` src/_routes/ ├── (auth)/ │ ├── _layout.tsx # Auth-specific layout │ ├── login.tsx # /login │ └── register.tsx # /register ├── (main)/ │ ├── _layout.tsx # Main app layout (tabs) │ ├── home.tsx # /home │ └── profile.tsx # /profile └── _layout.tsx # Root layout ``` Route groups are useful for: * Applying different layouts to different sections * Organizing related routes together * Keeping the URL structure clean Directory Structure Examples [#directory-structure-examples] Simple App [#simple-app] ``` src/_routes/ ├── _layout.tsx # Stack navigator ├── index.tsx # / ├── about.tsx # /about └── settings.tsx # /settings ``` App with Nested Routes [#app-with-nested-routes] ``` src/_routes/ ├── _layout.tsx # Root stack ├── index.tsx # / ├── users/ │ ├── _layout.tsx # Users stack │ ├── index.tsx # /users │ └── [userId].tsx # /users/:userId └── settings/ ├── index.tsx # /settings ├── account.tsx # /settings/account └── notifications.tsx # /settings/notifications ``` App with Tabs [#app-with-tabs] ``` src/_routes/ ├── _layout.tsx # Root stack ├── (tabs)/ │ ├── _layout.tsx # Tab navigator │ ├── home.tsx # /home (tab) │ ├── search.tsx # /search (tab) │ └── profile.tsx # /profile (tab) └── settings.tsx # /settings (modal) ``` Defining Screens [#defining-screens] Basic Screen [#basic-screen] ```tsx import { defineScreen } from "@teardown/navigation"; import { View, Text } from "react-native"; function MyScreen() { return ( Hello World ); } export default defineScreen({ component: MyScreen, }); ``` Screen with Options [#screen-with-options] ```tsx import { defineScreen } from "@teardown/navigation"; export default defineScreen({ component: ProfileScreen, options: { title: "Profile", headerShown: true, presentation: "modal", }, }); ``` Screen with Dynamic Options [#screen-with-dynamic-options] ```tsx import { defineScreen } from "@teardown/navigation"; export default defineScreen({ component: UserScreen, options: ({ route }) => ({ title: `User ${route.params?.userId}`, }), }); ``` Screen with Event Listeners [#screen-with-event-listeners] ```tsx import { defineScreen } from "@teardown/navigation"; export default defineScreen({ component: FormScreen, options: { title: "Edit" }, listeners: { beforeRemove: (e) => { // Prevent leaving if form is dirty if (formIsDirty) { e.preventDefault(); // Show confirmation dialog } }, }, }); ``` Auto-Generated Templates [#auto-generated-templates] When `autoTemplate` is enabled (default), creating a new file automatically populates it with a template: ```tsx // Auto-generated when you create src/_routes/new-page.tsx import { View, Text, StyleSheet } from "react-native"; import { defineScreen } from "@teardown/navigation"; function NewPageScreen() { return ( New Page ); } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: "center", alignItems: "center", }, }); export default defineScreen({ component: NewPageScreen, options: { title: "New Page", }, }); ``` Next Steps [#next-steps] * [Type-Safe Navigation](/docs/navigation/type-safe-nav) - Navigate with full type safety * [Layouts](/docs/navigation/layouts) - Configure Stack, Tab, and Drawer navigators # Getting Started (/docs/navigation/getting-started) This guide walks through setting up `@teardown/navigation` in a React Native project. Prerequisites [#prerequisites] * React Native 0.72+ * React Navigation 7+ * Node.js 18+ Installation [#installation] Core Dependencies [#core-dependencies] ```bash # Navigation packages bun add @teardown/navigation @react-navigation/native @react-navigation/native-stack # Required React Navigation dependencies bun add react-native-screens react-native-safe-area-context ``` Metro Plugin [#metro-plugin] The Metro plugin scans your route files and generates TypeScript types: ```bash bun add -D @teardown/navigation-metro ``` Optional Navigators [#optional-navigators] Install based on your needs: ```bash # Tab navigation bun add @react-navigation/bottom-tabs # Drawer navigation bun add @react-navigation/drawer react-native-gesture-handler react-native-reanimated ``` Project Setup [#project-setup] 1. Metro Configuration [#1-metro-configuration] Create or update `metro.config.js`: ```js const { getDefaultConfig } = require("@react-native/metro-config"); const { withTeardownNavigation } = require("@teardown/navigation-metro"); const config = getDefaultConfig(__dirname); module.exports = withTeardownNavigation(config, { // Optional: Override defaults routesDir: "./src/_routes", generatedDir: "./.teardown", verbose: false, }); ``` 2. Create Routes Directory [#2-create-routes-directory] ```bash mkdir -p src/_routes ``` 3. Create Root Layout [#3-create-root-layout] The root `_layout.tsx` defines your app's navigator structure: ```tsx // src/_routes/_layout.tsx import { defineLayout } from "@teardown/navigation"; export default defineLayout({ type: "stack", screenOptions: { headerShown: true, animation: "slide_from_right", }, }); ``` 4. Create Home Screen [#4-create-home-screen] ```tsx // src/_routes/index.tsx import { View, Text } from "react-native"; import { defineScreen } from "@teardown/navigation"; function HomeScreen() { return ( Welcome Home ); } export default defineScreen({ component: HomeScreen, options: { title: "Home", }, }); ``` 5. Create Router Component [#5-create-router-component] ```tsx // src/app/index.tsx import { createTeardownRouter } from "@teardown/navigation"; import { routeTree } from "../.teardown/routeTree.generated"; import { linking } from "../.teardown/linking.generated"; export const Router = createTeardownRouter({ routeTree, linking, initialRouteName: "index", }); ``` 6. Use Router in App Entry [#6-use-router-in-app-entry] ```tsx // App.tsx import { Router } from "./src/app"; export default function App() { return ; } ``` Generated Files [#generated-files] When you run the Metro bundler, the plugin generates files in `.teardown/`: | File | Purpose | | ------------------------ | --------------------------- | | `routes.generated.ts` | Route parameter types | | `routeTree.generated.ts` | Navigator tree structure | | `linking.generated.ts` | Deep linking configuration | | `register.d.ts` | Type augmentation for hooks | | `manifest.json` | Route metadata | Add `.teardown/` to your `.gitignore`: ```bash # Generated navigation files .teardown/ ``` Configuration Options [#configuration-options] The `withTeardownNavigation` function accepts these options: | Option | Type | Default | Description | | -------------- | --------- | ----------------- | ------------------------------- | | `routesDir` | string | `"./src/_routes"` | Path to routes directory | | `generatedDir` | string | `"./.teardown"` | Output path for generated files | | `prefixes` | string\[] | `[]` | Deep link URL prefixes | | `verbose` | boolean | `false` | Enable verbose logging | | `autoTemplate` | boolean | `true` | Auto-populate new route files | | `usePolling` | boolean | `false` | Use polling for file watching | Options can also be set in `teardown.config.ts`: ```typescript // teardown.config.ts export default { name: "MyApp", slug: "myapp", navigation: { routesDir: "./src/_routes", generatedDir: "./.teardown", verbose: false, }, }; ``` Verifying Setup [#verifying-setup] 1. Start the Metro bundler: ```bash bun run dev # or npx react-native start ``` 2. Check that `.teardown/` is generated with type files 3. Navigate in your app - you should see the home screen Next Steps [#next-steps] * [File-Based Routing](/docs/navigation/file-routing) - Learn route file conventions * [Type-Safe Navigation](/docs/navigation/type-safe-nav) - Use typed navigation hooks * [Layouts](/docs/navigation/layouts) - Configure navigators # Navigation (/docs/navigation) `@teardown/navigation` provides type-safe, file-based navigation for React Native applications using React Navigation under the hood. > ⚠️ **Early Development** - The Teardown SDKs and APIs are under active development. Expect frequent breaking changes. Not recommended for production apps. Features [#features] * **File-based routing** - Define routes by creating files in `src/_routes/` * **Full type safety** - Route params, navigation methods, and hooks are all type-checked * **React Navigation v7+** - Built on top of React Navigation for reliability * **Stack, Tab, and Drawer** - Support for all common navigator types * **Deep linking** - Automatic deep link configuration generation * **Hot reloading** - Route changes detected and types regenerated in development Installation [#installation] Install the navigation packages: ```bash bun add @teardown/navigation @react-navigation/native @react-navigation/native-stack react-native-screens react-native-safe-area-context ``` Add the Metro plugin for type generation: ```bash bun add -D @teardown/navigation-metro ``` Optional Navigators [#optional-navigators] Install additional navigators as needed: ```bash # For tab navigation bun add @react-navigation/bottom-tabs # For drawer navigation bun add @react-navigation/drawer react-native-gesture-handler react-native-reanimated ``` Quick Start [#quick-start] 1. Configure Metro [#1-configure-metro] Update your `metro.config.js`: ```js const { getDefaultConfig } = require("@react-native/metro-config"); const { withTeardownNavigation } = require("@teardown/navigation-metro"); const config = getDefaultConfig(__dirname); module.exports = withTeardownNavigation(config); ``` 2. Create Routes [#2-create-routes] Create your routes in `src/_routes/`: ``` src/_routes/ ├── _layout.tsx # Root layout (stack navigator) ├── index.tsx # Home screen (/) ├── settings.tsx # Settings screen (/settings) └── users/ ├── _layout.tsx # Users layout ├── index.tsx # Users list (/users) └── [userId].tsx # User profile (/users/:userId) ``` 3. Define Screens [#3-define-screens] ```tsx // src/_routes/index.tsx import { defineScreen } from "@teardown/navigation"; function HomeScreen() { return Welcome Home; } export default defineScreen({ component: HomeScreen, options: { title: "Home" }, }); ``` 4. Create Router [#4-create-router] ```tsx // src/app/index.tsx import { createTeardownRouter } from "@teardown/navigation"; import { routeTree } from "../.teardown/routeTree.generated"; export const Router = createTeardownRouter({ routeTree, initialRouteName: "index", }); ``` 5. Use in App [#5-use-in-app] ```tsx // App.tsx import { Router } from "./src/app"; export default function App() { return ; } ``` Next Steps [#next-steps] * [Getting Started](/docs/navigation/getting-started) - Detailed setup guide * [File-Based Routing](/docs/navigation/file-routing) - Route file conventions * [Type-Safe Navigation](/docs/navigation/type-safe-nav) - Using typed hooks * [Layouts](/docs/navigation/layouts) - Stack, Tab, and Drawer navigators # Layouts (/docs/navigation/layouts) Layouts define how screens are organized and navigated. Create a `_layout.tsx` file in any directory to configure its navigator. Layout Types [#layout-types] Stack Navigator [#stack-navigator] The default navigator type. Screens stack on top of each other: ```tsx // src/_routes/_layout.tsx import { defineLayout } from "@teardown/navigation"; export default defineLayout({ type: "stack", screenOptions: { headerShown: true, animation: "slide_from_right", }, }); ``` Stack Screen Options [#stack-screen-options] | Option | Type | Description | | ------------------------ | ------- | ------------------------------------------------------------------------------------ | | `headerShown` | boolean | Show/hide the header | | `title` | string | Header title | | `presentation` | string | `"card"`, `"modal"`, `"transparentModal"`, `"fullScreenModal"`, `"formSheet"` | | `animation` | string | `"slide_from_right"`, `"slide_from_left"`, `"slide_from_bottom"`, `"fade"`, `"none"` | | `gestureEnabled` | boolean | Enable swipe-to-go-back gesture | | `gestureDirection` | string | `"horizontal"` or `"vertical"` | | `headerBackTitleVisible` | boolean | Show back button title (iOS) | Tab Navigator [#tab-navigator] Display screens as tabs at the bottom of the screen: ```tsx // src/_routes/(tabs)/_layout.tsx import { defineLayout } from "@teardown/navigation"; import { Home, Search, User } from "lucide-react-native"; export default defineLayout({ type: "tabs", screenOptions: { headerShown: false, tabBarActiveTintColor: "#007AFF", tabBarInactiveTintColor: "#8E8E93", }, screens: { home: { tabBarLabel: "Home", tabBarIcon: ({ color, size }) => , }, search: { tabBarLabel: "Search", tabBarIcon: ({ color, size }) => , }, profile: { tabBarLabel: "Profile", tabBarIcon: ({ color, size }) => , }, }, }); ``` Tab Screen Options [#tab-screen-options] | Option | Type | Description | | ------------------------- | ------- | ---------------------------------- | | `tabBarActiveTintColor` | string | Active tab color | | `tabBarInactiveTintColor` | string | Inactive tab color | | `tabBarStyle` | object | Tab bar container style | | `tabBarLabelStyle` | object | Tab label text style | | `tabBarShowLabel` | boolean | Show/hide tab labels | | `tabBarHideOnKeyboard` | boolean | Hide tabs when keyboard is visible | | `lazy` | boolean | Lazy load tab screens | Per-Screen Tab Options [#per-screen-tab-options] | Option | Type | Description | | -------------- | ---------------- | --------------------------- | | `tabBarLabel` | string | Tab label text | | `tabBarIcon` | function | Tab icon component | | `tabBarBadge` | string \| number | Badge on tab | | `tabBarButton` | function | Custom tab button component | Drawer Navigator [#drawer-navigator] Side menu navigation: ```tsx // src/_routes/_layout.tsx import { defineLayout } from "@teardown/navigation"; import { Home, Settings, Info } from "lucide-react-native"; export default defineLayout({ type: "drawer", screenOptions: { headerShown: true, drawerActiveTintColor: "#007AFF", drawerPosition: "left", }, screens: { home: { drawerLabel: "Home", drawerIcon: ({ color, size }) => , }, settings: { drawerLabel: "Settings", drawerIcon: ({ color, size }) => , }, about: { drawerLabel: "About", drawerIcon: ({ color, size }) => , }, }, }); ``` Drawer Screen Options [#drawer-screen-options] | Option | Type | Description | | ------------------------- | ------- | --------------------------------------------- | | `drawerActiveTintColor` | string | Active item color | | `drawerInactiveTintColor` | string | Inactive item color | | `drawerStyle` | object | Drawer container style | | `drawerPosition` | string | `"left"` or `"right"` | | `drawerType` | string | `"front"`, `"back"`, `"slide"`, `"permanent"` | | `swipeEnabled` | boolean | Enable swipe to open | Nested Navigators [#nested-navigators] Combine different navigator types for complex navigation structures. Tabs Inside Stack [#tabs-inside-stack] ``` src/_routes/ ├── _layout.tsx # Stack (root) ├── index.tsx # Landing screen ├── (main)/ │ ├── _layout.tsx # Tabs │ ├── home.tsx # Tab 1 │ ├── search.tsx # Tab 2 │ └── profile.tsx # Tab 3 └── settings.tsx # Modal (stacks over tabs) ``` ```tsx // src/_routes/_layout.tsx (Stack) export default defineLayout({ type: "stack", screenOptions: { headerShown: false }, }); // src/_routes/(main)/_layout.tsx (Tabs) export default defineLayout({ type: "tabs", screenOptions: { tabBarActiveTintColor: "#007AFF" }, }); ``` Stack Inside Tabs [#stack-inside-tabs] Each tab can have its own stack: ``` src/_routes/ ├── _layout.tsx # Tabs (root) ├── (home)/ │ ├── _layout.tsx # Stack for home tab │ ├── index.tsx # Home │ └── details.tsx # Details (pushes in home tab) └── (profile)/ ├── _layout.tsx # Stack for profile tab ├── index.tsx # Profile └── edit.tsx # Edit profile ``` Initial Route [#initial-route] Set the initial route for a navigator: ```tsx export default defineLayout({ type: "tabs", initialRouteName: "home", // Start on home tab screenOptions: { ... }, }); ``` Custom Tab Bar [#custom-tab-bar] Provide a custom tab bar component: ```tsx import { defineLayout } from "@teardown/navigation"; import { CustomTabBar } from "../components/CustomTabBar"; export default defineLayout({ type: "tabs", tabBar: CustomTabBar, screenOptions: { headerShown: false }, }); ``` Custom Drawer Content [#custom-drawer-content] Provide custom drawer content: ```tsx import { defineLayout } from "@teardown/navigation"; import { CustomDrawer } from "../components/CustomDrawer"; export default defineLayout({ type: "drawer", drawerContent: CustomDrawer, screenOptions: { headerShown: true }, }); ``` Required Dependencies [#required-dependencies] Make sure to install the required packages for each navigator type: ```bash # Stack (included by default) bun add @react-navigation/native-stack # Tabs bun add @react-navigation/bottom-tabs # Drawer bun add @react-navigation/drawer react-native-gesture-handler react-native-reanimated ``` The library will throw a helpful error if you try to use a navigator without its dependencies installed. Next Steps [#next-steps] * [Deep Linking](/docs/navigation/deep-linking) - Configure URL handling * [API Reference](/docs/navigation/api-reference) - Full API documentation # Type-Safe Navigation (/docs/navigation/type-safe-nav) `@teardown/navigation` provides type-safe hooks that ensure your navigation calls are valid at compile time. Navigation Hooks [#navigation-hooks] useTypedNavigation [#usetypednavigation] Get a typed navigation object for navigating between screens: ```tsx import { useTypedNavigation } from "@teardown/navigation"; function MyComponent() { const navigation = useTypedNavigation(); const handlePress = () => { // Fully type-checked - TypeScript knows the route exists navigation.navigate("/users/:userId", { userId: "123" }); }; return ); } ``` Query Key Structure [#query-key-structure] All queries use a consistent key format for predictable cache management: ```typescript // Query keys ["@teardown", resource, "query", subResource, ...args] // Example: ["@teardown", "projects", "query", "list", "user-123"] // Mutation keys ["@teardown", resource, "mutation", mutationKey, ...args] // Example: ["@teardown", "projects", "mutation", "create"] ``` Documentation [#documentation] * [Core Concepts](/docs/queries/core-concepts) - QueryClient architecture and type system * [Queries](/docs/queries/queries) - Standard and infinite queries * [Mutations](/docs/queries/mutations) - Mutations and cache invalidation * [Best Practices](/docs/queries/best-practices) - Patterns and SDK integration * [API Reference](/docs/queries/api-reference) - Complete type exports # Mutations (/docs/queries/mutations) Creating Mutations [#creating-mutations] Use the `mutation()` method for write operations: ```typescript class ProjectsClient extends QueryClient<"projects"> { createProject() { return this.mutation< "create", // Mutation key Project, // Return type ApiError, // Error type { name: string; slug: string } // Variables type >({ key: ["create"], fn: async (data) => { const response = await fetch("/api/projects", { method: "POST", body: JSON.stringify(data), }); return response.json(); }, onSuccess: () => { this.refresh("list"); // Invalidate list queries }, }); } } ``` Using Mutations [#using-mutations] ```tsx import { useMutation } from "@tanstack/react-query"; function CreateProjectForm() { const projectsClient = useProjectsClient(); const { mutate, isPending, error } = useMutation(projectsClient.createProject()); const handleSubmit = (formData) => { mutate(formData, { onSuccess: (newProject) => { // Navigate to new project router.push(`/projects/${newProject.id}`); }, }); }; return (
{error &&

{error.message}

}
); } ``` Cache Invalidation [#cache-invalidation] Invalidate related queries after mutations to keep data fresh: ```typescript class ProjectsClient extends QueryClient<"projects"> { updateProject(projectId: string) { return this.mutation({ key: ["update", projectId], fn: async (data: UpdateProjectInput) => { const response = await fetch(`/api/projects/${projectId}`, { method: "PATCH", body: JSON.stringify(data), }); return response.json(); }, onSuccess: () => { // Invalidate both list and detail queries this.refresh("list"); this.refresh("byId", projectId); }, }); } deleteProject(projectId: string) { return this.mutation({ key: ["delete", projectId], fn: async () => { await fetch(`/api/projects/${projectId}`, { method: "DELETE" }); }, onSuccess: () => { // Invalidate all project queries this.refresh(); }, }); } } ``` Optimistic Updates [#optimistic-updates] Update the cache before the mutation completes: ```typescript updateProject(projectId: string) { return this.mutation({ key: ["update", projectId], fn: updateProjectApi, onMutate: async (newData) => { // Cancel outgoing refetches await queryClient.cancelQueries({ queryKey: ["@teardown", "projects"] }); // Snapshot previous value const previous = this.getProject(projectId).get(); // Optimistically update this.getProject(projectId).set({ ...previous, ...newData }); return { previous }; }, onError: (_err, _vars, context) => { // Rollback on error if (context?.previous) { this.getProject(projectId).set(context.previous); } }, onSettled: () => { // Refetch after mutation this.refresh("byId", projectId); }, }); } ``` Error Handling [#error-handling] Handle errors consistently in mutations: ```typescript deleteProject(projectId: string) { return this.mutation({ key: ["delete", projectId], fn: async () => { const response = await fetch(`/api/projects/${projectId}`, { method: "DELETE", }); if (!response.ok) { const error = await response.json(); throw error; } }, onError: (error) => { if (error.status === 404) { // Handle not found toast.error("Project not found"); } else if (error.status === 403) { // Handle forbidden toast.error("You don't have permission to delete this project"); } else { toast.error("Failed to delete project"); } }, }); } ``` Mutation Callbacks [#mutation-callbacks] All TanStack Query mutation callbacks are supported: ```typescript this.mutation({ key: ["create"], fn: createProjectFn, // Before mutation starts onMutate: async (variables) => { // Return context for rollback return { previousData }; }, // On success onSuccess: (data, variables, context) => { // Update cache, show toast, navigate }, // On error onError: (error, variables, context) => { // Rollback, show error }, // Always runs (success or error) onSettled: (data, error, variables, context) => { // Cleanup, refetch }, }); ``` Mutation Result [#mutation-result] ```typescript interface CreateMutationResult { mutationKey: MutationKey; mutationFn: MutationFunction; // ... other MutationOptions } ``` Cross-Resource Invalidation [#cross-resource-invalidation] Invalidate queries across multiple resources: ```typescript class ProjectsClient extends QueryClient<"projects"> { constructor( tanstackClient: TanstackQueryClient, private readonly orgsClient: OrgsClient ) { super(tanstackClient, "projects"); } deleteProject(projectId: string) { return this.mutation({ key: ["delete", projectId], fn: deleteProjectFn, onSuccess: () => { // Invalidate project queries this.refresh(); // Also invalidate org stats (cross-resource) this.orgsClient.refresh("stats"); }, }); } } ``` # Queries (/docs/queries/queries) Standard Queries [#standard-queries] Use the `query()` method to create type-safe query configurations: ```typescript class ProjectsClient extends QueryClient<"projects"> { getProjects(userId: string) { return this.query({ key: ["list", userId], fn: async () => { const response = await fetch(`/api/projects?userId=${userId}`); return response.json(); }, staleTime: 2 * 60 * 1000, // Override default }); } getProject(projectId: string) { return this.query({ key: ["byId", projectId], enabled: projectId !== "", fn: async () => { const response = await fetch(`/api/projects/${projectId}`); return response.json(); }, }); } } ``` Using in Components [#using-in-components] ```tsx import { useQuery } from "@tanstack/react-query"; function ProjectsList({ userId }) { const projectsClient = useProjectsClient(); const { data, isLoading, error } = useQuery(projectsClient.getProjects(userId)); if (isLoading) return
Loading...
; if (error) return
Error: {error.message}
; return (
    {data?.map(project => (
  • {project.name}
  • ))}
); } ``` Cache Operations [#cache-operations] Each query result includes utility methods for cache management: ```typescript const projectsQuery = projectsClient.getProjects(userId); // One-time fetch (doesn't subscribe to updates) const data = await projectsQuery.fetch(); // Populate cache proactively await projectsQuery.prefetch(); // Read current cached value synchronously const cached = projectsQuery.get(); // Update cache directly projectsQuery.set(newData); // Invalidate and trigger refetch await projectsQuery.refresh(); ``` Infinite Queries [#infinite-queries] For paginated or infinite-scroll data, use `infiniteQuery()`: ```typescript class ProjectsClient extends QueryClient<"projects"> { getProjectsInfinite() { return this.infiniteQuery({ key: ["list", "infinite"], fn: async ({ pageParam }) => { const response = await fetch(`/api/projects?page=${pageParam}`); return response.json(); }, initialPageParam: 1, getNextPageParam: (lastPage, _allPages, lastPageParam) => { return lastPage.hasMore ? lastPageParam + 1 : undefined; }, }); } } ``` Using Infinite Queries [#using-infinite-queries] ```tsx import { useInfiniteQuery } from "@tanstack/react-query"; function InfiniteProjectsList() { const projectsClient = useProjectsClient(); const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfiniteQuery(projectsClient.getProjectsInfinite()); return (
{data?.pages.map(page => page.projects.map(project => (
{project.name}
)) )} {hasNextPage && ( )}
); } ``` Infinite Query Result [#infinite-query-result] ```typescript interface CreateInfiniteQueryResult { queryKey: QueryKey; // ... other InfiniteQueryOptions fetch(): Promise>; refresh(): Promise; } ``` Query Options [#query-options] All standard TanStack Query options are supported: ```typescript this.query({ key: ["list", userId], fn: fetchProjects, // Common options enabled: userId !== "", // Conditional fetching staleTime: 5 * 60 * 1000, // Fresh for 5 minutes gcTime: 30 * 60 * 1000, // Keep unused 30 minutes refetchOnWindowFocus: true, // Refetch on focus refetchOnReconnect: true, // Refetch on network reconnect retry: 3, // Retry failed requests retryDelay: (attempt) => Math.min(1000 * 2 ** attempt, 30000), }); ``` Conditional Queries [#conditional-queries] Use `enabled` to conditionally run queries: ```typescript getProject(projectId: string) { return this.query({ key: ["byId", projectId], enabled: projectId !== "", // Only fetch when ID is provided fn: async () => { const response = await fetch(`/api/projects/${projectId}`); return response.json(); }, }); } ``` Query Dependencies [#query-dependencies] Chain dependent queries by using data from one as args for another: ```tsx function ProjectDetails({ projectId }) { const queryClients = useQueryClients(); // First query const { data: project } = useQuery( queryClients.projects.getProject(projectId) ); // Dependent query - only runs when project exists const { data: owner } = useQuery({ ...queryClients.users.getUser(project?.ownerId ?? ""), enabled: !!project?.ownerId, }); return (

{project?.name}

Owner: {owner?.name}

); } ``` # BasicAdapter (/docs/react-native/adapters/device/basic) The `BasicAdapter` provides minimal device information without external dependencies. Useful for testing or fallback scenarios. Usage [#usage] ```typescript import { TeardownCore } from '@teardown/force-updates'; import { BasicAdapter } from '@teardown/force-updates/adapters/basic'; import { MMKVStorageAdapter } from '@teardown/force-updates/adapters/mmkv'; export const teardown = new TeardownCore({ org_id: 'your-org-id', project_id: 'your-project-id', api_key: 'your-api-key', storageAdapter: new MMKVStorageAdapter(), deviceAdapter: new BasicAdapter(), }); ``` Import Path [#import-path] ```typescript import { BasicAdapter } from '@teardown/force-updates/adapters/basic'; ``` Collected Data [#collected-data] The BasicAdapter returns static or minimal information: Application Info [#application-info] * `version` - Static value or from app.json * `build_number` - Static value (as number) Hardware Info [#hardware-info] * `device_name` - "Unknown Device" * `device_brand` - "Unknown" * `device_type` - "unknown" OS Info [#os-info] * `platform` - From React Native `Platform.OS` mapped to `DevicePlatformEnum` * `name` - From React Native Platform API * `version` - From React Native Platform API When to Use [#when-to-use] * Unit testing without native modules * Development/prototyping * Fallback when other adapters fail * Environments where native modules aren't available Limitations [#limitations] * Limited device information accuracy * No hardware-specific details * Not recommended for production analytics # DeviceInfoAdapter (/docs/react-native/adapters/device/device-info) The `DeviceInfoAdapter` uses `react-native-device-info` to collect device information in bare React Native projects. Installation [#installation] ```bash npm install react-native-device-info # or bun add react-native-device-info ``` For iOS, run pod install: ```bash cd ios && pod install ``` Usage [#usage] ```typescript import { TeardownCore } from '@teardown/force-updates'; import { DeviceInfoAdapter } from '@teardown/force-updates/adapters/device-info'; import { MMKVStorageAdapter } from '@teardown/force-updates/adapters/mmkv'; export const teardown = new TeardownCore({ org_id: 'your-org-id', project_id: 'your-project-id', api_key: 'your-api-key', storageAdapter: new MMKVStorageAdapter(), deviceAdapter: new DeviceInfoAdapter(), }); ``` Import Path [#import-path] ```typescript import { DeviceInfoAdapter } from '@teardown/force-updates/adapters/device-info'; ``` Collected Data [#collected-data] Application Info [#application-info] * `version` - From `getVersion()` * `build_number` - From `getBuildNumber()` (as number) Hardware Info [#hardware-info] * `device_name` - From `getDeviceName()` * `device_brand` - From `getBrand()` * `device_type` - From `getDeviceType()` (phone, tablet, desktop, tv, unknown) OS Info [#os-info] * `platform` - Mapped from React Native `Platform.OS` to `DevicePlatformEnum` * `name` - From `getSystemName()` * `version` - From `getSystemVersion()` When to Use [#when-to-use] * Bare React Native CLI projects * Projects not using Expo modules * Projects requiring additional device info beyond what Expo provides Requirements [#requirements] * `react-native-device-info` >= 15.0.0 * React Native >= 0.60 (autolinking) Platform Notes [#platform-notes] iOS [#ios] Requires `pod install` after installation. Android [#android] Works out of the box with autolinking. # ExpoDeviceAdapter (/docs/react-native/adapters/device/expo) The `ExpoDeviceAdapter` uses Expo's native modules to collect device information. Installation [#installation] ```bash npx expo install expo-device expo-application ``` Usage [#usage] ```typescript import { TeardownCore } from '@teardown/force-updates'; import { ExpoDeviceAdapter } from '@teardown/force-updates/adapters/expo'; import { MMKVStorageAdapter } from '@teardown/force-updates/adapters/mmkv'; export const teardown = new TeardownCore({ org_id: 'your-org-id', project_id: 'your-project-id', api_key: 'your-api-key', storageAdapter: new MMKVStorageAdapter(), deviceAdapter: new ExpoDeviceAdapter(), }); ``` Import Path [#import-path] ```typescript import { ExpoDeviceAdapter } from '@teardown/force-updates/adapters/expo'; ``` Collected Data [#collected-data] Application Info [#application-info] * `version` - From `expo-application` `nativeApplicationVersion` * `build_number` - From `expo-application` `nativeBuildVersion` (as number) Hardware Info [#hardware-info] * `device_name` - From `expo-device` `deviceName` * `device_brand` - From `expo-device` `brand` * `device_type` - From `expo-device` `deviceType` (phone, tablet, desktop, tv, unknown) OS Info [#os-info] * `platform` - Mapped from React Native `Platform.OS` to `DevicePlatformEnum` * `name` - From `expo-device` `osName` * `version` - From `expo-device` `osVersion` When to Use [#when-to-use] * Expo managed workflow projects * Expo bare workflow projects using Expo modules * Projects using Expo SDK 50+ Requirements [#requirements] * `expo-device` >= 8.0.0 * `expo-application` >= 7.0.0 # Device Adapters (/docs/react-native/adapters/device) Device adapters collect device and application information for identification and analytics. You must provide a device adapter when initializing `TeardownCore`. Available Adapters [#available-adapters] | Adapter | Package | Best For | | --------------------------------------------------------------------- | --------------------------------- | ---------------- | | [`ExpoDeviceAdapter`](/docs/react-native/adapters/device/expo) | `expo-device`, `expo-application` | Expo projects | | [`DeviceInfoAdapter`](/docs/react-native/adapters/device/device-info) | `react-native-device-info` | Bare RN projects | | [`BasicAdapter`](/docs/react-native/adapters/device/basic) | None | Fallback/testing | Collected Information [#collected-information] All device adapters provide: Application Info [#application-info] ```typescript interface ApplicationInfo { version: string; // App version (e.g., "1.2.3") build_number: number; // Build number (e.g., 45) } ``` Hardware Info [#hardware-info] ```typescript interface HardwareInfo { device_name: string; // Device name (e.g., "iPhone 14 Pro") device_brand: string; // Manufacturer (e.g., "Apple") device_type: string; // Device type (e.g., "phone", "tablet") } ``` OS Info [#os-info] ```typescript interface OSInfo { platform: DevicePlatformEnum; // Platform enum (IOS, ANDROID, etc.) name: string; // OS name (e.g., "iOS", "Android") version: string; // OS version (e.g., "17.0") } ``` DevicePlatformEnum [#deviceplatformenum] ```typescript enum DevicePlatformEnum { IOS = "IOS", ANDROID = "ANDROID", WEB = "WEB", WINDOWS = "WINDOWS", MACOS = "MACOS", LINUX = "LINUX", UNKNOWN = "UNKNOWN", } ``` Usage [#usage] ```typescript import { TeardownCore } from '@teardown/force-updates'; import { ExpoDeviceAdapter } from '@teardown/force-updates/adapters/expo'; import { MMKVStorageAdapter } from '@teardown/force-updates/adapters/mmkv'; const teardown = new TeardownCore({ org_id: 'your-org-id', project_id: 'your-project-id', api_key: 'your-api-key', storageAdapter: new MMKVStorageAdapter(), deviceAdapter: new ExpoDeviceAdapter(), }); ``` Device ID [#device-id] The SDK generates a stable device ID that persists across app sessions: ```typescript const deviceId = await core.device.getDeviceId(); // Returns a UUID that persists in storage ``` To reset the device ID (device will appear as new install): ```typescript core.device.reset(); // Next getDeviceId() call will generate a new ID ``` Custom Adapter [#custom-adapter] Extend the `DeviceInfoAdapter` abstract class: ```typescript import { DeviceInfoAdapter, DevicePlatformEnum, type ApplicationInfo, type HardwareInfo, type OSInfo, } from '@teardown/force-updates'; class CustomDeviceAdapter extends DeviceInfoAdapter { get applicationInfo(): ApplicationInfo { return { version: '1.0.0', build_number: 1, }; } get hardwareInfo(): HardwareInfo { return { device_name: 'Custom Device', device_brand: 'Custom', device_type: 'phone', }; } get osInfo(): OSInfo { return { platform: DevicePlatformEnum.IOS, name: 'iOS', version: '17.0', }; } } ``` # Adapters (/docs/react-native/adapters) The Teardown SDK uses adapters to abstract platform-specific functionality. This allows the SDK to work across different React Native environments (Expo, bare RN) and with different underlying libraries. Adapter Types [#adapter-types] Device Adapters [#device-adapters] Collect device and application information for identification and analytics. | Adapter | Package | Use Case | | --------------------------------------------------------------------- | --------------------------------- | ---------------- | | [`ExpoDeviceAdapter`](/docs/react-native/adapters/device/expo) | `expo-device`, `expo-application` | Expo projects | | [`DeviceInfoAdapter`](/docs/react-native/adapters/device/device-info) | `react-native-device-info` | Bare RN projects | | [`BasicAdapter`](/docs/react-native/adapters/device/basic) | None | Fallback/testing | [View Device Adapters →](/docs/react-native/adapters/device) Storage Adapters [#storage-adapters] Handle persistent data storage for sessions, device IDs, and version status. | Adapter | Package | Use Case | | -------------------------------------------------------------------------- | ------------------------------------------- | ------------------------------- | | [`MMKVStorageAdapter`](/docs/react-native/adapters/storage/mmkv) | `react-native-mmkv` | Fast sync storage (recommended) | | [`AsyncStorageAdapter`](/docs/react-native/adapters/storage/async-storage) | `@react-native-async-storage/async-storage` | Broader compatibility | [View Storage Adapters →](/docs/react-native/adapters/storage) Notification Adapters [#notification-adapters] Handle push notification registration and token management. | Adapter | Package | Use Case | | -------------------------------------------------------------------------------- | ---------------------------------- | ------------------------- | | [`ExpoNotificationsAdapter`](/docs/react-native/adapters/notifications/expo) | `expo-notifications` | Expo projects | | [`FirebaseMessagingAdapter`](/docs/react-native/adapters/notifications/firebase) | `@react-native-firebase/messaging` | Firebase Cloud Messaging | | [`WixNotificationsAdapter`](/docs/react-native/adapters/notifications/wix) | `react-native-notifications` | Wix notifications library | [View Notification Adapters →](/docs/react-native/adapters/notifications) Required Adapters [#required-adapters] When initializing `TeardownCore`, you must provide: ```typescript const teardown = new TeardownCore({ org_id: 'your-org-id', project_id: 'your-project-id', api_key: 'your-api-key', storageAdapter: new MMKVStorageAdapter(), // Required deviceAdapter: new ExpoDeviceAdapter(), // Required }); ``` Common Configurations [#common-configurations] Expo Project [#expo-project] ```typescript import { TeardownCore } from '@teardown/force-updates'; import { ExpoDeviceAdapter } from '@teardown/force-updates/adapters/expo'; import { MMKVStorageAdapter } from '@teardown/force-updates/adapters/mmkv'; export const teardown = new TeardownCore({ org_id: 'your-org-id', project_id: 'your-project-id', api_key: 'your-api-key', storageAdapter: new MMKVStorageAdapter(), deviceAdapter: new ExpoDeviceAdapter(), }); ``` Bare React Native Project [#bare-react-native-project] ```typescript import { TeardownCore } from '@teardown/force-updates'; import { DeviceInfoAdapter } from '@teardown/force-updates/adapters/device-info'; import { AsyncStorageAdapter } from '@teardown/force-updates/adapters/async-storage'; export const teardown = new TeardownCore({ org_id: 'your-org-id', project_id: 'your-project-id', api_key: 'your-api-key', storageAdapter: new AsyncStorageAdapter(), deviceAdapter: new DeviceInfoAdapter(), }); ``` Custom Adapters [#custom-adapters] All adapter types can be extended for custom implementations. See individual adapter sections for interface definitions. # ExpoNotificationsAdapter (/docs/react-native/adapters/notifications/expo) > **Work in Progress**: The notification adapter APIs are under active development and may change in future releases. The `ExpoNotificationsAdapter` integrates with Expo's notifications library for push notification handling. Installation [#installation] ```bash npx expo install expo-notifications expo-device expo-constants ``` Usage [#usage] ```typescript import { TeardownCore } from '@teardown/force-updates'; import { ExpoNotificationsAdapter } from '@teardown/force-updates/expo'; import { ExpoDeviceAdapter } from '@teardown/force-updates/adapters/expo'; import { MMKVStorageAdapter } from '@teardown/force-updates/adapters/mmkv'; const teardown = new TeardownCore({ org_id: 'your-org-id', project_id: 'your-project-id', api_key: 'your-api-key', storageAdapter: new MMKVStorageAdapter(), deviceAdapter: new ExpoDeviceAdapter(), notificationAdapter: new ExpoNotificationsAdapter(), }); // Access via NotificationsClient if (teardown.notifications) { const status = await teardown.notifications.requestPermissions(); if (status.granted) { const token = await teardown.notifications.getToken(); console.log('Expo push token:', token); } } ``` Import Path [#import-path] ```typescript import { ExpoNotificationsAdapter } from '@teardown/force-updates/expo'; ``` API [#api] All methods are accessed via `NotificationsClient`, not directly on the adapter. requestPermissions() [#requestpermissions] Request notification permissions: ```typescript const status = await teardown.notifications.requestPermissions(); // Returns: { granted: boolean, canAskAgain: boolean } ``` getToken() [#gettoken] Get the Expo push token: ```typescript const token = await teardown.notifications.getToken(); // Returns: "ExponentPushToken[xxxxxx]" or null ``` onNotificationReceived() [#onnotificationreceived] Subscribe to foreground notifications: ```typescript const unsubscribe = teardown.notifications.onNotificationReceived((notification) => { console.log('Received:', notification.title); }); // Cleanup unsubscribe(); ``` onNotificationOpened() [#onnotificationopened] Subscribe to notification taps: ```typescript const unsubscribe = teardown.notifications.onNotificationOpened((notification) => { console.log('User tapped:', notification.title); }); // Cleanup unsubscribe(); ``` onTokenRefresh() [#ontokenrefresh] Subscribe to token changes: ```typescript const unsubscribe = teardown.notifications.onTokenChange((token) => { console.log('New token:', token); }); // Cleanup unsubscribe(); ``` onDataMessage() [#ondatamessage] Subscribe to data-only/silent messages: ```typescript const unsubscribe = teardown.notifications.onDataMessage((message) => { console.log('Data message:', message.data); }); // Cleanup unsubscribe(); ``` Configuration [#configuration] app.json / app.config.js [#appjson--appconfigjs] ```json { "expo": { "plugins": [ [ "expo-notifications", { "icon": "./assets/notification-icon.png", "color": "#ffffff" } ] ] } } ``` Android [#android] For FCM integration, add your `google-services.json` to the project root. iOS [#ios] Push notifications require: * Apple Developer account * Push notification capability * APNs certificate or key Platform [#platform] Returns `NotificationPlatformEnum.EXPO` Requirements [#requirements] * `expo-notifications` >= 0.32 * `expo-device` >= 8.0.0 * `expo-constants` >= 17.0.0 * EAS Build or dev client (not Expo Go) # FirebaseMessagingAdapter (/docs/react-native/adapters/notifications/firebase) > **Work in Progress**: The notification adapter APIs are under active development and may change in future releases. The `FirebaseMessagingAdapter` integrates with Firebase Cloud Messaging for push notifications. Installation [#installation] ```bash npm install @react-native-firebase/app @react-native-firebase/messaging # or bun add @react-native-firebase/app @react-native-firebase/messaging ``` For iOS: ```bash cd ios && pod install ``` Usage [#usage] ```typescript import { TeardownCore } from '@teardown/force-updates'; import { FirebaseMessagingAdapter } from '@teardown/force-updates/firebase'; import { DeviceInfoAdapter } from '@teardown/force-updates/adapters/device-info'; import { AsyncStorageAdapter } from '@teardown/force-updates/adapters/async-storage'; const teardown = new TeardownCore({ org_id: 'your-org-id', project_id: 'your-project-id', api_key: 'your-api-key', storageAdapter: new AsyncStorageAdapter(), deviceAdapter: new DeviceInfoAdapter(), notificationAdapter: new FirebaseMessagingAdapter(), }); // Access via NotificationsClient if (teardown.notifications) { const status = await teardown.notifications.requestPermissions(); if (status.granted) { const token = await teardown.notifications.getToken(); console.log('FCM token:', token); } } ``` Import Path [#import-path] ```typescript import { FirebaseMessagingAdapter } from '@teardown/force-updates/firebase'; ``` API [#api] All methods are accessed via `NotificationsClient`, not directly on the adapter. requestPermissions() [#requestpermissions] Request notification permissions (required on iOS): ```typescript const status = await teardown.notifications.requestPermissions(); // Returns: { granted: boolean, canAskAgain: boolean } ``` getToken() [#gettoken] Get the FCM registration token: ```typescript const token = await teardown.notifications.getToken(); // Returns: FCM token string or null ``` onNotificationReceived() [#onnotificationreceived] Subscribe to foreground notifications: ```typescript const unsubscribe = teardown.notifications.onNotificationReceived((notification) => { console.log('Received:', notification.title); }); // Cleanup unsubscribe(); ``` onNotificationOpened() [#onnotificationopened] Subscribe to notification taps: ```typescript const unsubscribe = teardown.notifications.onNotificationOpened((notification) => { console.log('User tapped:', notification.title); }); // Cleanup unsubscribe(); ``` onTokenRefresh() [#ontokenrefresh] Subscribe to token changes: ```typescript const unsubscribe = teardown.notifications.onTokenChange((token) => { console.log('New FCM token:', token); }); // Cleanup unsubscribe(); ``` onDataMessage() [#ondatamessage] Subscribe to data-only messages (silent push): ```typescript const unsubscribe = teardown.notifications.onDataMessage((message) => { console.log('Data message:', message.data); }); // Cleanup unsubscribe(); ``` Configuration [#configuration] Android [#android] 1. Add `google-services.json` to `android/app/` 2. Add to `android/build.gradle`: ```groovy buildscript { dependencies { classpath 'com.google.gms:google-services:4.3.15' } } ``` 3. Add to `android/app/build.gradle`: ```groovy apply plugin: 'com.google.gms.google-services' ``` iOS [#ios] 1. Add `GoogleService-Info.plist` to iOS project 2. Enable Push Notifications capability in Xcode 3. Upload APNs key to Firebase Console Background Messages [#background-messages] Handle messages when app is in background: ```typescript import messaging from '@react-native-firebase/messaging'; // Register background handler (must be outside component) messaging().setBackgroundMessageHandler(async (remoteMessage) => { console.log('Background message:', remoteMessage); }); ``` Platform [#platform] Returns `NotificationPlatformEnum.FCM` Requirements [#requirements] * `@react-native-firebase/app` >= 18.0.0 * `@react-native-firebase/messaging` >= 18.0.0 * React Native >= 0.71 # Notification Adapters (/docs/react-native/adapters/notifications) > **Work in Progress**: The notification adapter APIs are under active development and may change in future releases. Notification adapters handle push notification token registration, permission requests, and notification event handling with your push notification provider. Available Adapters [#available-adapters] | Adapter | Package | Best For | | -------------------------------------------------------------------------------- | ---------------------------------- | ------------------------- | | [`ExpoNotificationsAdapter`](/docs/react-native/adapters/notifications/expo) | `expo-notifications` | Expo projects | | [`FirebaseMessagingAdapter`](/docs/react-native/adapters/notifications/firebase) | `@react-native-firebase/messaging` | Firebase Cloud Messaging | | [`WixNotificationsAdapter`](/docs/react-native/adapters/notifications/wix) | `react-native-notifications` | Wix notifications library | Usage [#usage] Notification adapters are passed to `TeardownCore` to enable push notification support: ```typescript import { TeardownCore } from '@teardown/force-updates'; import { ExpoNotificationsAdapter } from '@teardown/force-updates/expo'; import { ExpoDeviceAdapter } from '@teardown/force-updates/adapters/expo'; import { MMKVStorageAdapter } from '@teardown/force-updates/adapters/mmkv'; const teardown = new TeardownCore({ org_id: 'your-org-id', project_id: 'your-project-id', api_key: 'your-api-key', storageAdapter: new MMKVStorageAdapter(), deviceAdapter: new ExpoDeviceAdapter(), notificationAdapter: new ExpoNotificationsAdapter(), // Optional }); // Access notifications via core if (teardown.notifications) { const token = await teardown.notifications.getToken(); } ``` Adapter Interface [#adapter-interface] All notification adapters extend the abstract `NotificationAdapter` class: ```typescript abstract class NotificationAdapter { /** The notification platform this adapter supports (APNS, FCM, EXPO) */ abstract get platform(): NotificationPlatformEnum; /** Get the current push notification token */ abstract getToken(): Promise; /** Request push notification permissions from the user */ abstract requestPermissions(): Promise; /** Subscribe to token refresh events */ abstract onTokenRefresh(listener: (token: string) => void): Unsubscribe; /** Subscribe to foreground notification events */ abstract onNotificationReceived(listener: (notification: PushNotification) => void): Unsubscribe; /** Subscribe to notification opened events (user taps) */ abstract onNotificationOpened(listener: (notification: PushNotification) => void): Unsubscribe; /** Subscribe to data-only message events (silent/background push) */ abstract onDataMessage(listener: (message: DataMessage) => void): Unsubscribe; } ``` PermissionStatus [#permissionstatus] ```typescript interface PermissionStatus { /** Whether notifications permission is granted */ granted: boolean; /** Whether the user can be prompted again (iOS specific) */ canAskAgain: boolean; } ``` PushNotification [#pushnotification] ```typescript interface PushNotification { title?: string; body?: string; data?: Record; } ``` DataMessage [#datamessage] ```typescript interface DataMessage { data: Record; } ``` NotificationPlatformEnum [#notificationplatformenum] ```typescript enum NotificationPlatformEnum { APNS = "APNS", // Apple Push Notification Service FCM = "FCM", // Firebase Cloud Messaging EXPO = "EXPO", // Expo Push Notifications } ``` Choosing an Adapter [#choosing-an-adapter] | If you're using... | Use this adapter | | ------------------------------ | -------------------------- | | Expo with expo-notifications | `ExpoNotificationsAdapter` | | Firebase Cloud Messaging | `FirebaseMessagingAdapter` | | Wix react-native-notifications | `WixNotificationsAdapter` | | Other push library | Implement custom adapter | Custom Adapter [#custom-adapter] Extend the `NotificationAdapter` abstract class: ```typescript import { NotificationAdapter, NotificationPlatformEnum, type PermissionStatus, type PushNotification, type DataMessage, type Unsubscribe, } from '@teardown/force-updates'; class CustomNotificationsAdapter extends NotificationAdapter { get platform(): NotificationPlatformEnum { return NotificationPlatformEnum.FCM; } async getToken(): Promise { return await myNotificationLib.getToken(); } async requestPermissions(): Promise { const granted = await myNotificationLib.requestPermission(); return { granted, canAskAgain: !granted }; } onTokenRefresh(listener: (token: string) => void): Unsubscribe { const subscription = myNotificationLib.onTokenRefresh(listener); return () => subscription.remove(); } onNotificationReceived(listener: (notification: PushNotification) => void): Unsubscribe { const subscription = myNotificationLib.onMessage((msg) => { listener({ title: msg.notification?.title, body: msg.notification?.body, data: msg.data, }); }); return () => subscription(); } onNotificationOpened(listener: (notification: PushNotification) => void): Unsubscribe { const subscription = myNotificationLib.onNotificationOpened((msg) => { listener({ title: msg.notification?.title, body: msg.notification?.body, data: msg.data, }); }); return () => subscription(); } onDataMessage(listener: (message: DataMessage) => void): Unsubscribe { const subscription = myNotificationLib.onDataMessage((msg) => { listener({ data: msg.data ?? {} }); }); return () => subscription(); } } ``` # WixNotificationsAdapter (/docs/react-native/adapters/notifications/wix) > **Work in Progress**: The notification adapter APIs are under active development and may change in future releases. The `WixNotificationsAdapter` integrates with Wix's react-native-notifications library. Installation [#installation] ```bash npm install react-native-notifications # or bun add react-native-notifications ``` For iOS: ```bash cd ios && pod install ``` Usage [#usage] ```typescript import { TeardownCore } from '@teardown/force-updates'; import { WixNotificationsAdapter } from '@teardown/force-updates/wix'; import { DeviceInfoAdapter } from '@teardown/force-updates/adapters/device-info'; import { AsyncStorageAdapter } from '@teardown/force-updates/adapters/async-storage'; const teardown = new TeardownCore({ org_id: 'your-org-id', project_id: 'your-project-id', api_key: 'your-api-key', storageAdapter: new AsyncStorageAdapter(), deviceAdapter: new DeviceInfoAdapter(), notificationAdapter: new WixNotificationsAdapter(), }); // Access via NotificationsClient if (teardown.notifications) { const status = await teardown.notifications.requestPermissions(); if (status.granted) { const token = await teardown.notifications.getToken(); console.log('Push token:', token); } } ``` Import Path [#import-path] ```typescript import { WixNotificationsAdapter } from '@teardown/force-updates/wix'; ``` API [#api] All methods are accessed via `NotificationsClient`, not directly on the adapter. requestPermissions() [#requestpermissions] Request notification permissions: ```typescript const status = await teardown.notifications.requestPermissions(); // Returns: { granted: boolean, canAskAgain: boolean } ``` getToken() [#gettoken] Get the device push token: ```typescript const token = await teardown.notifications.getToken(); // Returns: device token string or null ``` onNotificationReceived() [#onnotificationreceived] Subscribe to foreground notifications: ```typescript const unsubscribe = teardown.notifications.onNotificationReceived((notification) => { console.log('Received:', notification.title); }); // Cleanup unsubscribe(); ``` onNotificationOpened() [#onnotificationopened] Subscribe to notification taps: ```typescript const unsubscribe = teardown.notifications.onNotificationOpened((notification) => { console.log('User tapped:', notification.title); }); // Cleanup unsubscribe(); ``` onTokenRefresh() [#ontokenrefresh] Subscribe to token changes: ```typescript const unsubscribe = teardown.notifications.onTokenChange((token) => { console.log('New token:', token); }); // Cleanup unsubscribe(); ``` onDataMessage() [#ondatamessage] Subscribe to data-only messages (silent push): ```typescript const unsubscribe = teardown.notifications.onDataMessage((message) => { console.log('Data message:', message.data); }); // Cleanup unsubscribe(); ``` Configuration [#configuration] iOS [#ios] 1. Enable Push Notifications capability in Xcode 2. Add to `AppDelegate.m`: ```objc #import - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [RNNotifications startMonitorNotifications]; return YES; } - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { [RNNotifications didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; } - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { [RNNotifications didFailToRegisterForRemoteNotificationsWithError:error]; } ``` Android [#android] 1. Add Firebase configuration (google-services.json) 2. Initialize in `MainApplication.java`: ```java import com.wix.reactnativenotifications.RNNotificationsPackage; ``` Platform [#platform] Returns `NotificationPlatformEnum.APNS` on iOS, `NotificationPlatformEnum.FCM` on Android. When to Use [#when-to-use] * Existing projects using react-native-notifications * Projects requiring fine-grained notification control * Cross-platform notification handling Requirements [#requirements] * `react-native-notifications` >= 4.0.0 * React Native >= 0.60 # AsyncStorageAdapter (/docs/react-native/adapters/storage/async-storage) The `AsyncStorageAdapter` uses the community AsyncStorage library for persistent storage with broad platform compatibility. Installation [#installation] ```bash npm install @react-native-async-storage/async-storage # or bun add @react-native-async-storage/async-storage ``` For Expo: ```bash npx expo install @react-native-async-storage/async-storage ``` Usage [#usage] ```typescript import { TeardownCore } from '@teardown/force-updates'; import { DeviceInfoAdapter } from '@teardown/force-updates/adapters/device-info'; import { AsyncStorageAdapter } from '@teardown/force-updates/adapters/async-storage'; export const teardown = new TeardownCore({ org_id: 'your-org-id', project_id: 'your-project-id', api_key: 'your-api-key', storageAdapter: new AsyncStorageAdapter(), deviceAdapter: new DeviceInfoAdapter(), }); ``` Import Path [#import-path] ```typescript import { AsyncStorageAdapter } from '@teardown/force-updates/adapters/async-storage'; ``` When to Use [#when-to-use] * Web support needed * Existing AsyncStorage setup in your app * Simpler debugging (plain text storage) * Expo Go compatibility (no dev build needed) Trade-offs [#trade-offs] Pros [#pros] * Broader platform support (iOS, Android, Web) * Works in Expo Go without dev build * Plain text storage (easier debugging) * Well-established, stable API Cons [#cons] * Asynchronous API (slightly slower) * No built-in encryption * Larger bundle size than MMKV How It Works [#how-it-works] AsyncStorage provides a key-value storage API: 1. The adapter wraps AsyncStorage with a synchronous-like interface 2. Uses `preload()` to hydrate data into memory at startup 3. Writes are persisted asynchronously Requirements [#requirements] * `@react-native-async-storage/async-storage` >= 1.17.0 * React Native >= 0.60 Platform Notes [#platform-notes] iOS [#ios] * Data stored in NSUserDefaults (small data) or files (large data) * Not encrypted by default Android [#android] * Data stored in SQLite database * Not encrypted by default Web [#web] * Uses localStorage * Size limited (\~5MB) Encryption Note [#encryption-note] AsyncStorage does not encrypt data by default. For sensitive data, consider: * Using MMKV instead (encrypted by default) * Encrypting values before storage * Using a separate encrypted storage for secrets # Storage Adapters (/docs/react-native/adapters/storage) Storage adapters handle persistent data storage for the SDK. You must provide a storage adapter when initializing `TeardownCore`. Available Adapters [#available-adapters] | Adapter | Package | Sync/Async | Best For | | -------------------------------------------------------------------------- | ------------------------------------------- | ---------- | ------------------------- | | [`MMKVStorageAdapter`](/docs/react-native/adapters/storage/mmkv) | `react-native-mmkv` | Sync | Performance (recommended) | | [`AsyncStorageAdapter`](/docs/react-native/adapters/storage/async-storage) | `@react-native-async-storage/async-storage` | Async | Compatibility | Storage Interface [#storage-interface] All storage adapters implement: ```typescript interface SupportedStorage { preload(): void; getItem(key: string): string | null; setItem(key: string, value: string): void; removeItem(key: string): void; clear(): void; keys(): string[]; } ``` What Gets Stored [#what-gets-stored] The SDK stores: | Data | Storage Key | Description | | -------------- | ------------------------- | ------------------------ | | Identity State | `identity:IDENTIFY_STATE` | Session, user ID, token | | Version Status | `version:version_status` | Last known update status | | Device ID | `device:device_id` | Stable device UUID | Namespacing [#namespacing] Keys are prefixed with org/project IDs to prevent conflicts: ``` teardown:{org_id}:{project_id}:identity:IDENTIFY_STATE teardown:{org_id}:{project_id}:version:version_status teardown:{org_id}:{project_id}:device:device_id ``` Custom Adapter [#custom-adapter] Extend `StorageAdapter` for custom implementations: ```typescript import { StorageAdapter, type SupportedStorage } from '@teardown/force-updates'; class CustomStorageAdapter extends StorageAdapter { createStorage(storageKey: string): SupportedStorage { return { preload: () => { // Load data into memory (called on init) }, getItem: (key: string) => { return myStorage.get(`${storageKey}:${key}`); }, setItem: (key: string, value: string) => { myStorage.set(`${storageKey}:${key}`, value); }, removeItem: (key: string) => { myStorage.delete(`${storageKey}:${key}`); }, clear: () => { // Clear all keys for this namespace }, keys: () => { // Return all keys for this namespace return []; }, }; } } ``` Comparison [#comparison] | Feature | MMKV | AsyncStorage | | ---------------- | ------------- | ----------------- | | Speed | Faster (sync) | Slower (async) | | Encryption | Built-in | No | | Bundle Size | Smaller | Larger | | Debugging | Binary data | Plain text | | Platform Support | iOS, Android | iOS, Android, Web | # MMKVStorageAdapter (/docs/react-native/adapters/storage/mmkv) The `MMKVStorageAdapter` uses MMKV for high-performance, encrypted storage. **Recommended for most apps.** Installation [#installation] ```bash npm install react-native-mmkv # or bun add react-native-mmkv ``` For iOS, run pod install: ```bash cd ios && pod install ``` Usage [#usage] ```typescript import { TeardownCore } from '@teardown/force-updates'; import { ExpoDeviceAdapter } from '@teardown/force-updates/adapters/expo'; import { MMKVStorageAdapter } from '@teardown/force-updates/adapters/mmkv'; export const teardown = new TeardownCore({ org_id: 'your-org-id', project_id: 'your-project-id', api_key: 'your-api-key', storageAdapter: new MMKVStorageAdapter(), deviceAdapter: new ExpoDeviceAdapter(), }); ``` Import Path [#import-path] ```typescript import { MMKVStorageAdapter } from '@teardown/force-updates/adapters/mmkv'; ``` Benefits [#benefits] * **Synchronous** - No async/await needed for reads/writes * **Encrypted** - Data encrypted by default * **Fast** - Memory-mapped for optimal performance * **Small** - Minimal bundle size impact * **Reliable** - Used by WeChat with 1B+ users How It Works [#how-it-works] MMKV uses memory-mapped files for storage: 1. Data is written to memory 2. OS flushes to disk asynchronously 3. Reads are instant (from memory) 4. Crash-safe with journaling Requirements [#requirements] * `react-native-mmkv` >= 2.0.0 * React Native >= 0.71 Platform Notes [#platform-notes] iOS [#ios] * Requires `pod install` * Data stored in app's sandboxed Documents directory * Encrypted with iOS Keychain Android [#android] * Works out of the box * Data stored in app's private directory * Encrypted with Android KeyStore Expo Compatibility [#expo-compatibility] For Expo managed workflow, you need a development build: ```bash npx expo install react-native-mmkv npx expo prebuild ``` Or use EAS Build for production. # Advanced Usage (/docs/react-native/advanced) Multiple Environments [#multiple-environments] Use the `environment_slug` prop to target different environments within a single project: ```typescript const getEnvironment = () => __DEV__ ? 'development' : 'production'; export const teardown = new TeardownCore({ org_id: 'your-org-id', project_id: 'your-project-id', api_key: 'your-api-key', environment_slug: getEnvironment(), storageAdapter: new MMKVStorageAdapter(), deviceAdapter: new ExpoDeviceAdapter(), }); ``` Environments are configured in your project's dashboard settings. Common slugs include `development`, `staging`, and `production` (default). Push Notifications Setup [#push-notifications-setup] Enable push notifications by providing a notification adapter: ```typescript import { TeardownCore } from '@teardown/force-updates'; import { ExpoNotificationsAdapter, ExpoDeviceAdapter } from '@teardown/force-updates/expo'; import { MMKVStorageAdapter } from '@teardown/force-updates/adapters/mmkv'; const teardown = new TeardownCore({ org_id: 'your-org-id', project_id: 'your-project-id', api_key: 'your-api-key', storageAdapter: new MMKVStorageAdapter(), deviceAdapter: new ExpoDeviceAdapter(), notificationAdapter: new ExpoNotificationsAdapter(), // Enable notifications }); // Request permissions and get token if (teardown.notifications) { const status = await teardown.notifications.requestPermissions(); if (status.granted) { const token = await teardown.notifications.getToken(); // Token is automatically sent with identify() calls } } ``` Handle Notifications [#handle-notifications] ```typescript const { core } = useTeardown(); useEffect(() => { if (!core.notifications) return; const unsubs = [ core.notifications.onNotificationReceived((notification) => { // Foreground notification console.log('Notification:', notification.title); }), core.notifications.onNotificationOpened((notification) => { // User tapped notification navigateToScreen(notification.data?.screen); }), core.notifications.onDataMessage((message) => { // Silent/data message syncData(message.data); }), ]; return () => unsubs.forEach(unsub => unsub()); }, [core.notifications]); ``` Event Tracking [#event-tracking] Track custom events: ```typescript const { core } = useTeardown(); // Track single event await core.events.track({ event_name: 'purchase_completed', event_type: 'action', properties: { product_id: 'abc123', amount: 99.99, currency: 'USD', }, }); // Track multiple events await core.events.trackBatch([ { event_name: 'cart_viewed', event_type: 'screen_view' }, { event_name: 'item_added', properties: { item_id: 'xyz' } }, ]); ``` Disable Force Update Checking [#disable-force-update-checking] For debugging or testing: ```typescript const teardown = new TeardownCore({ // ... forceUpdate: { checkIntervalMs: -1, // Disable automatic checking }, }); ``` Custom Version Check Interval [#custom-version-check-interval] Check more or less frequently: ```typescript const teardown = new TeardownCore({ // ... forceUpdate: { checkIntervalMs: 60_000, // Check every minute checkOnForeground: true, }, }); ``` Manual Version Checking [#manual-version-checking] Trigger version check programmatically: ```typescript const { core } = useTeardown(); const checkForUpdates = async () => { const result = await core.identity.refresh(); if (result.success) { // Version status will update via subscription console.log('Version checked'); } }; ``` Offline Support [#offline-support] The SDK handles offline scenarios gracefully: 1. **Cached session** - Identity state persists across restarts 2. **Cached version status** - Last known status is preserved 3. **Retry on foreground** - Re-checks when connectivity restored ```typescript const session = useSession(); // Works offline - returns cached session if (session) { console.log('User:', session.user_id); } ``` Pre-warming [#pre-warming] Initialize SDK before app is fully loaded: ```typescript // In app entry point (before React renders) import { teardown } from './lib/teardown'; // SDK starts initializing immediately // By the time UI renders, it may already be ready ``` Error Boundaries [#error-boundaries] Handle SDK errors gracefully: ```tsx import { ErrorBoundary } from 'react-error-boundary'; function App() { return ( }> ); } ``` Testing [#testing] Mock the SDK [#mock-the-sdk] ```typescript // __mocks__/@teardown/force-updates.ts export const TeardownProvider = ({ children }) => children; export const useTeardown = () => ({ core: { identity: { identify: jest.fn().mockResolvedValue({ success: true, data: mockSession }), signOut: jest.fn().mockResolvedValue({ success: true, data: undefined }), signOutAll: jest.fn().mockResolvedValue({ success: true, data: undefined }), getSessionState: () => mockSession, }, forceUpdate: { getVersionStatus: () => ({ type: 'up_to_date' }), }, events: { track: jest.fn().mockResolvedValue({ success: true, data: undefined }), trackBatch: jest.fn().mockResolvedValue({ success: true, data: undefined }), }, notifications: { requestPermissions: jest.fn().mockResolvedValue({ granted: true, canAskAgain: true }), getToken: jest.fn().mockResolvedValue('mock-push-token'), onNotificationReceived: jest.fn().mockReturnValue(() => {}), onNotificationOpened: jest.fn().mockReturnValue(() => {}), onDataMessage: jest.fn().mockReturnValue(() => {}), }, }, }); export const useSession = () => mockSession; export const useForceUpdate = () => ({ versionStatus: { type: 'up_to_date' }, isUpdateRequired: false, isUpdateRecommended: false, isUpdateAvailable: false, releaseNotes: null, }); ``` Test Components [#test-components] ```typescript import { render } from '@testing-library/react-native'; test('shows login when not identified', () => { jest.spyOn(hooks, 'useSession').mockReturnValue(null); const { getByText } = render(); expect(getByText('Login')).toBeTruthy(); }); ``` Debugging [#debugging] Enable Verbose Logging [#enable-verbose-logging] ```typescript teardown.setLogLevel('verbose'); ``` Inspect State [#inspect-state] ```typescript const { core } = useTeardown(); console.log('Identity state:', core.identity.getIdentifyState()); console.log('Version status:', core.forceUpdate.getVersionStatus()); console.log('Device ID:', await core.device.getDeviceId()); ``` Clear All Data [#clear-all-data] For debugging or logout: ```typescript // Sign out - clears session, keeps device ID await core.identity.signOut(); // Full reset - clears session AND device ID (fresh install state) await core.identity.signOutAll(); ``` Performance [#performance] Minimize Re-renders [#minimize-re-renders] Use specific hooks instead of broad subscriptions: ```tsx // Good - only re-renders on session change const session = useSession(); // Avoid - accessing core gives no reactivity const { core } = useTeardown(); const session = core.identity.getSessionState(); // Not reactive! ``` Lazy Initialization [#lazy-initialization] Defer SDK init until needed: ```typescript let teardownInstance: TeardownCore | null = null; export const getTeardown = () => { if (!teardownInstance) { teardownInstance = new TeardownCore({ /* ... */ }); } return teardownInstance; }; ``` Cleanup [#cleanup] Properly cleanup on app shutdown: ```typescript import { AppState } from 'react-native'; useEffect(() => { const subscription = AppState.addEventListener('change', (state) => { if (state === 'background') { // SDK persists state automatically, no action needed } }); return () => { subscription.remove(); teardown.shutdown(); }; }, []); ``` TypeScript [#typescript] Strict Types [#strict-types] All SDK types are exported: ```typescript import type { TeardownCore, TeardownCoreOptions, Session, IdentifyState, VersionStatus, Persona, AsyncResult, } from '@teardown/force-updates'; ``` Type Guards [#type-guards] ```typescript function handleIdentifyState(state: IdentifyState) { switch (state.type) { case 'unidentified': // state is UnidentifiedSessionState break; case 'identifying': // state is IdentifyingSessionState break; case 'identified': // state is IdentifiedSessionState console.log(state.session.user_id); break; } } ``` # API Reference (/docs/react-native/api-reference) TeardownCore [#teardowncore] The main SDK class that orchestrates all clients. Constructor [#constructor] ```typescript new TeardownCore(options: TeardownCoreOptions) ``` TeardownCoreOptions [#teardowncoreoptions] | Property | Type | Required | Description | | --------------------- | -------------------------- | -------- | ---------------------------------------- | | `org_id` | `string` | Yes | Organization ID from dashboard | | `project_id` | `string` | Yes | Project ID from dashboard | | `api_key` | `string` | Yes | API key from dashboard | | `environment_slug` | `string` | No | Environment slug (default: "production") | | `ingestUrl` | `string` | No | Custom ingest API URL for local/staging | | `storageAdapter` | `StorageAdapter` | Yes | Storage adapter instance | | `deviceAdapter` | `DeviceInfoAdapter` | Yes | Device info adapter instance | | `notificationAdapter` | `NotificationAdapter` | No | Push notification adapter instance | | `forceUpdate` | `ForceUpdateClientOptions` | No | Force update configuration | Methods [#methods] | Method | Returns | Description | | -------------------- | ------- | --------------------- | | `setLogLevel(level)` | `void` | Set logging verbosity | | `shutdown()` | `void` | Cleanup SDK resources | Properties [#properties] | Property | Type | Description | | --------------- | ---------------------------------- | ----------------------------------------------- | | `api` | `ApiClient` | Backend API client | | `device` | `DeviceClient` | Device information client | | `events` | `EventsClient` | Event tracking client | | `identity` | `IdentityClient` | Identity management client | | `forceUpdate` | `ForceUpdateClient` | Version checking client | | `notifications` | `NotificationsClient \| undefined` | Push notifications client (if adapter provided) | *** IdentityClient [#identityclient] Manages user and device identification. Methods [#methods-1] | Method | Returns | Description | | --------------------------------- | --------------------------- | ------------------------------ | | `identify(persona?)` | `AsyncResult` | Identify user/device | | `refresh()` | `AsyncResult` | Re-identify current user | | `signOut(options?)` | `AsyncResult` | Sign out user, preserve device | | `signOutAll(options?)` | `AsyncResult` | Sign out and reset device | | `getIdentifyState()` | `IdentifyState` | Get current state | | `getSessionState()` | `Session \| null` | Get current session | | `onIdentifyStateChange(listener)` | `() => void` | Subscribe to state changes | | `shutdown()` | `void` | Cleanup listeners | Persona [#persona] ```typescript interface Persona { user_id?: string; email?: string; name?: string; } ``` Session [#session] ```typescript interface Session { session_id: string; device_id: string; user_id: string; token: string; } ``` IdentifyState [#identifystate] ```typescript type IdentifyState = | { type: 'unidentified' } | { type: 'identifying' } | { type: 'identified'; session: Session; version_info: VersionInfo }; ``` SignOutOptions [#signoutoptions] ```typescript interface SignOutOptions { // Additional properties to include in the sign out event properties?: Record; // Wait for event to be sent before clearing state (default: true) waitForEvent?: boolean; } ``` *** ForceUpdateClient [#forceupdateclient] Manages version checking and force updates. Methods [#methods-2] | Method | Returns | Description | | --------------------------------- | --------------- | --------------------------- | | `getVersionStatus()` | `VersionStatus` | Get current version status | | `onVersionStatusChange(listener)` | `() => void` | Subscribe to status changes | | `shutdown()` | `void` | Cleanup listeners | ForceUpdateClientOptions [#forceupdateclientoptions] | Property | Type | Default | Description | | ------------------------- | --------- | -------- | ---------------------------------------------------------------- | | `checkIntervalMs` | `number` | `300000` | Min time between checks. `0` = every foreground, `-1` = disabled | | `checkOnForeground` | `boolean` | `true` | Check when app foregrounds | | `identifyAnonymousDevice` | `boolean` | `false` | Check when not identified | VersionStatus [#versionstatus] ```typescript type VersionStatus = | { type: 'initializing' } | { type: 'checking' } | { type: 'up_to_date' } | { type: 'update_available'; releaseNotes?: string | null } | { type: 'update_recommended'; releaseNotes?: string | null } | { type: 'update_required'; releaseNotes?: string | null } | { type: 'disabled' }; ``` *** EventsClient [#eventsclient] Tracks events to the backend. Methods [#methods-3] | Method | Returns | Description | | -------------------------------- | ------------------- | --------------------- | | `track(event, sessionId?)` | `AsyncResult` | Track a single event | | `trackBatch(events, sessionId?)` | `AsyncResult` | Track multiple events | EventPayload [#eventpayload] ```typescript interface EventPayload { /** Name of the event (e.g., "button_clicked", "page_viewed") */ event_name: string; /** Type of event */ event_type?: "action" | "screen_view" | "custom"; /** Additional properties to attach to the event */ properties?: Record; /** ISO timestamp. Defaults to current time if not provided */ timestamp?: string; } ``` Usage [#usage] ```typescript const { core } = useTeardown(); // Track a single event await core.events.track({ event_name: 'button_clicked', event_type: 'action', properties: { button_id: 'submit-form' } }); // Track multiple events await core.events.trackBatch([ { event_name: 'page_viewed', event_type: 'screen_view', properties: { page: 'home' } }, { event_name: 'cta_shown', event_type: 'action' } ]); ``` *** NotificationsClient [#notificationsclient] Manages push notifications across different providers. Only available if `notificationAdapter` is provided in TeardownCore options. Methods [#methods-4] | Method | Returns | Description | | ---------------------------------- | --------------------------- | ------------------------------------- | | `requestPermissions()` | `Promise` | Request notification permissions | | `getToken()` | `Promise` | Get push notification token | | `onTokenChange(listener)` | `Unsubscribe` | Subscribe to token changes | | `onNotificationReceived(listener)` | `Unsubscribe` | Subscribe to foreground notifications | | `onNotificationOpened(listener)` | `Unsubscribe` | Subscribe to notification taps | | `onDataMessage(listener)` | `Unsubscribe` | Subscribe to silent/data messages | | `destroy()` | `void` | Cleanup resources | Properties [#properties-1] | Property | Type | Description | | ---------- | -------------------------- | --------------------------------------- | | `platform` | `NotificationPlatformEnum` | Notification platform (APNS, FCM, EXPO) | PermissionStatus [#permissionstatus] ```typescript interface PermissionStatus { granted: boolean; canAskAgain: boolean; } ``` PushNotification [#pushnotification] ```typescript interface PushNotification { title?: string; body?: string; data?: Record; } ``` DataMessage [#datamessage] ```typescript interface DataMessage { data: Record; } ``` NotificationPlatformEnum [#notificationplatformenum] ```typescript enum NotificationPlatformEnum { APNS = "APNS", // Apple Push Notification Service FCM = "FCM", // Firebase Cloud Messaging EXPO = "EXPO", // Expo Push Notifications } ``` Usage [#usage-1] ```typescript const { core } = useTeardown(); if (core.notifications) { // Request permissions const status = await core.notifications.requestPermissions(); // Get token for backend const token = await core.notifications.getToken(); // Listen for foreground notifications const unsub = core.notifications.onNotificationReceived((notification) => { console.log('Notification:', notification.title); }); // Cleanup unsub(); } ``` *** DeviceClient [#deviceclient] Collects device information. Methods [#methods-5] | Method | Returns | Description | | ----------------- | --------------------- | ------------------------------------------- | | `getDeviceId()` | `Promise` | Get stable device UUID | | `getDeviceInfo()` | `Promise` | Get full device info | | `reset()` | `void` | Clear stored deviceId (new ID on next call) | DeviceInfo [#deviceinfo] ```typescript interface DeviceInfo { timestamp?: Date; os: OSInfo; hardware: HardwareInfo; application: ApplicationInfo; update: DeviceUpdateInfo | null; notifications?: NotificationsInfo; } ``` *** StorageAdapter [#storageadapter] Abstract class for storage implementations. Methods [#methods-6] | Method | Returns | Description | | -------------------- | ------------------ | ------------------------- | | `createStorage(key)` | `SupportedStorage` | Create namespaced storage | SupportedStorage [#supportedstorage] ```typescript interface SupportedStorage { preload(): void; getItem(key: string): string | null; setItem(key: string, value: string): void; removeItem(key: string): void; clear(): void; keys(): string[]; } ``` *** DeviceInfoAdapter [#deviceinfoadapter] Interface for device information adapters. Properties [#properties-2] | Property | Type | Description | | ----------------- | ----------------- | --------------------- | | `applicationInfo` | `ApplicationInfo` | App version info | | `hardwareInfo` | `HardwareInfo` | Device hardware info | | `osInfo` | `OSInfo` | Operating system info | ApplicationInfo [#applicationinfo] ```typescript interface ApplicationInfo { version: string; build_number: number; } ``` HardwareInfo [#hardwareinfo] ```typescript interface HardwareInfo { device_name: string; device_brand: string; device_type: string; } ``` OSInfo [#osinfo] ```typescript interface OSInfo { platform: DevicePlatformEnum; name: string; version: string; } ``` DevicePlatformEnum [#deviceplatformenum] ```typescript enum DevicePlatformEnum { IOS = "IOS", ANDROID = "ANDROID", WEB = "WEB", WINDOWS = "WINDOWS", MACOS = "MACOS", LINUX = "LINUX", UNKNOWN = "UNKNOWN", // ... and more } ``` *** AsyncResult [#asyncresult] Type-safe result type for async operations. ```typescript type AsyncResult = | { success: true; data: T } | { success: false; error: string }; ``` Usage [#usage-2] ```typescript const result = await core.identity.identify({ user_id: '123' }); if (result.success) { console.log(result.data.session_id); } else { console.error(result.error); } ``` # Core Concepts (/docs/react-native/core-concepts) Architecture Overview [#architecture-overview] The Teardown SDK is built around a central `TeardownCore` class that orchestrates multiple specialized clients: ``` TeardownCore ├── IdentityClient # User/device identification ├── ForceUpdateClient # Version checking ├── DeviceClient # Device information ├── EventsClient # Event tracking ├── NotificationsClient # Push notifications (optional) ├── StorageClient # Persistent storage ├── ApiClient # Backend communication └── LoggingClient # Structured logging ``` Initialization Flow [#initialization-flow] When you create a `TeardownCore` instance, the following happens: 1. **Storage Hydration** - Storage adapters load persisted state 2. **Identity Initialization** - Loads cached session, then identifies with backend 3. **Force Update Setup** - Subscribes to identity events for version checking 4. **Ready State** - SDK is fully operational ```typescript const teardown = new TeardownCore({ org_id: 'your-org-id', project_id: 'your-project-id', api_key: 'your-api-key', storageAdapter: new MMKVStorageAdapter(), deviceAdapter: new ExpoDeviceAdapter(), }); // Initialization happens automatically in the background ``` State Management [#state-management] The SDK uses an event-driven architecture with discriminated unions for type-safe state: Identity State [#identity-state] ```typescript type IdentifyState = | { type: 'unidentified' } | { type: 'identifying' } | { type: 'identified'; session: Session; version_info: VersionInfo }; ``` Version Status [#version-status] ```typescript type VersionStatus = | { type: 'initializing' } | { type: 'checking' } | { type: 'up_to_date' } | { type: 'update_available'; releaseNotes?: string | null } | { type: 'update_recommended'; releaseNotes?: string | null } | { type: 'update_required'; releaseNotes?: string | null } | { type: 'disabled' }; ``` Adapter Pattern [#adapter-pattern] The SDK uses adapters to abstract platform-specific functionality: Storage Adapters [#storage-adapters] Handle persistent data storage with a consistent interface: ```typescript interface SupportedStorage { preload(): void; getItem(key: string): string | null; setItem(key: string, value: string): void; removeItem(key: string): void; clear(): void; keys(): string[]; } ``` Device Adapters [#device-adapters] Provide device and app information: ```typescript abstract class DeviceInfoAdapter { applicationInfo: ApplicationInfo; // version, build_number hardwareInfo: HardwareInfo; // device_name, device_brand, device_type osInfo: OSInfo; // platform, name, version } ``` Notification Adapters [#notification-adapters] Handle push notification registration and events (optional): ```typescript abstract class NotificationAdapter { platform: NotificationPlatformEnum; getToken(): Promise; requestPermissions(): Promise; onTokenRefresh(listener): Unsubscribe; onNotificationReceived(listener): Unsubscribe; onNotificationOpened(listener): Unsubscribe; onDataMessage(listener): Unsubscribe; } ``` React Integration [#react-integration] The SDK provides React primitives for seamless integration: TeardownProvider [#teardownprovider] Context provider that makes the SDK available throughout your app: ```tsx ``` Hooks [#hooks] Reactive hooks that subscribe to SDK state changes: * `useTeardown()` - Access the core instance * `useSession()` - Get current session (reactive) * `useForceUpdate()` - Get version status (reactive) Persistence [#persistence] The SDK automatically persists: * **Session data** - Device ID, user ID, token * **Version status** - Last known update state * **Device ID** - Stable device identifier State is restored on app restart, providing offline-first functionality. Error Handling [#error-handling] All async operations return `AsyncResult` for type-safe error handling: ```typescript type AsyncResult = | { success: true; data: T } | { success: false; error: string }; const result = await core.identity.identify({ user_id: '123' }); if (result.success) { console.log(result.data.session_id); } else { console.error(result.error); } ``` Logging [#logging] The SDK includes a structured logging system with configurable levels: ```typescript teardown.setLogLevel('verbose'); // none | error | warn | info | verbose ``` Each client logs with a prefix for easy filtering: * `[Teardown:IdentityClient]` * `[Teardown:ForceUpdateClient]` * `[Teardown:DeviceClient]` # Force Updates (/docs/react-native/force-updates) The Force Update client automatically checks your app version against your configured release policy and can require users to update. Overview [#overview] Force updates work by: 1. Checking app version on identify 2. Checking again when app returns to foreground 3. Emitting status changes your app can respond to Version Status [#version-status] ```typescript type VersionStatus = | { type: 'initializing' } // SDK starting up | { type: 'checking' } // Version check in progress | { type: 'up_to_date' } // Current version is valid | { type: 'update_available'; releaseNotes?: string | null } // Optional update exists | { type: 'update_recommended'; releaseNotes?: string | null } // Recommended update exists | { type: 'update_required'; releaseNotes?: string | null } // Must update to continue | { type: 'disabled' } // Version/build has been disabled ``` Update status types include an optional `releaseNotes` field containing notes set in the dashboard when changing version/build status. Basic Usage [#basic-usage] Using the Hook [#using-the-hook] ```typescript import { useForceUpdate } from '@teardown/force-updates'; function App() { const { versionStatus, isUpdateRequired, isUpdateAvailable, isUpdateRecommended, releaseNotes } = useForceUpdate(); if (isUpdateRequired) { return ; } if (isUpdateRecommended) { return ( <> {}} /> ); } return ; } ``` Hook Return Values [#hook-return-values] ```typescript interface UseForceUpdateResult { versionStatus: VersionStatus; // Full status object isUpdateAvailable: boolean; // Any update exists (available, recommended, or required) isUpdateRecommended: boolean; // Update is recommended but not required isUpdateRequired: boolean; // Update is mandatory releaseNotes: string | null; // Release notes for the update, if available } ``` Configuration [#configuration] Configure force update behavior in `TeardownCore`: ```typescript const teardown = new TeardownCore({ // ...other options forceUpdate: { checkIntervalMs: 300_000, // 5 minutes (default) checkOnForeground: true, // Check when app foregrounds (default: true) identifyAnonymousDevice: false, // Check even when not identified (default: false) }, }); ``` Options [#options] | Option | Type | Default | Description | | ------------------------- | ------- | ------- | ------------------------------------------------------------------------------------------------------------------------ | | `checkIntervalMs` | number | 300000 | Minimum time between version checks. Use `0` for every foreground, `-1` to disable. Values below 30s are clamped to 30s. | | `checkOnForeground` | boolean | true | Check version when app returns to foreground | | `identifyAnonymousDevice` | boolean | false | Check version even when user is not identified | Direct API Access [#direct-api-access] Get Current Status [#get-current-status] ```typescript const status = core.forceUpdate.getVersionStatus(); ``` Subscribe to Changes [#subscribe-to-changes] ```typescript const unsubscribe = core.forceUpdate.onVersionStatusChange((status) => { if (status.type === 'update_required') { // Show force update modal } }); // Cleanup unsubscribe(); ``` Implementation Patterns [#implementation-patterns] Force Update Modal [#force-update-modal] ```tsx function ForceUpdateModal({ releaseNotes }: { releaseNotes: string | null }) { const handleUpdate = () => { // Open app store Linking.openURL('https://apps.apple.com/app/your-app'); }; return ( Update Required Please update to continue using the app. {releaseNotes && ( What's New: {releaseNotes} )}