From 88e1b087f04d79adab4fb8b9197d230860e42c07 Mon Sep 17 00:00:00 2001 From: Austin Mudd <31991302+austinm911@users.noreply.github.com> Date: Wed, 19 Feb 2025 23:10:00 -0800 Subject: [PATCH 1/2] wip --- contents/docs/expo/index.mdx | 148 +++++++++++++++++++++++++++++ contents/docs/react-expo/index.mdx | 87 +++++++++++++++++ lib/routes-config.ts | 135 +++++++++++++------------- 3 files changed, 303 insertions(+), 67 deletions(-) create mode 100644 contents/docs/expo/index.mdx create mode 100644 contents/docs/react-expo/index.mdx diff --git a/contents/docs/expo/index.mdx b/contents/docs/expo/index.mdx new file mode 100644 index 0000000..c995c7f --- /dev/null +++ b/contents/docs/expo/index.mdx @@ -0,0 +1,148 @@ +--- +title: Expo +--- + +Zero has built-in support for Expo and React Native using the `expo-sqlite` and `op-sqlite` packages. + +## Prerequisites + +The `crypto.getRandomValues()` API is not available in React Native, so we need to polyfill it. + +### Polyfill manually + +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() { + 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 layout, wrap your app's root component in 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() { + // 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, // initialize the SQLite store + // kvStore: createExpoSQLiteStore, // or if using op-sqlite + schema, + }), + [], + ); + + 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 ReactJSX + return ...; +} +``` + +Complete quickstart here: COMING SOON diff --git a/contents/docs/react-expo/index.mdx b/contents/docs/react-expo/index.mdx new file mode 100644 index 0000000..68aa046 --- /dev/null +++ b/contents/docs/react-expo/index.mdx @@ -0,0 +1,87 @@ +--- +title: React Expo +--- + +Zero has built-in support for React Expo using the `expo-sqlite` and `op-sqlite` packages. + +In your mobile app's layout, wrap your app's root component in 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() { + // Memoize the Zero instance so it's only created once per app lifecycle + const zero = 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, // initialize the SQLite store + // kvStore: createExpoSQLiteStore, // or if using op-sqlite + schema, + }), + [] + ); + + 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 ReactJSX + return ( + + ... + + ) +} +``` + +Complete quickstart here: COMING SOON \ No newline at end of file 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)); From a75125190c1be937dce88a8bdb08e8908926a2dc Mon Sep 17 00:00:00 2001 From: Austin Mudd <31991302+austinm911@users.noreply.github.com> Date: Wed, 19 Feb 2025 23:10:12 -0800 Subject: [PATCH 2/2] wip --- contents/docs/expo/index.mdx | 24 +++++---- contents/docs/react-expo/index.mdx | 87 ------------------------------ 2 files changed, 13 insertions(+), 98 deletions(-) delete mode 100644 contents/docs/react-expo/index.mdx diff --git a/contents/docs/expo/index.mdx b/contents/docs/expo/index.mdx index c995c7f..4e79dcc 100644 --- a/contents/docs/expo/index.mdx +++ b/contents/docs/expo/index.mdx @@ -6,9 +6,8 @@ Zero has built-in support for Expo and React Native using the `expo-sqlite` and ## Prerequisites -The `crypto.getRandomValues()` API is not available in React Native, so we need to polyfill it. +The `crypto` API is not available in React Native, so we need to polyfill it. -### Polyfill manually First, install the `expo-crypto` package. See the [expo-crypto docs](https://docs.expo.dev/versions/latest/sdk/crypto/) for more information. @@ -30,6 +29,10 @@ declare const global: { }; export function bootCryptoPolyfill() { + if (global.crypto) { + return; + } + global.crypto = { getRandomValues(array: Uint8Array) { return Crypto.getRandomValues(array); @@ -43,7 +46,6 @@ export function bootCryptoPolyfill() { 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/). @@ -67,7 +69,7 @@ npx expo prebuild ## Usage -In your mobile app's layout, wrap your app's root component in the `ZeroProvider` component: +In your mobile app's root index or layout file, wrap your app's with the `ZeroProvider` component: ```tsx // apps/my-app/_layout.tsx @@ -88,7 +90,7 @@ export const unstable_settings = { }; export default function RootLayout() { - // Memoize the Zero instance so it's only created once per app lifecycle + // 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( @@ -97,11 +99,11 @@ export default function RootLayout() { 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, // initialize the SQLite store + kvStore: createExpoSQLiteStore, // kvStore: createExpoSQLiteStore, // or if using op-sqlite schema, }), - [], + [userId, authToken], ); if (!z) { @@ -111,8 +113,8 @@ export default function RootLayout() { return ( - - + + ); @@ -122,7 +124,7 @@ export default function RootLayout() { 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'; +import {useQuery, useZero} from '@rocicorp/zero/react'; function IssueList() { const z = useZero(); @@ -140,7 +142,7 @@ function IssueList() { const [issues, issuesDetail] = useQuery(issueQuery); - // Your component ReactJSX + // Your component React Native JSX return ...; } ``` diff --git a/contents/docs/react-expo/index.mdx b/contents/docs/react-expo/index.mdx deleted file mode 100644 index 68aa046..0000000 --- a/contents/docs/react-expo/index.mdx +++ /dev/null @@ -1,87 +0,0 @@ ---- -title: React Expo ---- - -Zero has built-in support for React Expo using the `expo-sqlite` and `op-sqlite` packages. - -In your mobile app's layout, wrap your app's root component in 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() { - // Memoize the Zero instance so it's only created once per app lifecycle - const zero = 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, // initialize the SQLite store - // kvStore: createExpoSQLiteStore, // or if using op-sqlite - schema, - }), - [] - ); - - 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 ReactJSX - return ( - - ... - - ) -} -``` - -Complete quickstart here: COMING SOON \ No newline at end of file