Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SQLite, account creation, and account fetching #16

Merged
merged 6 commits into from
Apr 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

67 changes: 67 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions packages/data-facade/src/facade.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ZodTypeAny, z } from "zod";
import { useQuery, useMutation } from "@tanstack/react-query";
import { useQuery, useMutation, UseMutationOptions } from "@tanstack/react-query";
import { buildQueryKey } from "./utils";
import type {
ResolverFunc,
Expand Down Expand Up @@ -32,13 +32,14 @@ function handlerQueryBuilder<TResolver extends ResolverFunc>(

function buildUseMutation() {
return <TResolver extends ResolverFunc>(resolver: TResolver) => ({
useMutation: () => {
useMutation: (opts?: UseMutationOptions<Awaited<ReturnType<TResolver>>, Error, unknown, unknown>) => {
return useMutation<
Awaited<ReturnType<TResolver>>,
Error,
unknown,
unknown
>({
...opts,
mutationFn: resolver,
});
},
Expand Down
2 changes: 1 addition & 1 deletion packages/data-facade/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export type HandlerQueryBuilderReturn<TResolver extends ResolverFunc> = (
type UseMutationType<
TResolver extends ResolverFunc,
TReturn = Awaited<ReturnType<TResolver>>,
> = (opts?: UseMutationOptions) => UseMutationResult<TReturn>;
> = (opts?: UseMutationOptions<TReturn, Error, unknown, unknown>) => UseMutationResult<TReturn>;

export type HandlerMutationBuilderReturn<TResolver extends ResolverFunc> =
() => {
Expand Down
31 changes: 16 additions & 15 deletions packages/mobile-app/app/(tabs)/transact.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,34 @@
import { View, Text } from "react-native";
import { useFacade } from "../../data";
import { useFacade } from "../../data/facades";
import { Button } from "@ironfish/ui";
import { useState } from "react";
import { useQueryClient } from "@tanstack/react-query";

export default function Transact() {
const [facadeResult, setFacadeResult] = useState([""]);
const facade = useFacade();
const qc = useQueryClient()

const getAccountsResult = facade.getAccounts.useQuery(123);
const getAccountsWithZodResult = facade.getAccountsWithZod.useQuery({
limit: 2,
const getAccountsResult = facade.getAccounts.useQuery();
const createAccount = facade.createAccount.useMutation({
onSuccess: () => {
qc.invalidateQueries({
queryKey: ['getAccounts']
})
}
Comment on lines +11 to +16
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: IIRC, it's possible to set a default for this with React Query. I'd recommend busting the whole cache on mutation. IMO, being overly aggressive with cache busting after a mutation is usually the way to go; especially for stuff like accounts that will be on-device, so the request doesn't even need to go over the wire.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think in the node app, we handle this at the trpc-react level: https://github.com/iron-fish/ironfish-node-app/blob/9712ebf2cd64507b42b8a055ab9efebc9a808f6d/renderer/providers/TRPCProvider.tsx#L14

So I think we'd need to add something to data-facade to allow for this? We can pass defaultOptions to the QueryClient to set onSuccess by default, but it would get clobbered if we wanted to set onSuccess on mutations explicitly.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm going to kick this down the road and add a ticket in Linear for it: https://linear.app/if-labs/issue/IFL-2522/invalidate-react-query-query-cache-on-all-mutations

});
const getAllAccountsResult = facade.getAllAccounts.useQuery();
const createAccount = facade.createAccount.useMutation();

return (
<View>
<Text>Accounts</Text>
<Text>{JSON.stringify(getAccountsResult.data)}</Text>
<Text>{JSON.stringify(getAccountsWithZodResult.data)}</Text>
<Text>{JSON.stringify(getAllAccountsResult.data)}</Text>
<Text>Mutation: {facadeResult}</Text>
{(getAccountsResult.data ?? []).map((account) => (
<Text key={account.id}>{account.name}</Text>
))}
<Button
onClick={async () => {
const otherResult = await createAccount.mutateAsync("dave");
setFacadeResult(otherResult);
const otherResult = await createAccount.mutateAsync({ name: "dave" });
console.log('Created Account:', otherResult)
}}
>
Click me
Create Account
</Button>
</View>
);
Expand Down
44 changes: 35 additions & 9 deletions packages/mobile-app/app/_layout.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,48 @@
import { Stack } from "expo-router";
import { Text } from 'react-native';
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

import { FacadeProvider } from "../data";
import { FacadeProvider, useFacade } from "../data/facades";
import React, { useEffect } from "react";

const queryClient = new QueryClient();

function DatabaseLoader({ loading, children }: { loading: React.ReactNode, children?: React.ReactNode }) {
const facade = useFacade();
const [status, setStatus] = React.useState<string>("loading");
const loadDatabases = facade.loadDatabases.useMutation();

useEffect(() => {
const fn = async () => {
const result = await loadDatabases.mutateAsync(undefined);
setStatus(result)
}
fn()
}, [])

if (status === "loading") {
return loading;
} else if (status === 'loaded') {
return children;
} else {
throw new Error(`Unknown status ${status}`);
}
}

export default function Layout() {
return (
<QueryClientProvider client={queryClient}>
<FacadeProvider>
<Stack>
<Stack.Screen
name="(tabs)"
options={{
headerShown: false,
}}
/>
</Stack>
<DatabaseLoader loading={<Text>Loading databases...</Text>}>
<Stack>
<Stack.Screen
name="(tabs)"
options={{
headerShown: false,
}}
/>
</Stack>
</DatabaseLoader>
</FacadeProvider>
</QueryClientProvider>
);
Expand Down
50 changes: 0 additions & 50 deletions packages/mobile-app/data/accounts/handlers.ts

This file was deleted.

9 changes: 0 additions & 9 deletions packages/mobile-app/data/accounts/types.ts

This file was deleted.

5 changes: 5 additions & 0 deletions packages/mobile-app/data/facades/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# facades

Facades use the `data-facade` package to provide a `tanstack/react-query` interface between the wallet code in the `data` folder and the Expo frontend in the `app` folder.

The facade routes are intended to be similar to the [Iron Fish RPC routes](https://github.com/iron-fish/ironfish/tree/master/ironfish/src/rpc/routes). Our goal is that in the future, we could implement a facade that connects directly to an Iron Fish node with minimal changes to the frontend code.
38 changes: 38 additions & 0 deletions packages/mobile-app/data/facades/accounts/demoHandlers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { f } from "data-facade";
import { z } from "zod";
import { AccountsMethods } from "./types";

let ACCOUNTS = [
{ id: 0, name: "alice", viewOnlyAccount: "alice" },
{ id: 1, name: "bob", viewOnlyAccount: "bob"},
{ id: 2, name: "carol", viewOnlyAccount: "carol"}
];

async function getAccounts(limit: number) {
await new Promise((resolve) => setTimeout(resolve, 1000));
return ACCOUNTS.slice(0, limit);
}

export const accountsHandlers = f.facade<AccountsMethods>({
getAccounts: f.handler.query(async () => {
const accounts = await getAccounts(ACCOUNTS.length);
console.log("getAccounts", accounts);
return accounts;
}),
createAccount: f.handler
.input(
z.object({
name: z.string(),
}),
)
.mutation(async ({ name }) => {
const existingId = ACCOUNTS.at(-1)?.id
if (existingId === undefined) {
throw new Error("No accounts found");
}
const account = { id: existingId + 1, name, viewOnlyAccount: name }
console.log("createAccount", account);
ACCOUNTS.push(account);
return account;
}),
});
Loading
Loading