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

Feature/add-vue-query-package #452

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,5 @@
"pnpm": "8",
"node": ">=14.0.0"
},
"packageManager": "pnpm@8"
"packageManager": "pnpm@8.15.6"
}
12 changes: 12 additions & 0 deletions packages/postgrest-vue-query/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"root": true,
"parser": "vue-eslint-parser",
"parserOptions": {
"parser": "@typescript-eslint/parser"
},
"extends": [
"@supabase-cache-helpers/custom",
"plugin:vue/vue3-recommended",
"@vue/typescript/recommended"
]
}
3 changes: 3 additions & 0 deletions packages/postgrest-vue-query/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# @supabase-cache-helpers/postgrest-vue-query

## 0.0.1
29 changes: 29 additions & 0 deletions packages/postgrest-vue-query/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# PostgREST React Query

A collection of React Query utilities for working with Supabase.

<a href="https://github.com/psteinroe/supabase-cache-helpers/actions/workflows/ci.yml"><img src="https://github.com/psteinroe/supabase-cache-helpers/actions/workflows/ci.yml/badge.svg?branch=main" alt="Latest build" target="\_parent"></a>
<a href="https://github.com/psteinroe/supabase-cache-helpers"><img src="https://img.shields.io/github/stars/psteinroe/supabase-cache-helpers.svg?style=social&amp;label=Star" alt="GitHub Stars" target="\_parent"></a>
[![codecov](https://codecov.io/gh/psteinroe/supabase-cache-helpers/branch/main/graph/badge.svg?token=SPMWSVBRGX)](https://codecov.io/gh/psteinroe/supabase-cache-helpers)

## Introduction

The cache helpers bridge the gap between popular frontend cache management solutions such as [SWR](https://swr.vercel.app) or [React Query](https://tanstack.com/query/latest), and the Supabase client libraries. All features of [`postgrest-js`](https://github.com/supabase/postgrest-js), [`storage-js`](https://github.com/supabase/storage-js) and [`realtime-js`](https://github.com/supabase/realtime-js) are supported. The cache helpers parse any query into a unique and definite query key, and automatically populates your query cache with every mutation using implicit knowledge of the schema. Check out the [demo](https://supabase-cache-helpers-react-query.vercel.app/) and find out how it feels like for your users.

## Features

With just one single line of code, you can simplify the logic of **fetching, subscribing to updates, and mutating data as well as storage objects** in your project, and have all the amazing features of [SWR](https://swr.vercel.app) or [React Query](https://tanstack.com/query/latest) out-of-the-box.

- **Seamless** integration with [SWR](https://swr.vercel.app) and [React Query](https://tanstack.com/query/latest)
- **Automatic** cache key generation
- Easy **Pagination** and **Infinite Scroll** queries
- **Insert**, **update**, **upsert** and **delete** mutations
- **Auto-populate** cache after mutations and subscriptions
- **Auto-expand** mutation queries based on existing cache data to keep app up-to-date
- One-liner to upload, download and remove **Supabase Storage** objects

And a lot [more](https://supabase-cache-helpers.vercel.app).

---

**View full documentation and examples on [supabase-cache-helpers.vercel.app](https://supabase-cache-helpers.vercel.app).**
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { createClient, SupabaseClient } from '@supabase/supabase-js';
import { QueryClient } from '@tanstack/vue-query';
import { fireEvent, screen } from '@testing-library/vue';

import type { Database } from '../database.types';
import { renderWithConfig } from '../utils';
import Page from '../components/MutateItemPage.vue';

const TEST_PREFIX = 'postgrest-vue-query-mutate-item';

describe('useMutateItem', () => {
let client: SupabaseClient<Database>;
let testRunPrefix: string;

let contacts: Database['public']['Tables']['contact']['Row'][];

beforeAll(async () => {
testRunPrefix = `${TEST_PREFIX}-${Math.floor(Math.random() * 100)}`;
client = createClient(
process.env.SUPABASE_URL as string,
process.env.SUPABASE_ANON_KEY as string,
);
});

beforeEach(async () => {
await client.from('contact').delete().ilike('username', `${TEST_PREFIX}%`);

const { data } = await client
.from('contact')
.insert(
new Array<number>(3)
.fill(0)
.map((_, idx) => ({ username: `${testRunPrefix}-${idx}` })),
)
.select('*');
contacts = data as Database['public']['Tables']['contact']['Row'][];
});

it('should mutate existing item in cache', async () => {
const queryClient = new QueryClient();

renderWithConfig(Page, { client, testRunPrefix }, queryClient);
await screen.findByText(
`count: ${contacts.length}`,
{},
{ timeout: 10000 },
);
fireEvent.click(screen.getByTestId('mutate'));
await screen.findByText(
`${testRunPrefix}-0-updated`,
{},
{ timeout: 10000 },
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<template>
<div>
<div data-testid="delete" @click="executeDeleteContact" />
<div
data-testid="deleteWithEmptyOptions"
@click="executeDeleteWithEmptyOptions"
/>
<div
data-testid="deleteWithoutOptions"
@click="executeDeleteWithoutOptions"
/>
<span v-for="d in data" :key="d.id">{{ d.username }}</span>
<span data-testid="count">{{ `count: ${count}` }}</span>
<span data-testid="success">{{ `success: ${success}` }}</span>
</div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { useQuery, useDeleteManyMutation } from '../../src';
import { SupabaseClient } from '@supabase/supabase-js';
import type { Database } from '../database.types';

interface Props {
client: SupabaseClient;
contacts: Database['public']['Tables']['contact']['Row'][];
}
const props = defineProps<Props>();

const success = ref<boolean>(false);

const { data, count } = useQuery(
props.client
.from('contact')
.select('id,username', { count: 'exact' })
.eq('username', props.contacts[0].username ?? ''),
);
const { mutateAsync: deleteContact } = useDeleteManyMutation(
props.client.from('contact'),
['id'],
null,
{
onSuccess: () => (success.value = true),
},
);
const { mutateAsync: deleteWithEmptyOptions } = useDeleteManyMutation(
props.client.from('contact'),
['id'],
null,
{},
);
const { mutateAsync: deleteWithoutOptions } = useDeleteManyMutation(
props.client.from('contact'),
['id'],
);

const executeDeleteContact = () =>
deleteContact([
{
id: (data.value ?? []).find((c) => c)?.id,
},
]);

const executeDeleteWithEmptyOptions = () =>
deleteWithEmptyOptions([
{
id: (data.value ?? []).find((c) => c)?.id,
},
]);

const executeDeleteWithoutOptions = () =>
deleteWithoutOptions([
{
id: (data.value ?? []).find((c) => c)?.id,
},
]);
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<template>
<div>
{{ addressBookAndContact?.name }}
<span data-testid="count">
count: {{ addressBookAndContact?.contacts.length }}
</span>
<div
v-for="contact in addressBookAndContact?.contacts"
:key="contact.id"
data-testid="contact"
>
{{ contact.username }}
<button
@click="
deleteContactFromAddressBook({
contact: contact.id,
address_book: addressBookAndContact?.id,
})
"
>
Delete Contact
</button>
</div>
</div>
</template>

<script setup lang="ts">
import { useQuery, useDeleteMutation } from '../../src';
import { SupabaseClient } from '@supabase/supabase-js';

interface Props {
client: SupabaseClient;
addressBookId: string;
}
const props = defineProps<Props>();

const { data: addressBookAndContact } = useQuery(
props.client
.from('address_book')
.select('id, name, contacts:contact (id, username)')
.eq('id', props.addressBookId)
.single(),
);

const { mutateAsync: deleteContactFromAddressBook } = useDeleteMutation(
props.client.from('address_book_contact'),
['contact', 'address_book'],
'contact, address_book',
{
revalidateRelations: [
{
relation: 'address_book',
relationIdColumn: 'id',
fKeyColumn: 'address_book',
},
],
},
);
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<template>
<div>
<div data-testid="delete" @click="executeDeleteContact" />
<div
data-testid="deleteWithEmptyOptions"
@click="executeDeleteWithEmptyOptions"
/>
<div
data-testid="deleteWithoutOptions"
@click="executeDeleteWithoutOptions"
/>
<span v-for="d in data" :key="d.id">{{ d.username }}</span>
<span data-testid="count">{{ `count: ${count}` }}</span>
<span data-testid="success">{{ `success: ${success}` }}</span>
</div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import type { Database } from '../database.types';
import { useQuery, useDeleteMutation } from '../../src';
import { SupabaseClient } from '@supabase/supabase-js';

interface Props {
client: SupabaseClient;
contacts: Database['public']['Tables']['contact']['Row'][];
}
const props = defineProps<Props>();

const success = ref<boolean>(false);
const { data, count } = useQuery(
props.client
.from('contact')
.select('id,username', { count: 'exact' })
.eq('username', props.contacts[0].username ?? ''),
);
const { mutateAsync: deleteContact } = useDeleteMutation(
props.client.from('contact'),
['id'],
null,
{ onSuccess: () => (success.value = true) },
);
const { mutateAsync: deleteWithEmptyOptions } = useDeleteMutation(
props.client.from('contact'),
['id'],
null,
{},
);
const { mutateAsync: deleteWithoutOptions } = useDeleteMutation(
props.client.from('contact'),
['id'],
);

const executeDeleteContact = () =>
deleteContact({
id: (data.value ?? []).find((c) => c)?.id,
});

const executeDeleteWithEmptyOptions = () =>
deleteWithEmptyOptions({
id: (data.value ?? []).find((c) => c)?.id,
});
const executeDeleteWithoutOptions = () =>
deleteWithoutOptions({
id: (data.value ?? []).find((c) => c)?.id,
});
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<template>
<div>
<div data-testid="insertMany" @click="executeInsert" />
<span v-for="d in data" :key="d.id">{{ d.alias }}</span>
<span data-testid="count">{{ `count: ${count}` }}</span>
<span data-testid="success">{{ `success: ${success}` }}</span>
</div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { useQuery, useInsertMutation } from '../../src';
import { SupabaseClient } from '@supabase/supabase-js';

interface Props {
client: SupabaseClient;
userName1: string;
userName2: string;
userName3: string;
}
const props = defineProps<Props>();

const success = ref<boolean>(false);

const { data, count } = useQuery(
props.client
.from('contact')
.select('id,alias:username', { count: 'exact' })
.in('username', [props.userName1, props.userName2, props.userName3]),
);
const { mutateAsync: insert } = useInsertMutation(
props.client.from('contact'),
['id'],
null,
{
onSuccess: () => (success.value = true),
},
);

const executeInsert = () =>
insert([
{
username: props.userName2,
},
{
username: props.userName3,
},
]);
</script>
Loading