diff --git a/contents/docs/expo/index.mdx b/contents/docs/expo/index.mdx new file mode 100644 index 0000000..4e79dcc --- /dev/null +++ b/contents/docs/expo/index.mdx @@ -0,0 +1,150 @@ +--- +title: Expo +--- + +Zero has built-in support for Expo and React Native using the `expo-sqlite` and `op-sqlite` packages. + +## Prerequisites + +The `crypto` API is not available in React Native, so we need to polyfill it. + + +First, install the `expo-crypto` package. See the [expo-crypto docs](https://docs.expo.dev/versions/latest/sdk/crypto/) for more information. + +```bash +npx expo install expo-crypto +``` + +Then create a new file in your project, e.g. `lib/crypto.ts`, and add the following code: + +```tsx +// lib/crypto.ts +import * as Crypto from 'expo-crypto'; + +declare const global: { + crypto: { + getRandomValues(array: Uint8Array): Uint8Array; + randomUUID(): string; + }; +}; + +export function bootCryptoPolyfill() { + if (global.crypto) { + return; + } + + global.crypto = { + getRandomValues(array: Uint8Array) { + return Crypto.getRandomValues(array); + }, + randomUUID() { + return Crypto.randomUUID(); + }, + }; +} +``` + +This will allow you to use the `crypto` API in your React Native project such as `crypto.getRandomValues()` and `crypto.randomUUID()`. + +## Expo SQLite + +For more information on how to use the `expo-sqlite` package, see the [expo-sqlite docs](https://docs.expo.dev/versions/latest/sdk/sqlite/). + +Remember to install the dependencies: + +```bash +npx expo install expo-sqlite +``` + +## OP-SQLite + +For more information on how to use the `op-sqlite` package, see the [op-sqlite docs](https://github.com/OP-Engineering/op-sqlite). + +Per the docs, if you are using Expo, you cannot add this library on a expo-go app, you need to pre-build your app. + +```bash +npx expo install @op-engineering/op-sqlite +npx expo prebuild +``` + +## Usage + +In your mobile app's root index or layout file, wrap your app's with the `ZeroProvider` component: + +```tsx +// apps/my-app/_layout.tsx +import '../globals.css'; +import {Zero} from '@rocicorp/zero'; +import {ZeroProvider} from '@rocicorp/zero/react'; +import {createExpoSQLiteStore} from '@rocicorp/zero/expo'; +// or if using op-sqlite +// import { createOPSQLiteStore } from '@rocicorp/zero/op-sqlite'; + +import {Stack} from 'expo-router'; +import {useMemo} from 'react'; +import {schema} from '../lib/schema'; // or wherever you have your schema + +export const unstable_settings = { + // Ensure that reloading on `/modal` keeps a back button present. + initialRouteName: '(tabs)', +}; + +export default function RootLayout() { + // In production, memoize the Zero instance so it's only created once per app lifecycle + // Note: If you intend to use Expo Web, you should use kvStore: 'mem' or 'idb' with a check like + // const store = Platform.OS === 'web' ? 'idb' : createExpoSQLiteStore; + const z = useMemo( + () => + new Zero({ + userID: 'your-user-id', + auth: 'your-auth-token', + server: process.env.EXPO_PUBLIC_SERVER_URL, // see https://docs.expo.dev/guides/environment-variables/ + kvStore: createExpoSQLiteStore, + // kvStore: createExpoSQLiteStore, // or if using op-sqlite + schema, + }), + [userId, authToken], + ); + + if (!z) { + return null; + } + + return ( + + + + + + + ); +} +``` + +Interact with the Zero instance in your components using the `zero/react` package. Please see the [React docs](react) for more details. + +```tsx +import {useQuery, useZero} from '@rocicorp/zero/react'; + +function IssueList() { + const z = useZero(); + + let issueQuery = z.query.issue + .related('creator') + .related('labels') + .limit(100); + + const userID = selectedUserID(); + + if (userID) { + issueQuery = issueQuery.where('creatorID', '=', userID); + } + + const [issues, issuesDetail] = useQuery(issueQuery); + + // Your component React Native JSX + return ...; +} +``` + +Complete quickstart here: COMING SOON diff --git a/lib/routes-config.ts b/lib/routes-config.ts index 665488d..e73b6d5 100644 --- a/lib/routes-config.ts +++ b/lib/routes-config.ts @@ -1,82 +1,83 @@ // for page navigation & to sort on leftbar export type EachRoute = { - title: string; - href: string; - noLink?: true; - items?: EachRoute[]; + title: string; + href: string; + noLink?: true; + items?: EachRoute[]; }; export const ROUTES: EachRoute[] = [ - { - title: 'Welcome', - href: '', - noLink: true, - items: [ - {title: 'Introduction', href: '/introduction'}, - {title: 'Quickstart', href: '/quickstart'}, - {title: 'Samples', href: '/samples'}, - ], - }, + { + title: "Welcome", + href: "", + noLink: true, + items: [ + { title: "Introduction", href: "/introduction" }, + { title: "Quickstart", href: "/quickstart" }, + { title: "Samples", href: "/samples" }, + ], + }, - { - title: 'Using Zero', - href: '', - noLink: true, - items: [ - //TODO - //{title: 'How Zero Works', href: '/overview'}, - {title: 'Connecting to Postgres', href: '/connecting-to-postgres'}, - {title: 'Supported Postgres Features', href: '/postgres-support'}, - {title: 'Zero Schema', href: '/zero-schema'}, - {title: 'Reading Data with ZQL', href: '/reading-data'}, - {title: 'Writing Data with Mutators', href: '/writing-data'}, - {title: 'Authentication', href: '/auth'}, - {title: 'Permissions', href: '/permissions'}, - {title: 'Preloading', href: '/preloading'}, - {title: 'Schema Migrations', href: '/migrations'}, - {title: 'Deployment', href: '/deployment'}, - {title: '`zero-cache` Config', href: '/zero-cache-config'}, - {title: 'Recipes', href: '/recipes'}, - ], - }, + { + title: "Using Zero", + href: "", + noLink: true, + items: [ + //TODO + //{title: 'How Zero Works', href: '/overview'}, + { title: "Connecting to Postgres", href: "/connecting-to-postgres" }, + { title: "Supported Postgres Features", href: "/postgres-support" }, + { title: "Zero Schema", href: "/zero-schema" }, + { title: "Reading Data with ZQL", href: "/reading-data" }, + { title: "Writing Data with Mutators", href: "/writing-data" }, + { title: "Authentication", href: "/auth" }, + { title: "Permissions", href: "/permissions" }, + { title: "Preloading", href: "/preloading" }, + { title: "Schema Migrations", href: "/migrations" }, + { title: "Deployment", href: "/deployment" }, + { title: "`zero-cache` Config", href: "/zero-cache-config" }, + { title: "Recipes", href: "/recipes" }, + ], + }, - { - title: 'Integrations', - href: '', - noLink: true, - items: [ - {title: 'React', href: '/react'}, - {title: 'SolidJS', href: '/solidjs'}, - {title: 'Community', href: '/community'}, - ], - }, + { + title: "Integrations", + href: "", + noLink: true, + items: [ + { title: "React", href: "/react" }, + { title: "Expo", href: "/expo" }, + { title: "SolidJS", href: "/solidjs" }, + { title: "Community", href: "/community" }, + ], + }, - { - title: 'Meta', - href: '', - noLink: true, - items: [ - {title: 'Roadmap', href: '/roadmap'}, - {title: 'Reporting Bugs', href: '/reporting-bugs'}, - {title: 'Release Notes', href: '/release-notes'}, - {title: 'Open Source', href: '/open-source'}, - ], - }, + { + title: "Meta", + href: "", + noLink: true, + items: [ + { title: "Roadmap", href: "/roadmap" }, + { title: "Reporting Bugs", href: "/reporting-bugs" }, + { title: "Release Notes", href: "/release-notes" }, + { title: "Open Source", href: "/open-source" }, + ], + }, ]; -type Page = {title: string; href: string}; +type Page = { title: string; href: string }; function getRecurrsiveAllLinks(node: EachRoute) { - const ans: Page[] = []; - if (!node.noLink) { - ans.push({title: node.title, href: node.href}); - } - node.items?.forEach(subNode => { - const temp = {...subNode, href: `${node.href}${subNode.href}`}; - ans.push(...getRecurrsiveAllLinks(temp)); - }); - return ans; + const ans: Page[] = []; + if (!node.noLink) { + ans.push({ title: node.title, href: node.href }); + } + node.items?.forEach((subNode) => { + const temp = { ...subNode, href: `${node.href}${subNode.href}` }; + ans.push(...getRecurrsiveAllLinks(temp)); + }); + return ans; } -export const page_routes = ROUTES.map(it => getRecurrsiveAllLinks(it)).flat(); +export const page_routes = ROUTES.flatMap((it) => getRecurrsiveAllLinks(it));