feat: add auth to zero queries

This commit is contained in:
Max Koon
2025-10-13 23:13:16 -04:00
parent 92d297e2c9
commit fb6b7ff683
11 changed files with 95 additions and 27 deletions

2
.gitignore vendored
View File

@@ -31,7 +31,7 @@ yarn-error.*
*.pem
# local env files
.env*.local
.env
# typescript
*.tsbuildinfo

View File

@@ -38,7 +38,8 @@
"backgroundColor": "#000000"
}
}
]
],
"expo-sqlite"
],
"experiments": {
"typedRoutes": true,

View File

@@ -3,23 +3,50 @@ import 'react-native-reanimated';
import { authClient } from '@/lib/auth-client';
import { ZeroProvider } from '@rocicorp/zero/react';
import { zero } from '@/lib/zero';
import { useMemo } from 'react';
import { authDataSchema } from '@/shared/src/auth';
import { Platform } from 'react-native';
import type { ZeroOptions } from '@rocicorp/zero';
import { schema, type Schema } from '@/shared/src';
import { expoSQLiteStoreProvider } from "@rocicorp/zero/react-native";
export const unstable_settings = {
anchor: '(tabs)',
anchor: 'index',
};
const kvStore = Platform.OS === "web" ? undefined : expoSQLiteStoreProvider();
export default function RootLayout() {
const { data, isPending } = authClient.useSession();
const { data: session, isPending } = authClient.useSession();
const authData = useMemo(() => {
const result = authDataSchema.safeParse(session);
return result.success ? result.data : null;
}, [session]);
const cookie = useMemo(() => {
return Platform.OS == 'web' ? undefined : authClient.getCookie();
}, [session, isPending]);
const zeroProps = useMemo(() => {
return {
storageKey: 'money',
kvStore,
server: 'http://localhost:4848',
userID: authData?.user.id ?? "anon",
schema,
// mutators: createMutators(),
auth: cookie,
} as const satisfies ZeroOptions<Schema>;
}, [authData, cookie]);
return (
<ZeroProvider zero={zero}>
<ZeroProvider {...zeroProps}>
<Stack>
<Stack.Protected guard={!isPending && !!data}>
<Stack.Protected guard={!isPending && !!session}>
<Stack.Screen name="index" />
</Stack.Protected>
<Stack.Protected guard={!isPending && !data}>
<Stack.Protected guard={!isPending && !session}>
<Stack.Screen name="auth" />
</Stack.Protected>
</Stack>

View File

@@ -5,15 +5,15 @@ import { useQuery, useZero } from "@rocicorp/zero/react";
import { queries } from '@money/shared';
export default function HomeScreen() {
const { data } = authClient.useSession();
const { data: session } = authClient.useSession();
const onLogout = () => {
authClient.signOut();
}
const [transactions] = useQuery(queries.allTransactions());
const [transactions] = useQuery(queries.allTransactions(session));
return (
<SafeAreaView>
<Text>Hello {data?.user.name}</Text>
<Text>Hello {session?.user.name}</Text>
<Button onPress={onLogout} title="Logout" />
<Text>Transactions: {JSON.stringify(transactions, null, 4)}</Text>
</SafeAreaView>

View File

@@ -1,8 +0,0 @@
import {Zero} from '@rocicorp/zero';
import { schema } from "@money/shared";
export const zero = new Zero({
userID: 'anon',
server: 'http://localhost:4848',
schema,
});

View File

@@ -2,6 +2,9 @@ const { getDefaultConfig } = require("expo/metro-config");
const config = getDefaultConfig(__dirname)
// Add wasm asset support
config.resolver.assetExts.push("wasm");
config.resolver.unstable_enablePackageExports = true;
module.exports = config;

View File

@@ -27,6 +27,7 @@
"expo-linking": "~8.0.8",
"expo-router": "~6.0.11",
"expo-splash-screen": "~31.0.10",
"expo-sqlite": "~16.0.8",
"expo-status-bar": "~3.0.8",
"expo-symbols": "~1.0.7",
"expo-system-ui": "~6.0.7",

22
pnpm-lock.yaml generated
View File

@@ -56,6 +56,9 @@ importers:
expo-splash-screen:
specifier: ~31.0.10
version: 31.0.10(expo@54.0.13)
expo-sqlite:
specifier: ~16.0.8
version: 16.0.8(expo@54.0.13)(react-native@0.81.4(@babel/core@7.28.4)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
expo-status-bar:
specifier: ~3.0.8
version: 3.0.8(react-native@0.81.4(@babel/core@7.28.4)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
@@ -2574,6 +2577,9 @@ packages:
avvio@9.1.0:
resolution: {integrity: sha512-fYASnYi600CsH/j9EQov7lECAniYiBFiiAtBNuZYLA2leLe9qOvZzqYHFjtIj6gD2VMoMLP14834LFWvr4IfDw==}
await-lock@2.2.2:
resolution: {integrity: sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw==}
babel-jest@29.7.0:
resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -3424,6 +3430,13 @@ packages:
peerDependencies:
expo: '*'
expo-sqlite@16.0.8:
resolution: {integrity: sha512-xw776gFgH4ZM5oGs0spSLNmkHO/kJ/EuRXGzE4/22yII9EmG84vm7aM/M2aEb8taBTqwhSGYUpkwkRT5YFFmsg==}
peerDependencies:
expo: '*'
react: '*'
react-native: '*'
expo-status-bar@3.0.8:
resolution: {integrity: sha512-L248XKPhum7tvREoS1VfE0H6dPCaGtoUWzRsUv7hGKdiB4cus33Rc0sxkWkoQ77wE8stlnUlL5lvmT0oqZ3ZBw==}
peerDependencies:
@@ -9041,6 +9054,8 @@ snapshots:
'@fastify/error': 4.2.0
fastq: 1.19.1
await-lock@2.2.2: {}
babel-jest@29.7.0(@babel/core@7.28.4):
dependencies:
'@babel/core': 7.28.4
@@ -10124,6 +10139,13 @@ snapshots:
transitivePeerDependencies:
- supports-color
expo-sqlite@16.0.8(expo@54.0.13)(react-native@0.81.4(@babel/core@7.28.4)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0):
dependencies:
await-lock: 2.2.2
expo: 54.0.13(@babel/core@7.28.4)(@expo/metro-runtime@6.1.2)(expo-router@6.0.11)(react-native@0.81.4(@babel/core@7.28.4)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
react: 19.1.0
react-native: 0.81.4(@babel/core@7.28.4)(@types/react@19.1.17)(react@19.1.0)
expo-status-bar@3.0.8(react-native@0.81.4(@babel/core@7.28.4)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0):
dependencies:
react: 19.1.0

View File

@@ -1,9 +1,15 @@
import { syncedQuery } from "@rocicorp/zero";
import { syncedQueryWithContext } from "@rocicorp/zero";
import { z } from "zod";
import { builder } from "@money/shared";
import type { AuthData } from "./auth";
import { isLoggedIn } from "./zql";
export const queries = {
allTransactions: syncedQuery('allTransactions', z.tuple([]), () =>
builder.transaction.limit(10)
allTransactions: syncedQueryWithContext('allTransactions', z.tuple([]), (authData: AuthData | null) => {
isLoggedIn(authData);
return builder.transaction
.where('user_id', '=', authData.user.id)
.limit(10)
}
),
};

View File

@@ -1,4 +1,4 @@
import { createSchema, table, string, number, createBuilder, definePermissions } from "@rocicorp/zero";
import { type Schema as ZeroSchema, createSchema, table, string, number, createBuilder, definePermissions } from "@rocicorp/zero";
const transaction = table('transaction')
.columns({
@@ -7,13 +7,20 @@ const transaction = table('transaction')
name: string(),
amount: number(),
})
.primaryKey('id');
.primaryKey('id').schema;
export const schema = createSchema({
tables: [transaction],
export const schema = {
tables: { transaction },
relationships: {},
enableLegacyMutators: false,
enableLegacyQueries: false,
});
} satisfies ZeroSchema;
// export const schema = createSchema({
// tables: [transaction],
// enableLegacyMutators: false,
// enableLegacyQueries: false,
// });
export const builder = createBuilder(schema);

9
shared/src/zql.ts Normal file
View File

@@ -0,0 +1,9 @@
import type { AuthData } from "./auth";
export function isLoggedIn(
authData: AuthData | null,
): asserts authData is AuthData {
if (!authData?.user.id) {
throw new Error("User is not logged in");
}
}