diff --git a/package.json b/package.json index b6766938..b91a6a06 100644 --- a/package.json +++ b/package.json @@ -38,5 +38,5 @@ "pnpm": "8", "node": ">=14.0.0" }, - "packageManager": "pnpm@8" + "packageManager": "pnpm@8.15.6" } diff --git a/packages/postgrest-vue-query/.eslintrc.json b/packages/postgrest-vue-query/.eslintrc.json new file mode 100644 index 00000000..de95d41d --- /dev/null +++ b/packages/postgrest-vue-query/.eslintrc.json @@ -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" + ] +} diff --git a/packages/postgrest-vue-query/CHANGELOG.md b/packages/postgrest-vue-query/CHANGELOG.md new file mode 100644 index 00000000..82b10ad3 --- /dev/null +++ b/packages/postgrest-vue-query/CHANGELOG.md @@ -0,0 +1,3 @@ +# @supabase-cache-helpers/postgrest-vue-query + +## 0.0.1 diff --git a/packages/postgrest-vue-query/README.md b/packages/postgrest-vue-query/README.md new file mode 100644 index 00000000..800b3e94 --- /dev/null +++ b/packages/postgrest-vue-query/README.md @@ -0,0 +1,29 @@ +# PostgREST React Query + +A collection of React Query utilities for working with Supabase. + +Latest build +GitHub Stars +[![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).** diff --git a/packages/postgrest-vue-query/__tests__/cache/use-mutate-item.spec.ts b/packages/postgrest-vue-query/__tests__/cache/use-mutate-item.spec.ts new file mode 100644 index 00000000..6aa547a9 --- /dev/null +++ b/packages/postgrest-vue-query/__tests__/cache/use-mutate-item.spec.ts @@ -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; + 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(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 }, + ); + }); +}); diff --git a/packages/postgrest-vue-query/__tests__/components/DeleteManyMutationPage.vue b/packages/postgrest-vue-query/__tests__/components/DeleteManyMutationPage.vue new file mode 100644 index 00000000..a6003f40 --- /dev/null +++ b/packages/postgrest-vue-query/__tests__/components/DeleteManyMutationPage.vue @@ -0,0 +1,77 @@ + + + diff --git a/packages/postgrest-vue-query/__tests__/components/DeleteMutationPage-1.vue b/packages/postgrest-vue-query/__tests__/components/DeleteMutationPage-1.vue new file mode 100644 index 00000000..a3a1969d --- /dev/null +++ b/packages/postgrest-vue-query/__tests__/components/DeleteMutationPage-1.vue @@ -0,0 +1,59 @@ + + + diff --git a/packages/postgrest-vue-query/__tests__/components/DeleteMutationPage-2.vue b/packages/postgrest-vue-query/__tests__/components/DeleteMutationPage-2.vue new file mode 100644 index 00000000..342a7b9f --- /dev/null +++ b/packages/postgrest-vue-query/__tests__/components/DeleteMutationPage-2.vue @@ -0,0 +1,67 @@ + + + diff --git a/packages/postgrest-vue-query/__tests__/components/InsertMutationPage.vue b/packages/postgrest-vue-query/__tests__/components/InsertMutationPage.vue new file mode 100644 index 00000000..89758df6 --- /dev/null +++ b/packages/postgrest-vue-query/__tests__/components/InsertMutationPage.vue @@ -0,0 +1,49 @@ + + + diff --git a/packages/postgrest-vue-query/__tests__/components/MutateItemPage.vue b/packages/postgrest-vue-query/__tests__/components/MutateItemPage.vue new file mode 100644 index 00000000..c74db30c --- /dev/null +++ b/packages/postgrest-vue-query/__tests__/components/MutateItemPage.vue @@ -0,0 +1,40 @@ + + + diff --git a/packages/postgrest-vue-query/__tests__/components/QueryPage-1.vue b/packages/postgrest-vue-query/__tests__/components/QueryPage-1.vue new file mode 100644 index 00000000..c4ef23da --- /dev/null +++ b/packages/postgrest-vue-query/__tests__/components/QueryPage-1.vue @@ -0,0 +1,20 @@ + + + diff --git a/packages/postgrest-vue-query/__tests__/components/QueryPage-2.vue b/packages/postgrest-vue-query/__tests__/components/QueryPage-2.vue new file mode 100644 index 00000000..15a24e43 --- /dev/null +++ b/packages/postgrest-vue-query/__tests__/components/QueryPage-2.vue @@ -0,0 +1,20 @@ + + + diff --git a/packages/postgrest-vue-query/__tests__/components/QueryPage-3.vue b/packages/postgrest-vue-query/__tests__/components/QueryPage-3.vue new file mode 100644 index 00000000..698e7e2c --- /dev/null +++ b/packages/postgrest-vue-query/__tests__/components/QueryPage-3.vue @@ -0,0 +1,26 @@ + + + diff --git a/packages/postgrest-vue-query/__tests__/components/QueryPage-4.vue b/packages/postgrest-vue-query/__tests__/components/QueryPage-4.vue new file mode 100644 index 00000000..751de292 --- /dev/null +++ b/packages/postgrest-vue-query/__tests__/components/QueryPage-4.vue @@ -0,0 +1,30 @@ + + + diff --git a/packages/postgrest-vue-query/__tests__/components/QueryPage-5.vue b/packages/postgrest-vue-query/__tests__/components/QueryPage-5.vue new file mode 100644 index 00000000..d092b2c5 --- /dev/null +++ b/packages/postgrest-vue-query/__tests__/components/QueryPage-5.vue @@ -0,0 +1,33 @@ + + + diff --git a/packages/postgrest-vue-query/__tests__/components/QueryPage-6.vue b/packages/postgrest-vue-query/__tests__/components/QueryPage-6.vue new file mode 100644 index 00000000..5ab0a6e4 --- /dev/null +++ b/packages/postgrest-vue-query/__tests__/components/QueryPage-6.vue @@ -0,0 +1,29 @@ + + + diff --git a/packages/postgrest-vue-query/__tests__/components/QueryPage-7.vue b/packages/postgrest-vue-query/__tests__/components/QueryPage-7.vue new file mode 100644 index 00000000..b81a357d --- /dev/null +++ b/packages/postgrest-vue-query/__tests__/components/QueryPage-7.vue @@ -0,0 +1,36 @@ + + + diff --git a/packages/postgrest-vue-query/__tests__/components/SubscriptionPage.vue b/packages/postgrest-vue-query/__tests__/components/SubscriptionPage.vue new file mode 100644 index 00000000..cfa7bf19 --- /dev/null +++ b/packages/postgrest-vue-query/__tests__/components/SubscriptionPage.vue @@ -0,0 +1,51 @@ + + + diff --git a/packages/postgrest-vue-query/__tests__/components/SubscriptionQueryPage.vue b/packages/postgrest-vue-query/__tests__/components/SubscriptionQueryPage.vue new file mode 100644 index 00000000..2c5cfc92 --- /dev/null +++ b/packages/postgrest-vue-query/__tests__/components/SubscriptionQueryPage.vue @@ -0,0 +1,55 @@ + + + diff --git a/packages/postgrest-vue-query/__tests__/components/UpdateMutationPage.vue b/packages/postgrest-vue-query/__tests__/components/UpdateMutationPage.vue new file mode 100644 index 00000000..88843d63 --- /dev/null +++ b/packages/postgrest-vue-query/__tests__/components/UpdateMutationPage.vue @@ -0,0 +1,59 @@ + + + diff --git a/packages/postgrest-vue-query/__tests__/components/UpsertMutationPage.vue b/packages/postgrest-vue-query/__tests__/components/UpsertMutationPage.vue new file mode 100644 index 00000000..05499e83 --- /dev/null +++ b/packages/postgrest-vue-query/__tests__/components/UpsertMutationPage.vue @@ -0,0 +1,57 @@ + + + diff --git a/packages/postgrest-vue-query/__tests__/database.types.ts b/packages/postgrest-vue-query/__tests__/database.types.ts new file mode 100644 index 00000000..a1905a1b --- /dev/null +++ b/packages/postgrest-vue-query/__tests__/database.types.ts @@ -0,0 +1,418 @@ +export type Json = + | string + | number + | boolean + | null + | { [key: string]: Json | undefined } + | Json[]; + +export interface Database { + public: { + Tables: { + address_book: { + Row: { + id: string; + created_at: string; + name: string | null; + }; + Insert: { + name?: string | null; + }; + Update: { + name?: string | null; + }; + Relationships: []; + }; + address_book_contact: { + Row: { + address_book: string; + contact: string; + }; + Insert: { + address_book: string; + contact: string; + }; + Update: { + address_book: string; + contact: string; + }; + Relationships: [ + { + foreignKeyName: 'address_book_contact_address_book_fkey'; + columns: ['address_book']; + referencedRelation: 'address_book'; + referencedColumns: ['id']; + }, + { + foreignKeyName: 'address_book_contact_contact_fkey'; + columns: ['contact']; + referencedRelation: 'contact'; + referencedColumns: ['id']; + }, + ]; + }; + contact: { + Row: { + age_range: unknown | null; + catchphrase: unknown | null; + continent: string | null; + country: string | null; + created_at: string; + golden_ticket: boolean | null; + id: string; + metadata: Json | null; + tags: string[] | null; + ticket_number: number | null; + username: string | null; + has_low_ticket_number: unknown | null; + }; + Insert: { + age_range?: unknown | null; + catchphrase?: unknown | null; + continent?: string | null; + country?: string | null; + created_at?: string; + golden_ticket?: boolean | null; + id?: string; + metadata?: Json | null; + tags?: string[] | null; + ticket_number?: number | null; + username?: string | null; + }; + Update: { + age_range?: unknown | null; + catchphrase?: unknown | null; + continent?: string | null; + country?: string | null; + created_at?: string; + golden_ticket?: boolean | null; + id?: string; + metadata?: Json | null; + tags?: string[] | null; + ticket_number?: number | null; + username?: string | null; + }; + Relationships: [ + { + foreignKeyName: 'contact_continent_fkey'; + columns: ['continent']; + referencedRelation: 'continent'; + referencedColumns: ['code']; + }, + { + foreignKeyName: 'contact_country_fkey'; + columns: ['country']; + referencedRelation: 'country'; + referencedColumns: ['code']; + }, + ]; + }; + contact_note: { + Row: { + contact_id: string; + created_at: string; + id: string; + text: string; + }; + Insert: { + contact_id: string; + created_at?: string; + id?: string; + text: string; + }; + Update: { + contact_id?: string; + created_at?: string; + id?: string; + text?: string; + }; + Relationships: [ + { + foreignKeyName: 'contact_note_contact_id_fkey'; + columns: ['contact_id']; + referencedRelation: 'contact'; + referencedColumns: ['id']; + }, + ]; + }; + continent: { + Row: { + code: string; + name: string | null; + }; + Insert: { + code: string; + name?: string | null; + }; + Update: { + code?: string; + name?: string | null; + }; + Relationships: []; + }; + country: { + Row: { + code: string; + continent_code: string; + full_name: string; + iso3: string; + name: string; + number: string; + }; + Insert: { + code: string; + continent_code: string; + full_name: string; + iso3: string; + name: string; + number: string; + }; + Update: { + code?: string; + continent_code?: string; + full_name?: string; + iso3?: string; + name?: string; + number?: string; + }; + Relationships: [ + { + foreignKeyName: 'country_continent_code_fkey'; + columns: ['continent_code']; + referencedRelation: 'continent'; + referencedColumns: ['code']; + }, + ]; + }; + multi_pk: { + Row: { + id_1: number; + id_2: number; + name: string | null; + }; + Insert: { + id_1: number; + id_2: number; + name?: string | null; + }; + Update: { + id_1?: number; + id_2?: number; + name?: string | null; + }; + Relationships: []; + }; + serial_key_table: { + Row: { + id: number; + value: string | null; + }; + Insert: { + id?: number; + value?: string | null; + }; + Update: { + id?: number; + value?: string | null; + }; + Relationships: []; + }; + }; + Views: { + [_ in never]: never; + }; + Functions: { + has_low_ticket_number: { + Args: { + '': unknown; + }; + Returns: boolean; + }; + }; + Enums: { + [_ in never]: never; + }; + CompositeTypes: { + [_ in never]: never; + }; + }; + storage: { + Tables: { + buckets: { + Row: { + allowed_mime_types: string[] | null; + avif_autodetection: boolean | null; + created_at: string | null; + file_size_limit: number | null; + id: string; + name: string; + owner: string | null; + public: boolean | null; + updated_at: string | null; + }; + Insert: { + allowed_mime_types?: string[] | null; + avif_autodetection?: boolean | null; + created_at?: string | null; + file_size_limit?: number | null; + id: string; + name: string; + owner?: string | null; + public?: boolean | null; + updated_at?: string | null; + }; + Update: { + allowed_mime_types?: string[] | null; + avif_autodetection?: boolean | null; + created_at?: string | null; + file_size_limit?: number | null; + id?: string; + name?: string; + owner?: string | null; + public?: boolean | null; + updated_at?: string | null; + }; + Relationships: [ + { + foreignKeyName: 'buckets_owner_fkey'; + columns: ['owner']; + referencedRelation: 'users'; + referencedColumns: ['id']; + }, + ]; + }; + migrations: { + Row: { + executed_at: string | null; + hash: string; + id: number; + name: string; + }; + Insert: { + executed_at?: string | null; + hash: string; + id: number; + name: string; + }; + Update: { + executed_at?: string | null; + hash?: string; + id?: number; + name?: string; + }; + Relationships: []; + }; + objects: { + Row: { + bucket_id: string | null; + created_at: string | null; + id: string; + last_accessed_at: string | null; + metadata: Json | null; + name: string | null; + owner: string | null; + path_tokens: string[] | null; + updated_at: string | null; + version: string | null; + }; + Insert: { + bucket_id?: string | null; + created_at?: string | null; + id?: string; + last_accessed_at?: string | null; + metadata?: Json | null; + name?: string | null; + owner?: string | null; + path_tokens?: string[] | null; + updated_at?: string | null; + version?: string | null; + }; + Update: { + bucket_id?: string | null; + created_at?: string | null; + id?: string; + last_accessed_at?: string | null; + metadata?: Json | null; + name?: string | null; + owner?: string | null; + path_tokens?: string[] | null; + updated_at?: string | null; + version?: string | null; + }; + Relationships: [ + { + foreignKeyName: 'objects_bucketId_fkey'; + columns: ['bucket_id']; + referencedRelation: 'buckets'; + referencedColumns: ['id']; + }, + ]; + }; + }; + Views: { + [_ in never]: never; + }; + Functions: { + can_insert_object: { + Args: { + bucketid: string; + name: string; + owner: string; + metadata: Json; + }; + Returns: undefined; + }; + extension: { + Args: { + name: string; + }; + Returns: string; + }; + filename: { + Args: { + name: string; + }; + Returns: string; + }; + foldername: { + Args: { + name: string; + }; + Returns: unknown; + }; + get_size_by_bucket: { + Args: Record; + Returns: { + size: number; + bucket_id: string; + }[]; + }; + search: { + Args: { + prefix: string; + bucketname: string; + limits?: number; + levels?: number; + offsets?: number; + search?: string; + sortcolumn?: string; + sortorder?: string; + }; + Returns: { + name: string; + id: string; + updated_at: string; + created_at: string; + last_accessed_at: string; + metadata: Json; + }[]; + }; + }; + Enums: { + [_ in never]: never; + }; + CompositeTypes: { + [_ in never]: never; + }; + }; +} diff --git a/packages/postgrest-vue-query/__tests__/mutate/use-delete-many-mutation.integration.spec.ts b/packages/postgrest-vue-query/__tests__/mutate/use-delete-many-mutation.integration.spec.ts new file mode 100644 index 00000000..03b6b1ac --- /dev/null +++ b/packages/postgrest-vue-query/__tests__/mutate/use-delete-many-mutation.integration.spec.ts @@ -0,0 +1,68 @@ +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/DeleteManyMutationPage.vue'; + +const TEST_PREFIX = 'postgrest-vue-query-delmany'; + +describe('useDeleteManyMutation', () => { + let client: SupabaseClient; + 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(3) + .fill(0) + .map((idx) => ({ username: `${testRunPrefix}-${idx}` })), + ) + .select('*'); + contacts = data as Database['public']['Tables']['contact']['Row'][]; + }); + + it('should delete existing cache item and reduce count', async () => { + const queryClient = new QueryClient(); + + renderWithConfig(Page, { client, contacts }, queryClient); + await screen.findByText( + `count: ${contacts.length}`, + {}, + { timeout: 10000 }, + ); + fireEvent.click(screen.getByTestId('deleteWithEmptyOptions')); + await screen.findByText( + `count: ${contacts.length - 1}`, + {}, + { timeout: 10000 }, + ); + fireEvent.click(screen.getByTestId('deleteWithoutOptions')); + await screen.findByText( + `count: ${contacts.length - 2}`, + {}, + { timeout: 10000 }, + ); + fireEvent.click(screen.getByTestId('delete')); + await screen.findByText('success: true', {}, { timeout: 10000 }); + await screen.findByText( + `count: ${contacts.length - 3}`, + {}, + { timeout: 10000 }, + ); + }); +}); diff --git a/packages/postgrest-vue-query/__tests__/mutate/use-delete-mutation.integration.spec.ts b/packages/postgrest-vue-query/__tests__/mutate/use-delete-mutation.integration.spec.ts new file mode 100644 index 00000000..487b4f83 --- /dev/null +++ b/packages/postgrest-vue-query/__tests__/mutate/use-delete-mutation.integration.spec.ts @@ -0,0 +1,113 @@ +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 DeleteMutationPage1 from '../components/DeleteMutationPage-1.vue'; +import DeleteMutationPage2 from '../components/DeleteMutationPage-2.vue'; + +const TEST_PREFIX = 'postgrest-vue-query-delete'; + +describe('useDeleteMutation', () => { + let client: SupabaseClient; + 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(3) + .fill(0) + .map((idx) => ({ username: `${testRunPrefix}-${idx}` })), + ) + .select('*'); + contacts = data as Database['public']['Tables']['contact']['Row'][]; + }); + + it('should invalidate address_book cache after delete', async () => { + const { data: addressBooks } = await client + .from('address_book') + .insert([ + { + name: 'hello', + }, + ]) + .select('id'); + + const addressBookId = addressBooks ? addressBooks[0].id : ''; + + await client.from('address_book_contact').insert([ + { + address_book: addressBookId, + contact: contacts[0].id, + }, + { + address_book: addressBookId, + contact: contacts[1].id, + }, + ]); + + const queryClient = new QueryClient(); + + renderWithConfig( + DeleteMutationPage1, + { client, addressBookId }, + queryClient, + ); + + await screen.findByText(`hello`, {}, { timeout: 10000 }); + + await screen.findByText(`count: 2`, {}, { timeout: 10000 }); + + const deleteButtons = screen.getAllByRole(`button`, { + name: /Delete Contact/i, + }); + + fireEvent.click(deleteButtons[0]); + + await screen.findByText(`count: 1`, {}, { timeout: 10000 }); + }); + + it('should delete existing cache item and reduce count', async () => { + const queryClient = new QueryClient(); + + renderWithConfig(DeleteMutationPage2, {}, queryClient); + await screen.findByText( + `count: ${contacts.length}`, + {}, + { timeout: 10000 }, + ); + fireEvent.click(screen.getByTestId('deleteWithEmptyOptions')); + await screen.findByText( + `count: ${contacts.length - 1}`, + {}, + { timeout: 10000 }, + ); + fireEvent.click(screen.getByTestId('deleteWithoutOptions')); + await screen.findByText( + `count: ${contacts.length - 2}`, + {}, + { timeout: 10000 }, + ); + fireEvent.click(screen.getByTestId('delete')); + await screen.findByText('success: true', {}, { timeout: 10000 }); + await screen.findByText( + `count: ${contacts.length - 3}`, + {}, + { timeout: 10000 }, + ); + }); +}); diff --git a/packages/postgrest-vue-query/__tests__/mutate/use-insert-mutation.integration.spec.ts b/packages/postgrest-vue-query/__tests__/mutate/use-insert-mutation.integration.spec.ts new file mode 100644 index 00000000..2ca986b3 --- /dev/null +++ b/packages/postgrest-vue-query/__tests__/mutate/use-insert-mutation.integration.spec.ts @@ -0,0 +1,47 @@ +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/InsertMutationPage.vue'; + +const TEST_PREFIX = 'postgrest-vue-query-insert'; + +describe('useInsertMutation', () => { + let client: SupabaseClient; + let testRunPrefix: string; + + 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, + ); + await client.from('contact').delete().ilike('username', `${TEST_PREFIX}%`); + }); + + it('should insert into existing cache item with alias', async () => { + const queryClient = new QueryClient(); + const USERNAME_1 = `${testRunPrefix}-1`; + const USERNAME_2 = `${testRunPrefix}-2`; + const USERNAME_3 = `${testRunPrefix}-3`; + + renderWithConfig( + Page, + { + client, + userName1: USERNAME_1, + userName2: USERNAME_2, + userName3: USERNAME_3, + }, + queryClient, + ); + await screen.findByText('count: 0', {}, { timeout: 10000 }); + fireEvent.click(screen.getByTestId('insertMany')); + await screen.findByText(USERNAME_2, {}, { timeout: 10000 }); + await screen.findByText(USERNAME_3, {}, { timeout: 10000 }); + expect(screen.getByTestId('count').textContent).toEqual('count: 2'); + await screen.findByText('success: true', {}, { timeout: 10000 }); + }); +}); diff --git a/packages/postgrest-vue-query/__tests__/mutate/use-update-mutation.integration.spec.ts b/packages/postgrest-vue-query/__tests__/mutate/use-update-mutation.integration.spec.ts new file mode 100644 index 00000000..fdf7101f --- /dev/null +++ b/packages/postgrest-vue-query/__tests__/mutate/use-update-mutation.integration.spec.ts @@ -0,0 +1,43 @@ +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/UpdateMutationPage.vue'; + +const TEST_PREFIX = 'postgrest-react-query-update'; + +describe('useUpdateMutation', () => { + let client: SupabaseClient; + let testRunPrefix: string; + + 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, + ); + await client.from('contact').delete().ilike('username', `${TEST_PREFIX}%`); + }); + + it('should update existing cache item', async () => { + const queryClient = new QueryClient(); + const USERNAME_1 = `${testRunPrefix}-2`; + const USERNAME_2 = `${testRunPrefix}-3`; + + renderWithConfig( + Page, + { client, username1: USERNAME_1, username2: USERNAME_2 }, + queryClient, + ); + await screen.findByText('count: 0', {}, { timeout: 10000 }); + fireEvent.click(screen.getByTestId('insert')); + await screen.findByText(USERNAME_1, {}, { timeout: 10000 }); + expect(screen.getByTestId('count').textContent).toEqual('count: 1'); + fireEvent.click(screen.getByTestId('update')); + await screen.findByText(USERNAME_2, {}, { timeout: 10000 }); + expect(screen.getByTestId('count').textContent).toEqual('count: 1'); + await screen.findByText('success: true', {}, { timeout: 10000 }); + }); +}); diff --git a/packages/postgrest-vue-query/__tests__/mutate/use-upsert-mutation.integration.spec.ts b/packages/postgrest-vue-query/__tests__/mutate/use-upsert-mutation.integration.spec.ts new file mode 100644 index 00000000..5252c915 --- /dev/null +++ b/packages/postgrest-vue-query/__tests__/mutate/use-upsert-mutation.integration.spec.ts @@ -0,0 +1,48 @@ +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/UpsertMutationPage.vue'; + +const TEST_PREFIX = 'postgrest-react-query-upsert'; + +describe('useUpsertMutation', () => { + let client: SupabaseClient; + let testRunPrefix: string; + + 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, + ); + await client.from('contact').delete().ilike('username', `${TEST_PREFIX}%`); + }); + + it('should upsert into existing cache item', async () => { + const queryClient = new QueryClient(); + const USERNAME_1 = `${testRunPrefix}-2`; + const USERNAME_2 = `${testRunPrefix}-3`; + + await client + .from('contact') + .insert({ + username: USERNAME_1, + golden_ticket: true, + }) + .throwOnError(); + renderWithConfig( + Page, + { client, username1: USERNAME_1, username2: USERNAME_2 }, + queryClient, + ); + await screen.findByText('count: 1', {}, { timeout: 10000 }); + fireEvent.click(screen.getByTestId('upsertMany')); + await screen.findByText(`${USERNAME_1} - true`, {}, { timeout: 10000 }); + await screen.findByText(`${USERNAME_2} - null`, {}, { timeout: 10000 }); + expect(screen.getByTestId('count').textContent).toEqual('count: 2'); + await screen.findByText('success: true', {}, { timeout: 10000 }); + }); +}); diff --git a/packages/postgrest-vue-query/__tests__/query/fetch.spec.ts b/packages/postgrest-vue-query/__tests__/query/fetch.spec.ts new file mode 100644 index 00000000..240b3442 --- /dev/null +++ b/packages/postgrest-vue-query/__tests__/query/fetch.spec.ts @@ -0,0 +1,45 @@ +import { createClient, SupabaseClient } from '@supabase/supabase-js'; +import { QueryClient } from '@tanstack/vue-query'; + +import { fetchQuery } from '../../src'; +import type { Database } from '../database.types'; +import '../utils'; + +const TEST_PREFIX = 'postgrest-vue-query-fetch'; + +describe('fetchQuery', () => { + let client: SupabaseClient; + 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, + ); + await client.from('contact').delete().ilike('username', `${TEST_PREFIX}%`); + + const { data } = await client + .from('contact') + .insert([ + { username: `${testRunPrefix}-username-1` }, + { username: `${testRunPrefix}-username-2` }, + { username: `${testRunPrefix}-username-3` }, + { username: `${testRunPrefix}-username-4` }, + ]) + .select('*') + .throwOnError(); + contacts = data ?? []; + expect(contacts).toHaveLength(4); + }); + + it('fetchQuery should work', async () => { + const queryClient = new QueryClient(); + const { data } = await fetchQuery( + queryClient, + client.from('contact').select('*').ilike('username', `${testRunPrefix}%`), + ); + expect(data).toEqual(contacts); + }); +}); diff --git a/packages/postgrest-vue-query/__tests__/query/prefetch.integration.spec.ts b/packages/postgrest-vue-query/__tests__/query/prefetch.integration.spec.ts new file mode 100644 index 00000000..41a65a3a --- /dev/null +++ b/packages/postgrest-vue-query/__tests__/query/prefetch.integration.spec.ts @@ -0,0 +1,53 @@ +import { createClient, SupabaseClient } from '@supabase/supabase-js'; +import { QueryClient } from '@tanstack/vue-query'; + +import { fetchQueryInitialData, prefetchQuery } from '../../src'; +import type { Database } from '../database.types'; +import '../utils'; + +const TEST_PREFIX = 'postgrest-vue-query-prefetch'; + +describe('prefetch', () => { + let client: SupabaseClient; + 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, + ); + await client.from('contact').delete().ilike('username', `${TEST_PREFIX}%`); + + const { data } = await client + .from('contact') + .insert([ + { username: `${testRunPrefix}-username-1` }, + { username: `${testRunPrefix}-username-2` }, + { username: `${testRunPrefix}-username-3` }, + { username: `${testRunPrefix}-username-4` }, + ]) + .select('*') + .throwOnError(); + contacts = data ?? []; + expect(contacts).toHaveLength(4); + }); + + it('prefetchQuery should throw if query is not a PostgrestBuilder', async () => { + const queryClient = new QueryClient(); + try { + await prefetchQuery(queryClient, Promise.resolve({} as any)); + } catch (error) { + expect(error).toEqual(new Error('Key is not a PostgrestBuilder')); + } + }); + + it('fetchQueryInitialData should throw if query is not a PostgrestBuilder', async () => { + try { + await fetchQueryInitialData(Promise.resolve({} as any)); + } catch (error) { + expect(error).toEqual(new Error('Query is not a PostgrestBuilder')); + } + }); +}); diff --git a/packages/postgrest-vue-query/__tests__/query/use-query.integration.spec.ts b/packages/postgrest-vue-query/__tests__/query/use-query.integration.spec.ts new file mode 100644 index 00000000..8e1b0561 --- /dev/null +++ b/packages/postgrest-vue-query/__tests__/query/use-query.integration.spec.ts @@ -0,0 +1,152 @@ +import { createClient, SupabaseClient } from '@supabase/supabase-js'; +import { QueryClient } from '@tanstack/vue-query'; +import { fireEvent, screen } from '@testing-library/vue'; + +import { fetchQueryInitialData, prefetchQuery } from '../../src'; +import { encode } from '../../src/lib/key'; +import type { Database } from '../database.types'; +import { renderWithConfig } from '../utils'; +import QueryPage1 from '../components/QueryPage-1.vue'; +import QueryPage2 from '../components/QueryPage-2.vue'; +import QueryPage3 from '../components/QueryPage-3.vue'; +import QueryPage4 from '../components/QueryPage-4.vue'; +import QueryPage5 from '../components/QueryPage-5.vue'; +import QueryPage6 from '../components/QueryPage-6.vue'; +import QueryPage7 from '../components/QueryPage-7.vue'; + +const TEST_PREFIX = 'postgrest-vue-query-query'; + +describe('useQuery', () => { + let client: SupabaseClient; + 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, + ); + await client.from('contact').delete().ilike('username', `${TEST_PREFIX}%`); + + const { data } = await client + .from('contact') + .insert([ + { username: `${testRunPrefix}-username-1` }, + { username: `${testRunPrefix}-username-2` }, + { username: `${testRunPrefix}-username-3` }, + { username: `${testRunPrefix}-username-4` }, + ]) + .select('*') + .throwOnError(); + contacts = data ?? []; + expect(contacts).toHaveLength(4); + }); + + it('should work for single', async () => { + const queryClient = new QueryClient(); + const query = client + .from('contact') + .select('id,username') + .eq('username', contacts[0].username ?? '') + .single(); + + renderWithConfig(QueryPage1, { client, query }, queryClient); + await screen.findByText( + contacts[0].username as string, + {}, + { timeout: 10000 }, + ); + expect(queryClient.getQueryData(encode(query, false))).toBeDefined(); + }); + + it('should work for maybeSingle', async () => { + const queryClient = new QueryClient(); + const query = client + .from('contact') + .select('id,username') + .eq('username', 'unknown') + .maybeSingle(); + + renderWithConfig(QueryPage2, { client, query }, queryClient); + await screen.findByText('username: undefined', {}, { timeout: 10000 }); + expect(queryClient.getQueryData(encode(query, false))).toBeDefined(); + }); + + it('should work with multiple', async () => { + const queryClient = new QueryClient(); + const query = client + .from('contact') + .select('id,username', { count: 'exact' }) + .ilike('username', `${testRunPrefix}%`); + + renderWithConfig(QueryPage3, { client, query, contacts }, queryClient); + await screen.findByText( + contacts[0].username as string, + {}, + { timeout: 10000 }, + ); + expect(screen.getByTestId('count').textContent).toEqual('4'); + expect(queryClient.getQueryData(encode(query, false))).toBeDefined(); + }); + + it('should work for with conditional query', async () => { + const queryClient = new QueryClient(); + + renderWithConfig(QueryPage4, { client, contacts }, queryClient); + await screen.findByText('undefined', {}, { timeout: 10000 }); + fireEvent.click(screen.getByTestId('setCondition')); + await screen.findByText( + contacts[0].username as string, + {}, + { timeout: 10000 }, + ); + }); + + it('refetch should work', async () => { + const queryClient = new QueryClient(); + + renderWithConfig(QueryPage5, { client, contacts }, queryClient); + await screen.findByText('isLoading: false', {}, { timeout: 10000 }); + fireEvent.click(screen.getByTestId('mutate')); + await screen.findByText('refetched: true', {}, { timeout: 10000 }); + }); + + it('prefetch should work', async () => { + const queryClient = new QueryClient(); + const query = client + .from('contact') + .select('id,username') + .eq('username', contacts[0].username ?? '') + .single(); + await prefetchQuery(queryClient, query); + + const wrapper = renderWithConfig( + QueryPage6, + { client, query }, + queryClient, + ); + const updateEvent = wrapper.emitted('update'); + expect(updateEvent).toHaveLength(0); + await screen.findByText(contacts[0].username!, {}, { timeout: 10000 }); + }); + + it('initalData should work', async () => { + const queryClient = new QueryClient(); + const query = client + .from('contact') + .select('id,username') + .eq('username', contacts[0].username ?? '') + .single(); + const [_, initial] = await fetchQueryInitialData(query); + + const wrapper = renderWithConfig( + QueryPage7, + { client, query, initial }, + queryClient, + ); + const updateEvent = wrapper.emitted('update'); + expect(updateEvent).toHaveLength(0); + await screen.findByText(contacts[0].username!, {}, { timeout: 10000 }); + }); +}); diff --git a/packages/postgrest-vue-query/__tests__/subscribe/use-subscription-query.integration.spec.ts b/packages/postgrest-vue-query/__tests__/subscribe/use-subscription-query.integration.spec.ts new file mode 100644 index 00000000..829234d2 --- /dev/null +++ b/packages/postgrest-vue-query/__tests__/subscribe/use-subscription-query.integration.spec.ts @@ -0,0 +1,70 @@ +import { createClient, SupabaseClient } from '@supabase/supabase-js'; +import { QueryClient } from '@tanstack/vue-query'; +import { screen } from '@testing-library/vue'; + +import type { Database } from '../database.types'; +import { renderWithConfig } from '../utils'; +import Page from '../components/SubscriptionQueryPage.vue'; + +const TEST_PREFIX = 'postgrest-vue-query-subscription-query'; + +describe('useSubscriptionQuery', () => { + let client: SupabaseClient; + let testRunPrefix: string; + + 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, + ); + await client.from('contact').delete().ilike('username', `${TEST_PREFIX}%`); + }); + + afterEach(async () => { + if (client) await client.removeAllChannels(); + }); + + it('should properly update cache', async () => { + const queryClient = new QueryClient(); + const USERNAME_1 = `${testRunPrefix}-1`; + + const { unmount } = renderWithConfig( + Page, + { client, username: USERNAME_1 }, + queryClient, + ); + await screen.findByText('count: 0', {}, { timeout: 10000 }); + await screen.findByText('SUBSCRIBED', {}, { timeout: 10000 }); + await new Promise((resolve) => setTimeout(resolve, 2000)); + + await client + .from('contact') + .insert({ username: USERNAME_1, ticket_number: 1 }) + .select('*') + .throwOnError() + .single(); + + await screen.findByText( + 'ticket_number: 1 | has_low_ticket_number: true', + {}, + { timeout: 10000 }, + ); + expect(screen.getByTestId('count').textContent).toEqual('count: 1'); + + await client + .from('contact') + .update({ ticket_number: 1000 }) + .eq('username', USERNAME_1) + .throwOnError(); + + await screen.findByText( + 'ticket_number: 1000 | has_low_ticket_number: false', + {}, + { timeout: 10000 }, + ); + expect(screen.getByTestId('count').textContent).toEqual('count: 1'); + await screen.findByText('cbCalled: true', {}, { timeout: 10000 }); + unmount(); + }); +}); diff --git a/packages/postgrest-vue-query/__tests__/subscribe/use-subscription.integration.spec.ts b/packages/postgrest-vue-query/__tests__/subscribe/use-subscription.integration.spec.ts new file mode 100644 index 00000000..0c576eb0 --- /dev/null +++ b/packages/postgrest-vue-query/__tests__/subscribe/use-subscription.integration.spec.ts @@ -0,0 +1,61 @@ +import { createClient, SupabaseClient } from '@supabase/supabase-js'; +import { QueryClient } from '@tanstack/vue-query'; +import { screen } from '@testing-library/vue'; + +import type { Database } from '../database.types'; +import { renderWithConfig } from '../utils'; +import Page from '../components/SubscriptionPage.vue'; + +const TEST_PREFIX = 'postgrest-vue-query-subscription-plain'; + +describe('useSubscription', () => { + let client: SupabaseClient; + let testRunPrefix: string; + + 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, + ); + await client.from('contact').delete().ilike('username', `${TEST_PREFIX}%`); + }); + + afterEach(async () => { + if (client) await client.removeAllChannels(); + }); + + it('should properly update cache', async () => { + const queryClient = new QueryClient(); + const USERNAME_1 = `${testRunPrefix}-1`; + + const { unmount } = renderWithConfig( + Page, + { client, username: USERNAME_1 }, + queryClient, + ); + await screen.findByText('count: 0', {}, { timeout: 10000 }); + await screen.findByText('SUBSCRIBED', {}, { timeout: 10000 }); + + await client + .from('contact') + .insert({ username: USERNAME_1, ticket_number: 1 }) + .select('id') + .throwOnError() + .single(); + + await screen.findByText('ticket_number: 1', {}, { timeout: 10000 }); + expect(screen.getByTestId('count').textContent).toEqual('count: 1'); + + await client + .from('contact') + .update({ ticket_number: 5 }) + .eq('username', USERNAME_1) + .throwOnError(); + + await screen.findByText('ticket_number: 5', {}, { timeout: 10000 }); + expect(screen.getByTestId('count').textContent).toEqual('count: 1'); + await screen.findByText('cbCalled: true', {}, { timeout: 10000 }); + unmount(); + }); +}); diff --git a/packages/postgrest-vue-query/__tests__/utils.ts b/packages/postgrest-vue-query/__tests__/utils.ts new file mode 100644 index 00000000..230ed2e5 --- /dev/null +++ b/packages/postgrest-vue-query/__tests__/utils.ts @@ -0,0 +1,20 @@ +import { QueryClient, VueQueryPlugin } from '@tanstack/vue-query'; +import { render } from '@testing-library/vue'; +import * as dotenv from 'dotenv'; +import { resolve } from 'node:path'; + +dotenv.config({ path: resolve(__dirname, '../../../.env.local') }); + +export const renderWithConfig = ( + element: any, + props?: { [key: string]: unknown }, + queryClient?: QueryClient, +): ReturnType => { + const client = queryClient ?? new QueryClient(); + return render(element, { + props, + global: { + plugins: [[VueQueryPlugin, { queryClient: client }]], + }, + }); +}; diff --git a/packages/postgrest-vue-query/package.json b/packages/postgrest-vue-query/package.json new file mode 100644 index 00000000..06999a14 --- /dev/null +++ b/packages/postgrest-vue-query/package.json @@ -0,0 +1,83 @@ +{ + "name": "@supabase-cache-helpers/postgrest-vue-query", + "version": "0.0.1", + "author": "Christian Pannwitz ", + "homepage": "https://supabase-cache-helpers.vercel.app", + "bugs": { + "url": "https://github.com/psteinroe/supabase-cache-helpers/issues" + }, + "main": "./dist/index.js", + "source": "./src/index.ts", + "types": "./dist/index.d.ts", + "files": [ + "dist/**" + ], + "publishConfig": { + "access": "public" + }, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.mjs", + "require": "./dist/index.js" + }, + "./package.json": "./package.json" + }, + "license": "MIT", + "scripts": { + "build": "tsup", + "test": "jest --coverage --runInBand", + "clean": "rm -rf .turbo && rm -rf lint-results && rm -rf .nyc_output && rm -rf node_modules && rm -rf dist", + "lint": "eslint src/**", + "lint:report": "eslint {src/**,__tests__/**} --format json --output-file ./lint-results/postgrest-vue-query.json", + "lint:fix": "eslint {src/**,__tests__/**} --fix", + "typecheck": "tsc --pretty --noEmit", + "format:write": "prettier --write \"{src/**/*.{ts,tsx,md},__tests__/**/*.{ts,tsx,md}}\"", + "format:check": "prettier --check \"{src/**/*.{ts,tsx,md},__tests__/**/*.{ts,tsx,md}}\"" + }, + "keywords": [ + "Supabase", + "PostgREST", + "Cache", + "Tanstack Query", + "Vue Query" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/psteinroe/supabase-cache-helpers.git", + "directory": "packages/postgrest-vue-query" + }, + "peerDependencies": { + "@supabase/postgrest-js": "^1.15.4", + "@tanstack/vue-query": "^5.38.0", + "vue": "^3.4.27" + }, + "jest": { + "preset": "@supabase-cache-helpers/jest-presets/jest/node" + }, + "devDependencies": { + "@supabase-cache-helpers/eslint-config-custom": "workspace:*", + "@supabase-cache-helpers/jest-presets": "workspace:*", + "@supabase-cache-helpers/prettier-config": "workspace:*", + "@supabase-cache-helpers/tsconfig": "workspace:*", + "@supabase/postgrest-js": "1.15.4", + "@supabase/supabase-js": "2.43.4", + "@testing-library/jest-dom": "6.4.5", + "@testing-library/vue": "8.1.0", + "@types/flat": "5.0.5", + "@types/jest": "29.5.12", + "dotenv": "16.4.5", + "eslint": "8.54.0", + "eslint-plugin-vue": "^9.26.0", + "jest": "29.7.0", + "jest-environment-jsdom": "29.7.0", + "ts-jest": "29.1.3", + "tsup": "8.0.2", + "typescript": "5.4.5", + "vue": "3.4.27" + }, + "dependencies": { + "@supabase-cache-helpers/postgrest-core": "workspace:*", + "flat": "5.0.2" + } +} diff --git a/packages/postgrest-vue-query/prettier.config.cjs b/packages/postgrest-vue-query/prettier.config.cjs new file mode 100644 index 00000000..3fb75475 --- /dev/null +++ b/packages/postgrest-vue-query/prettier.config.cjs @@ -0,0 +1 @@ +module.exports = require("@supabase-cache-helpers/prettier-config/prettier.config.js"); diff --git a/packages/postgrest-vue-query/src/cache/index.ts b/packages/postgrest-vue-query/src/cache/index.ts new file mode 100644 index 00000000..c2ef6a83 --- /dev/null +++ b/packages/postgrest-vue-query/src/cache/index.ts @@ -0,0 +1,3 @@ +export * from './use-delete-item'; +export * from './use-mutate-item'; +export * from './use-upsert-item'; diff --git a/packages/postgrest-vue-query/src/cache/use-delete-item.ts b/packages/postgrest-vue-query/src/cache/use-delete-item.ts new file mode 100644 index 00000000..e41bf0c5 --- /dev/null +++ b/packages/postgrest-vue-query/src/cache/use-delete-item.ts @@ -0,0 +1,40 @@ +import { + deleteItem, + DeleteItemOperation, +} from '@supabase-cache-helpers/postgrest-core'; +import { useQueryClient } from '@tanstack/vue-query'; +import flatten from 'flat'; + +import { decode, usePostgrestFilterCache } from '../lib'; + +/** + * Convenience hook to delete an item from the vue query cache. Does not make any http requests, and is supposed to be used for custom cache updates. + * @param opts The mutation options + * @returns void + */ +export function useDeleteItem>( + opts: Omit, 'input'>, +) { + const queryClient = useQueryClient(); + const getPostgrestFilter = usePostgrestFilterCache(); + + return async (input: Type) => + await deleteItem( + { + input: flatten(input) as Type, + ...opts, + }, + { + cacheKeys: queryClient + .getQueryCache() + .getAll() + .map((c) => c.queryKey), + getPostgrestFilter, + revalidate: (key) => queryClient.invalidateQueries({ queryKey: key }), + mutate: (key, fn) => { + queryClient.setQueriesData({ queryKey: key }, fn); + }, + decode, + }, + ); +} diff --git a/packages/postgrest-vue-query/src/cache/use-mutate-item.ts b/packages/postgrest-vue-query/src/cache/use-mutate-item.ts new file mode 100644 index 00000000..619405e5 --- /dev/null +++ b/packages/postgrest-vue-query/src/cache/use-mutate-item.ts @@ -0,0 +1,41 @@ +import { + mutateItem, + MutateItemOperation, +} from '@supabase-cache-helpers/postgrest-core'; +import { useQueryClient } from '@tanstack/vue-query'; +import flatten from 'flat'; + +import { decode, usePostgrestFilterCache } from '../lib'; + +/** + * Convenience hook to mutate an item within the vue query cache. Does not make any http requests, and is supposed to be used for custom cache updates. + * @param opts The mutation options + * @returns void + */ +export function useMutateItem>( + opts: Omit, 'input' | 'mutate'>, +): (input: Partial, mutateFn: (current: Type) => Type) => Promise { + const queryClient = useQueryClient(); + const getPostgrestFilter = usePostgrestFilterCache(); + + return async (input: Partial, mutateFn: (current: Type) => Type) => + await mutateItem( + { + input: flatten(input) as Partial, + mutate: mutateFn, + ...opts, + }, + { + cacheKeys: queryClient + .getQueryCache() + .getAll() + .map((c) => c.queryKey), + getPostgrestFilter, + revalidate: (key) => queryClient.invalidateQueries({ queryKey: key }), + mutate: (key, fn) => { + queryClient.setQueriesData({ queryKey: key }, fn); + }, + decode, + }, + ); +} diff --git a/packages/postgrest-vue-query/src/cache/use-upsert-item.ts b/packages/postgrest-vue-query/src/cache/use-upsert-item.ts new file mode 100644 index 00000000..9027e88f --- /dev/null +++ b/packages/postgrest-vue-query/src/cache/use-upsert-item.ts @@ -0,0 +1,40 @@ +import { + upsertItem, + UpsertItemOperation, +} from '@supabase-cache-helpers/postgrest-core'; +import { useQueryClient } from '@tanstack/vue-query'; +import flatten from 'flat'; + +import { decode, usePostgrestFilterCache } from '../lib'; + +/** + * Convenience hook to upsert an item into the vue query cache. Does not make any http requests, and is supposed to be used for custom cache updates. + * @param opts The mutation options + * @returns void + */ +export function useUpsertItem>( + opts: Omit, 'input'>, +) { + const queryClient = useQueryClient(); + const getPostgrestFilter = usePostgrestFilterCache(); + + return async (input: Type) => + await upsertItem( + { + input: flatten(input) as Type, + ...opts, + }, + { + cacheKeys: queryClient + .getQueryCache() + .getAll() + .map((c) => c.queryKey), + getPostgrestFilter, + revalidate: (key) => queryClient.invalidateQueries({ queryKey: key }), + mutate: (key, fn) => { + queryClient.setQueriesData({ queryKey: key }, fn); + }, + decode, + }, + ); +} diff --git a/packages/postgrest-vue-query/src/index.ts b/packages/postgrest-vue-query/src/index.ts new file mode 100644 index 00000000..7937708f --- /dev/null +++ b/packages/postgrest-vue-query/src/index.ts @@ -0,0 +1,10 @@ +export type { + PostgrestHasMorePaginationCacheData, + PostgrestPaginationCacheData, +} from '@supabase-cache-helpers/postgrest-core'; + +export * from './cache'; +export * from './lib'; +export * from './mutate'; +export * from './query'; +export * from './subscribe'; diff --git a/packages/postgrest-vue-query/src/lib/index.ts b/packages/postgrest-vue-query/src/lib/index.ts new file mode 100644 index 00000000..fef0d0c0 --- /dev/null +++ b/packages/postgrest-vue-query/src/lib/index.ts @@ -0,0 +1,3 @@ +export * from './use-postgrest-filter-cache'; +export * from './key'; +export * from './use-queries-for-table-loader'; diff --git a/packages/postgrest-vue-query/src/lib/key.ts b/packages/postgrest-vue-query/src/lib/key.ts new file mode 100644 index 00000000..aa7795bf --- /dev/null +++ b/packages/postgrest-vue-query/src/lib/key.ts @@ -0,0 +1,71 @@ +import { + PostgrestParser, + DecodedKey, + isPostgrestBuilder, +} from '@supabase-cache-helpers/postgrest-core'; + +export const KEY_PREFIX = 'postgrest'; +export const INFINITE_KEY_PREFIX = 'page'; + +export type DecodedVueQueryKey = DecodedKey & { + isInfinite: boolean; + key: string[]; +}; + +export const encode = (key: unknown, isInfinite: boolean): string[] => { + if (!isPostgrestBuilder(key)) { + throw new Error('Key is not a PostgrestBuilder'); + } + + const parser = new PostgrestParser(key); + return [ + KEY_PREFIX, + isInfinite ? INFINITE_KEY_PREFIX : 'null', + parser.schema, + parser.table, + parser.queryKey, + parser.bodyKey ?? 'null', + `count=${parser.count}`, + `head=${parser.isHead}`, + parser.orderByKey, + ]; +}; + +export const decode = (key: unknown): DecodedVueQueryKey | null => { + if (!Array.isArray(key)) return null; + + const [ + prefix, + infinitePrefix, + schema, + table, + queryKey, + bodyKey, + count, + head, + orderByKey, + ] = key; + + // Exit early if not a postgrest key + if (prefix !== KEY_PREFIX) return null; + + const params = new URLSearchParams(queryKey); + const limit = params.get('limit'); + const offset = params.get('offset'); + + const countValue = count.replace('count=', ''); + + return { + limit: limit ? Number(limit) : undefined, + offset: offset ? Number(offset) : undefined, + bodyKey, + count: countValue === 'null' ? null : countValue, + isHead: head === 'head=true', + isInfinite: infinitePrefix === INFINITE_KEY_PREFIX, + key, + queryKey, + schema, + table, + orderByKey, + }; +}; diff --git a/packages/postgrest-vue-query/src/lib/use-postgrest-filter-cache.ts b/packages/postgrest-vue-query/src/lib/use-postgrest-filter-cache.ts new file mode 100644 index 00000000..9551c173 --- /dev/null +++ b/packages/postgrest-vue-query/src/lib/use-postgrest-filter-cache.ts @@ -0,0 +1,29 @@ +import { + encodeObject, + PostgrestFilter, + PostgrestQueryParserOptions, +} from '@supabase-cache-helpers/postgrest-core'; +import { useQueryClient } from '@tanstack/vue-query'; + +export const POSTGREST_FILTER_KEY_PREFIX = 'postgrest-filter'; + +export const usePostgrestFilterCache = < + R extends Record, +>() => { + const queryClient = useQueryClient(); + + return (query: string, opts?: PostgrestQueryParserOptions) => { + const key = [ + POSTGREST_FILTER_KEY_PREFIX, + query, + opts ? encodeObject(opts) : null, + ]; + const cacheData = queryClient.getQueryData(key); + if (cacheData instanceof PostgrestFilter) { + return cacheData; + } + const filter = PostgrestFilter.fromQuery(query, opts); + queryClient.setQueryData(key, filter); + return filter as PostgrestFilter; + }; +}; diff --git a/packages/postgrest-vue-query/src/lib/use-queries-for-table-loader.ts b/packages/postgrest-vue-query/src/lib/use-queries-for-table-loader.ts new file mode 100644 index 00000000..aa4792ca --- /dev/null +++ b/packages/postgrest-vue-query/src/lib/use-queries-for-table-loader.ts @@ -0,0 +1,26 @@ +import { BuildNormalizedQueryOps } from '@supabase-cache-helpers/postgrest-core'; +import { useQueryClient } from '@tanstack/vue-query'; + +import { decode } from './key'; +import { usePostgrestFilterCache } from './use-postgrest-filter-cache'; + +export const useQueriesForTableLoader = (table: string) => { + const queryClient = useQueryClient(); + const getPostgrestFilter = usePostgrestFilterCache(); + + return () => + queryClient + .getQueryCache() + .getAll() + .map((c) => c.queryKey) + .reduce>( + (prev, curr) => { + const decodedKey = decode(curr); + if (decodedKey?.table === table) { + prev.push(getPostgrestFilter(decodedKey.queryKey).params); + } + return prev; + }, + [], + ); +}; diff --git a/packages/postgrest-vue-query/src/mutate/get-user-response.ts b/packages/postgrest-vue-query/src/mutate/get-user-response.ts new file mode 100644 index 00000000..509d909d --- /dev/null +++ b/packages/postgrest-vue-query/src/mutate/get-user-response.ts @@ -0,0 +1,14 @@ +import { MutationFetcherResponse } from '@supabase-cache-helpers/postgrest-core'; + +type Truthy = T extends false | '' | 0 | null | undefined ? never : T; // from lodash + +export function truthy(value: T): value is Truthy { + return !!value; +} + +export const getUserResponse = ( + d: MutationFetcherResponse[] | null | undefined, +) => { + if (!d) return d; + return d.map((r) => r.userQueryData).filter(truthy); +}; diff --git a/packages/postgrest-vue-query/src/mutate/index.ts b/packages/postgrest-vue-query/src/mutate/index.ts new file mode 100644 index 00000000..9f7c605c --- /dev/null +++ b/packages/postgrest-vue-query/src/mutate/index.ts @@ -0,0 +1,5 @@ +export * from './use-delete-many-mutation'; +export * from './use-delete-mutation'; +export * from './use-insert-mutation'; +export * from './use-update-mutation'; +export * from './use-upsert-mutation'; diff --git a/packages/postgrest-vue-query/src/mutate/types.ts b/packages/postgrest-vue-query/src/mutate/types.ts new file mode 100644 index 00000000..74b3aae1 --- /dev/null +++ b/packages/postgrest-vue-query/src/mutate/types.ts @@ -0,0 +1,91 @@ +import { GetResult } from '@supabase/postgrest-js/dist/module/select-query-parser'; +import { + GenericSchema, + GenericTable, +} from '@supabase/postgrest-js/dist/module/types'; +import { PostgrestError } from '@supabase/supabase-js'; +import { + DeleteFetcherOptions, + InsertFetcherOptions, + UpdateFetcherOptions, + UpsertFetcherOptions, + RevalidateOpts, +} from '@supabase-cache-helpers/postgrest-core'; +import type { UseMutationOptions } from '@tanstack/vue-query'; + +export type Operation = + | 'Insert' + | 'UpdateOne' + | 'Upsert' + | 'DeleteOne' + | 'DeleteMany'; + +export type GetFetcherOptions< + S extends GenericSchema, + T extends GenericTable, + O extends Operation, +> = O extends 'Insert' + ? InsertFetcherOptions + : O extends 'UpdateOne' + ? UpdateFetcherOptions + : O extends 'Upsert' + ? UpsertFetcherOptions + : O extends 'DeleteOne' | 'DeleteMany' + ? DeleteFetcherOptions + : never; + +export type GetInputType< + T extends GenericTable, + O extends Operation, +> = O extends 'DeleteOne' + ? Partial // TODO: Can we pick the primary keys somehow? + : O extends 'DeleteMany' + ? Partial[] + : O extends 'Insert' | 'Upsert' + ? T['Insert'][] + : O extends 'UpdateOne' + ? T['Update'] + : never; + +export type GetReturnType< + S extends GenericSchema, + T extends GenericTable, + RelationName, + Relationships, + O extends Operation, + Q extends string = '*', + R = GetResult< + S, + T['Row'], + RelationName, + Relationships, + Q extends '*' ? '*' : Q + >, +> = O extends 'UpdateOne' + ? R | null + : O extends 'DeleteOne' + ? R | null + : O extends 'Insert' | 'Upsert' | 'DeleteMany' + ? R[] | null + : never; + +export type UsePostgrestMutationOpts< + S extends GenericSchema, + T extends GenericTable, + RelationName, + Relationships, + O extends Operation, + Q extends string = '*', + R = GetResult< + S, + T['Row'], + RelationName, + Relationships, + Q extends '*' ? '*' : Q + >, +> = RevalidateOpts & + UseMutationOptions< + GetReturnType | null, + PostgrestError, + GetInputType + > & { disableAutoQuery?: boolean } & GetFetcherOptions; diff --git a/packages/postgrest-vue-query/src/mutate/use-delete-many-mutation.ts b/packages/postgrest-vue-query/src/mutate/use-delete-many-mutation.ts new file mode 100644 index 00000000..f16d8bab --- /dev/null +++ b/packages/postgrest-vue-query/src/mutate/use-delete-many-mutation.ts @@ -0,0 +1,73 @@ +import { PostgrestQueryBuilder } from '@supabase/postgrest-js'; +import { GetResult } from '@supabase/postgrest-js/dist/module/select-query-parser'; +import { + GenericSchema, + GenericTable, +} from '@supabase/postgrest-js/dist/module/types'; +import { + buildDeleteFetcher, + getTable, +} from '@supabase-cache-helpers/postgrest-core'; +import { useMutation } from '@tanstack/vue-query'; + +import { UsePostgrestMutationOpts } from './types'; +import { useDeleteItem } from '../cache'; +import { useQueriesForTableLoader } from '../lib'; + +/** + * Hook to execute a DELETE mutation + * + * @param {PostgrestQueryBuilder} qb PostgrestQueryBuilder instance for the table + * @param {Array} primaryKeys Array of primary keys of the table + * @param {string | null} query Optional PostgREST query string for the DELETE mutation + * @param {UsePostgrestMutationOpts} [opts] Options to configure the hook + */ +function useDeleteManyMutation< + S extends GenericSchema, + T extends GenericTable, + RelationName, + Re = T extends { Relationships: infer R } ? R : unknown, + Q extends string = '*', + R = GetResult, +>( + qb: PostgrestQueryBuilder, + primaryKeys: (keyof T['Row'])[], + query?: Q | null, + opts?: UsePostgrestMutationOpts, +) { + const queriesForTable = useQueriesForTableLoader(getTable(qb)); + const deleteItem = useDeleteItem({ + ...opts, + primaryKeys, + table: getTable(qb), + schema: qb.schema as string, + }); + + return useMutation({ + mutationFn: async (input: T['Row'][]) => { + const result = await buildDeleteFetcher( + qb, + primaryKeys, + { + query: query ?? undefined, + queriesForTable, + disabled: opts?.disableAutoQuery, + ...opts, + }, + )(input); + + if (result) { + for (const r of result) { + deleteItem(r.normalizedData as T['Row']); + } + } + + if (!result || result.every((r) => !r.userQueryData)) return null; + + return result.map((r) => r.userQueryData as R); + }, + ...opts, + }); +} + +export { useDeleteManyMutation }; diff --git a/packages/postgrest-vue-query/src/mutate/use-delete-mutation.ts b/packages/postgrest-vue-query/src/mutate/use-delete-mutation.ts new file mode 100644 index 00000000..4ed79219 --- /dev/null +++ b/packages/postgrest-vue-query/src/mutate/use-delete-mutation.ts @@ -0,0 +1,72 @@ +import { PostgrestQueryBuilder } from '@supabase/postgrest-js'; +import { GetResult } from '@supabase/postgrest-js/dist/module/select-query-parser'; +import { + GenericSchema, + GenericTable, +} from '@supabase/postgrest-js/dist/module/types'; +import { + buildDeleteFetcher, + getTable, +} from '@supabase-cache-helpers/postgrest-core'; +import { useMutation } from '@tanstack/vue-query'; + +import { UsePostgrestMutationOpts } from './types'; +import { useDeleteItem } from '../cache'; +import { useQueriesForTableLoader } from '../lib'; + +/** + * Hook to execute a DELETE mutation + * + * @param {PostgrestQueryBuilder} qb PostgrestQueryBuilder instance for the table + * @param {Array} primaryKeys Array of primary keys of the table + * @param {string | null} query Optional PostgREST query string for the DELETE mutation + * @param {UsePostgrestMutationOpts} [opts] Options to configure the hook + */ +function useDeleteMutation< + S extends GenericSchema, + T extends GenericTable, + RelationName, + Re = T extends { Relationships: infer R } ? R : unknown, + Q extends string = '*', + R = GetResult, +>( + qb: PostgrestQueryBuilder, + primaryKeys: (keyof T['Row'])[], + query?: Q | null, + opts?: UsePostgrestMutationOpts, +) { + const queriesForTable = useQueriesForTableLoader(getTable(qb)); + const deleteItem = useDeleteItem({ + ...opts, + primaryKeys, + table: getTable(qb), + schema: qb.schema as string, + }); + + return useMutation({ + mutationFn: async (input: T['Row']) => { + const r = await buildDeleteFetcher( + qb, + primaryKeys, + { + query: query ?? undefined, + queriesForTable, + disabled: opts?.disableAutoQuery, + ...opts, + }, + )([input]); + + if (!r) return null; + + const result = r[0]; + + if (result) { + await deleteItem(result.normalizedData as T['Row']); + } + return result?.userQueryData ?? null; + }, + ...opts, + }); +} + +export { useDeleteMutation }; diff --git a/packages/postgrest-vue-query/src/mutate/use-insert-mutation.ts b/packages/postgrest-vue-query/src/mutate/use-insert-mutation.ts new file mode 100644 index 00000000..cf710cb4 --- /dev/null +++ b/packages/postgrest-vue-query/src/mutate/use-insert-mutation.ts @@ -0,0 +1,72 @@ +import { PostgrestQueryBuilder } from '@supabase/postgrest-js'; +import { GetResult } from '@supabase/postgrest-js/dist/module/select-query-parser'; +import { + GenericSchema, + GenericTable, +} from '@supabase/postgrest-js/dist/module/types'; +import { + buildInsertFetcher, + getTable, +} from '@supabase-cache-helpers/postgrest-core'; +import { useMutation } from '@tanstack/vue-query'; + +import { getUserResponse } from './get-user-response'; +import { UsePostgrestMutationOpts } from './types'; +import { useUpsertItem } from '../cache'; +import { useQueriesForTableLoader } from '../lib'; + +/** + * Hook to execute a INSERT mutation + * + * @param {PostgrestQueryBuilder} qb PostgrestQueryBuilder instance for the table + * @param {Array} primaryKeys Array of primary keys of the table + * @param {string | null} query Optional PostgREST query string for the INSERT mutation + * @param {UsePostgrestMutationOpts} [opts] Options to configure the hook + */ +function useInsertMutation< + S extends GenericSchema, + T extends GenericTable, + RelationName, + Re = T extends { Relationships: infer R } ? R : unknown, + Q extends string = '*', + R = GetResult, +>( + qb: PostgrestQueryBuilder, + primaryKeys: (keyof T['Row'])[], + query?: Q | null, + opts?: UsePostgrestMutationOpts, +) { + const queriesForTable = useQueriesForTableLoader(getTable(qb)); + const upsertItem = useUpsertItem({ + ...opts, + primaryKeys, + table: getTable(qb), + schema: qb.schema as string, + }); + + return useMutation({ + mutationFn: async (input: T['Insert'][]) => { + const result = await buildInsertFetcher( + qb, + { + query: query ?? undefined, + queriesForTable, + disabled: opts?.disableAutoQuery, + ...opts, + }, + )(input); + + if (result) { + await Promise.all( + result.map( + async (d) => await upsertItem(d.normalizedData as T['Row']), + ), + ); + } + return getUserResponse(result) ?? null; + }, + ...opts, + }); +} + +export { useInsertMutation }; diff --git a/packages/postgrest-vue-query/src/mutate/use-update-mutation.ts b/packages/postgrest-vue-query/src/mutate/use-update-mutation.ts new file mode 100644 index 00000000..ab05c104 --- /dev/null +++ b/packages/postgrest-vue-query/src/mutate/use-update-mutation.ts @@ -0,0 +1,67 @@ +import { PostgrestQueryBuilder } from '@supabase/postgrest-js'; +import { GetResult } from '@supabase/postgrest-js/dist/module/select-query-parser'; +import { + GenericSchema, + GenericTable, +} from '@supabase/postgrest-js/dist/module/types'; +import { + buildUpdateFetcher, + getTable, +} from '@supabase-cache-helpers/postgrest-core'; +import { useMutation } from '@tanstack/vue-query'; + +import { UsePostgrestMutationOpts } from './types'; +import { useUpsertItem } from '../cache'; +import { useQueriesForTableLoader } from '../lib'; + +/** + * Hook to execute a UPDATE mutation + * + * @param {PostgrestQueryBuilder} qb PostgrestQueryBuilder instance for the table + * @param {Array} primaryKeys Array of primary keys of the table + * @param {string | null} query Optional PostgREST query string for the UPDATE mutation + * @param {UsePostgrestMutationOpts} [opts] Options to configure the hook + */ +function useUpdateMutation< + S extends GenericSchema, + T extends GenericTable, + RelationName, + Re = T extends { Relationships: infer R } ? R : unknown, + Q extends string = '*', + R = GetResult, +>( + qb: PostgrestQueryBuilder, + primaryKeys: (keyof T['Row'])[], + query?: Q | null, + opts?: UsePostgrestMutationOpts, +) { + const queriesForTable = useQueriesForTableLoader(getTable(qb)); + const upsertItem = useUpsertItem({ + ...opts, + primaryKeys, + table: getTable(qb), + schema: qb.schema as string, + }); + + return useMutation({ + mutationFn: async (input: T['Update']) => { + const result = await buildUpdateFetcher( + qb, + primaryKeys, + { + query: query ?? undefined, + queriesForTable, + disabled: opts?.disableAutoQuery, + ...opts, + }, + )(input); + if (result) { + await upsertItem(result.normalizedData as T['Row']); + } + return result?.userQueryData ?? null; + }, + ...opts, + }); +} + +export { useUpdateMutation }; diff --git a/packages/postgrest-vue-query/src/mutate/use-upsert-mutation.ts b/packages/postgrest-vue-query/src/mutate/use-upsert-mutation.ts new file mode 100644 index 00000000..62a9b60c --- /dev/null +++ b/packages/postgrest-vue-query/src/mutate/use-upsert-mutation.ts @@ -0,0 +1,66 @@ +import { PostgrestQueryBuilder } from '@supabase/postgrest-js'; +import { GetResult } from '@supabase/postgrest-js/dist/module/select-query-parser'; +import { + GenericSchema, + GenericTable, +} from '@supabase/postgrest-js/dist/module/types'; +import { + buildUpsertFetcher, + getTable, +} from '@supabase-cache-helpers/postgrest-core'; +import { useMutation } from '@tanstack/vue-query'; + +import { getUserResponse } from './get-user-response'; +import { UsePostgrestMutationOpts } from './types'; +import { useUpsertItem } from '../cache'; +import { useQueriesForTableLoader } from '../lib'; + +/** + * Hook to execute a UPSERT mutation + * + * @param {PostgrestQueryBuilder} qb PostgrestQueryBuilder instance for the table + * @param {Array} primaryKeys Array of primary keys of the table + * @param {string | null} query Optional PostgREST query string for the UPSERT mutation + * @param {UsePostgrestMutationOpts} [opts] Options to configure the hook + */ +function useUpsertMutation< + S extends GenericSchema, + T extends GenericTable, + RelationName, + Re = T extends { Relationships: infer R } ? R : unknown, + Q extends string = '*', + R = GetResult, +>( + qb: PostgrestQueryBuilder, + primaryKeys: (keyof T['Row'])[], + query?: Q | null, + opts?: UsePostgrestMutationOpts, +) { + const queriesForTable = useQueriesForTableLoader(getTable(qb)); + const upsertItem = useUpsertItem({ + ...opts, + primaryKeys, + table: getTable(qb), + schema: qb.schema as string, + }); + + return useMutation({ + mutationFn: async (input: T['Insert'][]) => { + const data = await buildUpsertFetcher(qb, { + query: query ?? undefined, + queriesForTable, + disabled: opts?.disableAutoQuery, + ...opts, + })(input); + if (data) { + await Promise.all( + data.map(async (d) => await upsertItem(d.normalizedData as T['Row'])), + ); + } + return getUserResponse(data) ?? null; + }, + ...opts, + }); +} + +export { useUpsertMutation }; diff --git a/packages/postgrest-vue-query/src/query/build-query-opts.ts b/packages/postgrest-vue-query/src/query/build-query-opts.ts new file mode 100644 index 00000000..c6a005e9 --- /dev/null +++ b/packages/postgrest-vue-query/src/query/build-query-opts.ts @@ -0,0 +1,27 @@ +import { PostgrestError } from '@supabase/postgrest-js'; +import { + AnyPostgrestResponse, + isPostgrestBuilder, +} from '@supabase-cache-helpers/postgrest-core'; +import { UseQueryOptions as UseVueQueryOptions } from '@tanstack/vue-query'; + +import { encode } from '../lib/key'; + +export function buildQueryOpts( + query: PromiseLike>, + config?: Omit< + UseVueQueryOptions, PostgrestError>, + 'queryKey' | 'queryFn' + >, +): UseVueQueryOptions, PostgrestError> { + return { + queryKey: encode(query, false), + queryFn: async () => { + if (isPostgrestBuilder(query)) { + query = query.throwOnError(); + } + return await query; + }, + ...config, + }; +} diff --git a/packages/postgrest-vue-query/src/query/fetch.ts b/packages/postgrest-vue-query/src/query/fetch.ts new file mode 100644 index 00000000..273c40dd --- /dev/null +++ b/packages/postgrest-vue-query/src/query/fetch.ts @@ -0,0 +1,51 @@ +import { + PostgrestError, + PostgrestMaybeSingleResponse, + PostgrestResponse, + PostgrestSingleResponse, +} from '@supabase/postgrest-js'; +import { AnyPostgrestResponse } from '@supabase-cache-helpers/postgrest-core'; +import { FetchQueryOptions, QueryClient } from '@tanstack/vue-query'; + +import { buildQueryOpts } from './build-query-opts'; + +function fetchQuery( + queryClient: QueryClient, + query: PromiseLike>, + config?: Omit< + FetchQueryOptions, PostgrestError>, + 'queryKey' | 'queryFn' + >, +): Promise>; +function fetchQuery( + queryClient: QueryClient, + query: PromiseLike>, + config?: Omit< + FetchQueryOptions, PostgrestError>, + 'queryKey' | 'queryFn' + >, +): Promise>; +function fetchQuery( + queryClient: QueryClient, + query: PromiseLike>, + config?: Omit< + FetchQueryOptions, PostgrestError>, + 'queryKey' | 'queryFn' + >, +): Promise>; + +async function fetchQuery( + queryClient: QueryClient, + query: PromiseLike>, + config?: Omit< + FetchQueryOptions, PostgrestError>, + 'queryKey' | 'queryFn' + >, +): Promise> { + return await queryClient.fetchQuery< + AnyPostgrestResponse, + PostgrestError + >(buildQueryOpts(query, config)); +} + +export { fetchQuery }; diff --git a/packages/postgrest-vue-query/src/query/index.ts b/packages/postgrest-vue-query/src/query/index.ts new file mode 100644 index 00000000..0870aa48 --- /dev/null +++ b/packages/postgrest-vue-query/src/query/index.ts @@ -0,0 +1,4 @@ +export * from './build-query-opts'; +export * from './fetch'; +export * from './prefetch'; +export * from './use-query'; diff --git a/packages/postgrest-vue-query/src/query/prefetch.ts b/packages/postgrest-vue-query/src/query/prefetch.ts new file mode 100644 index 00000000..ab212676 --- /dev/null +++ b/packages/postgrest-vue-query/src/query/prefetch.ts @@ -0,0 +1,76 @@ +import { + PostgrestError, + PostgrestMaybeSingleResponse, + PostgrestResponse, + PostgrestSingleResponse, +} from '@supabase/postgrest-js'; +import { + AnyPostgrestResponse, + isPostgrestBuilder, +} from '@supabase-cache-helpers/postgrest-core'; +import { FetchQueryOptions, QueryClient } from '@tanstack/vue-query'; + +import { buildQueryOpts } from './build-query-opts'; +import { encode } from '../lib'; + +function prefetchQuery( + queryClient: QueryClient, + query: PromiseLike>, + config?: Omit< + FetchQueryOptions, PostgrestError>, + 'queryKey' | 'queryFn' + >, +): Promise; +function prefetchQuery( + queryClient: QueryClient, + query: PromiseLike>, + config?: Omit< + FetchQueryOptions, PostgrestError>, + 'queryKey' | 'queryFn' + >, +): Promise; +function prefetchQuery( + queryClient: QueryClient, + query: PromiseLike>, + config?: Omit< + FetchQueryOptions, PostgrestError>, + 'queryKey' | 'queryFn' + >, +): Promise; + +async function prefetchQuery( + queryClient: QueryClient, + query: PromiseLike>, + config?: Omit< + FetchQueryOptions, PostgrestError>, + 'queryKey' | 'queryFn' + >, +) { + await queryClient.prefetchQuery, PostgrestError>( + buildQueryOpts(query, config), + ); +} + +function fetchQueryInitialData( + query: PromiseLike>, +): Promise<[string[], PostgrestSingleResponse]>; + +function fetchQueryInitialData( + query: PromiseLike>, +): Promise<[string[], PostgrestMaybeSingleResponse]>; + +function fetchQueryInitialData( + query: PromiseLike>, +): Promise<[string[], PostgrestResponse]>; + +async function fetchQueryInitialData( + query: PromiseLike>, +): Promise<[string[], AnyPostgrestResponse]> { + if (!isPostgrestBuilder(query)) { + throw new Error('Query is not a PostgrestBuilder'); + } + + return [encode(query, false), await query.throwOnError()]; +} + +export { prefetchQuery, fetchQueryInitialData }; diff --git a/packages/postgrest-vue-query/src/query/use-query.ts b/packages/postgrest-vue-query/src/query/use-query.ts new file mode 100644 index 00000000..43ba9ff1 --- /dev/null +++ b/packages/postgrest-vue-query/src/query/use-query.ts @@ -0,0 +1,140 @@ +import { + PostgrestError, + PostgrestResponse, + PostgrestSingleResponse, + PostgrestMaybeSingleResponse, +} from '@supabase/postgrest-js'; +import { AnyPostgrestResponse } from '@supabase-cache-helpers/postgrest-core'; +import { + useQuery as useVueQuery, + UseQueryReturnType as UseVueQueryResult, + UseQueryOptions as UseVueQueryOptions, +} from '@tanstack/vue-query'; +import { toRef, ref, unref } from 'vue'; + +import { buildQueryOpts } from './build-query-opts'; + +/** + * Represents the return value of the `useQuery` hook when `query` is expected to return + * a single row. + */ +export type UseQuerySingleReturn = Omit< + UseVueQueryResult['data'], PostgrestError>, + 'refetch' +> & + Pick< + UseVueQueryResult, PostgrestError>, + 'refetch' + > & + Pick, 'count'>; + +/** + * Represents the return value of the `useQuery` hook when `query` is expected to return + * either a single row or an empty response. + */ +export type UseQueryMaybeSingleReturn = Omit< + UseVueQueryResult< + PostgrestMaybeSingleResponse['data'], + PostgrestError + >, + 'refetch' +> & + Pick< + UseVueQueryResult, PostgrestError>, + 'refetch' + > & + Pick, 'count'>; + +/** + * Represents the return value of the `useQuery` hook when `query` is expected to return + * one or more rows. + */ +export type UseQueryReturn = Omit< + UseVueQueryResult['data'], PostgrestError>, + 'refetch' +> & + Pick< + UseVueQueryResult, PostgrestError>, + 'refetch' + > & + Pick, 'count'>; + +/** + * Represents the return value of the `useQuery` hook when the type of the query response + * is not known. + */ +export type UseQueryAnyReturn = Omit< + UseVueQueryResult['data'], PostgrestError>, + 'refetch' +> & + Pick< + UseVueQueryResult, PostgrestError>, + 'refetch' + > & + Pick, 'count'>; + +/** + * Vue hook to execute a PostgREST query and return a single item response. + * + * @param {PromiseLike>} query A promise that resolves to a PostgREST single item response. + * @param {Omit, PostgrestError>, 'queryKey' | 'queryFn'>} [config] The Vue Query options. + * @returns {UseQuerySingleReturn} The hook result containing the single item response data. + */ +function useQuery( + query: PromiseLike>, + config?: UseVueQueryOptions, PostgrestError>, +): UseQuerySingleReturn; +/** + * Vue hook to execute a PostgREST query and return a maybe single item response. + * + * @param {PromiseLike>} query A promise that resolves to a PostgREST maybe single item response. + * @param {Omit, PostgrestError>, 'queryKey' | 'queryFn'>} [config] The Vue Query options. + * @returns {UseQueryMaybeSingleReturn} The hook result containing the maybe single item response data. + */ +function useQuery( + query: PromiseLike>, + config?: UseVueQueryOptions< + PostgrestMaybeSingleResponse, + PostgrestError + >, +): UseQueryMaybeSingleReturn; +/** + * Vue hook to execute a PostgREST query. + * + * @template Result The expected response data type. + * @param {PromiseLike>} query A promise that resolves to a PostgREST response. + * @param {Omit, PostgrestError>, 'queryKey' | 'queryFn'>} [config] The Vue Query options. + * @returns {UseQueryReturn} The hook result containing the response data. + */ +function useQuery( + query: PromiseLike>, + config?: UseVueQueryOptions, PostgrestError>, +): UseQueryReturn; + +/** + * Vue hook to execute a PostgREST query. + * + * @template Result The expected response data type. + * @param {PromiseLike>} query A promise that resolves to a PostgREST response of any kind. + * @param {Omit, PostgrestError>, 'queryKey' | 'queryFn'>} [config] The Vue Query options. + * @returns {UseQueryAnyReturn} The hook result containing the response data. + */ +function useQuery( + query: PromiseLike>, + config?: UseVueQueryOptions, PostgrestError>, +): UseQueryAnyReturn { + const { data, ...rest } = useVueQuery< + AnyPostgrestResponse, + PostgrestError + >(buildQueryOpts(query, config)); + + // TODO: data: data.value || Type 'AnyPostgrestResponse | undefined' is not assignable to type 'Ref | Ref' + // TODO: data: data.value?.data || Type 'Result | Result[] | null | undefined' is not assignable to type 'Ref | Ref'. + return { + data: data.value?.data, + count: data.value?.count ?? null, + ...rest, + }; +} + +export { useQuery }; diff --git a/packages/postgrest-vue-query/src/subscribe/index.ts b/packages/postgrest-vue-query/src/subscribe/index.ts new file mode 100644 index 00000000..2289783c --- /dev/null +++ b/packages/postgrest-vue-query/src/subscribe/index.ts @@ -0,0 +1,2 @@ +export * from './use-subscription-query'; +export * from './use-subscription'; diff --git a/packages/postgrest-vue-query/src/subscribe/use-subscription-query.ts b/packages/postgrest-vue-query/src/subscribe/use-subscription-query.ts new file mode 100644 index 00000000..62cac68e --- /dev/null +++ b/packages/postgrest-vue-query/src/subscribe/use-subscription-query.ts @@ -0,0 +1,172 @@ +import { GetResult } from '@supabase/postgrest-js/dist/module/select-query-parser'; +import { + GenericSchema, + GenericTable, +} from '@supabase/postgrest-js/dist/module/types'; +import { + RealtimeChannel, + RealtimePostgresChangesFilter, + RealtimePostgresChangesPayload, + REALTIME_LISTEN_TYPES, + REALTIME_POSTGRES_CHANGES_LISTEN_EVENT, + SupabaseClient, +} from '@supabase/supabase-js'; +import { + buildNormalizedQuery, + normalizeResponse, + RevalidateOpts, +} from '@supabase-cache-helpers/postgrest-core'; +import { MutationOptions as VueQueryMutatorOptions } from '@tanstack/vue-query'; +import { watchEffect, ref } from 'vue'; + +import { useDeleteItem, useUpsertItem } from '../cache'; +import { useQueriesForTableLoader } from '../lib'; + +/** + * Options for `useSubscriptionQuery` hook + */ +export type UseSubscriptionQueryOpts< + S extends GenericSchema, + T extends GenericTable, + RelationName, + Relatsonships, + Q extends string = '*', + R = GetResult< + S, + T['Row'], + RelationName, + Relatsonships, + Q extends '*' ? '*' : Q + >, +> = RevalidateOpts & + VueQueryMutatorOptions & { + /** + * A callback that will be called whenever a realtime event occurs for the given channel. + * The callback will receive the event payload with an additional "data" property, which will be + * the affected row of the event (or a modified version of it, if a select query is provided). + */ + callback?: ( + event: RealtimePostgresChangesPayload & { data: T['Row'] | R }, + ) => void | Promise; + }; + +/** + * A hook for subscribing to realtime Postgres events on a given channel. + * + * The subscription will automatically update the cache for the specified table in response + * to incoming Postgres events, and optionally run a user-provided callback function with the + * event and the updated data. + * + * This hook works by creating a Supabase Realtime channel for the specified table and + * subscribing to Postgres changes on that channel. When an event is received, the hook + * fetches the updated data from the database (using a `select` query generated from the cache + * configuration), and then updates the cache accordingly. + * + * @param client - The Supabase client instance. + * @param channelName - The name of the channel to subscribe to. + * @param filter - The filter object to use when listening for changes. + * @param primaryKeys - An array of the primary keys for the table being listened to. + * @param query - An optional PostgREST query to use when selecting data for an event. + * @param opts - Additional options to pass to the hook. + * @returns An object containing the RealtimeChannel and the current status of the subscription. + */ +function useSubscriptionQuery< + S extends GenericSchema, + T extends GenericTable, + RelationName, + Relationships, + Q extends string = '*', + R = GetResult< + S, + T['Row'], + RelationName, + Relationships, + Q extends '*' ? '*' : Q + >, +>( + client: SupabaseClient | null, + channelName: string, + filter: Omit< + RealtimePostgresChangesFilter<`${REALTIME_POSTGRES_CHANGES_LISTEN_EVENT.ALL}`>, + 'table' + > & { table: string }, + primaryKeys: (keyof T['Row'])[], + query?: Q extends '*' ? "'*' is not allowed" : Q | null, + opts?: UseSubscriptionQueryOpts, +) { + const statusRef = ref(); + const channelRef = ref(); + const queriesForTable = useQueriesForTableLoader(filter.table); + const deleteItem = useDeleteItem({ + ...opts, + primaryKeys, + table: filter.table, + schema: filter.schema, + }); + const upsertItem = useUpsertItem({ + ...opts, + primaryKeys, + table: filter.table, + schema: filter.schema, + }); + + watchEffect((onCleanup) => { + if (!client) return; + + const c = client + .channel(channelName) + .on( + REALTIME_LISTEN_TYPES.POSTGRES_CHANGES, + filter, + async (payload) => { + let data: T['Row'] | R = payload.new ?? payload.old; + const selectQuery = buildNormalizedQuery({ queriesForTable, query }); + if ( + payload.eventType !== + REALTIME_POSTGRES_CHANGES_LISTEN_EVENT.DELETE && + selectQuery + ) { + const qb = client + .from(payload.table) + .select(selectQuery.selectQuery); + for (const pk of primaryKeys) { + qb.eq(pk.toString(), data[pk]); + } + const res = await qb.single(); + if (res.data) { + data = normalizeResponse(selectQuery.groupedPaths, res.data) as R; + } + } + + if ( + payload.eventType === + REALTIME_POSTGRES_CHANGES_LISTEN_EVENT.INSERT || + payload.eventType === REALTIME_POSTGRES_CHANGES_LISTEN_EVENT.UPDATE + ) { + await upsertItem(data as Record); + } else if ( + payload.eventType === REALTIME_POSTGRES_CHANGES_LISTEN_EVENT.DELETE + ) { + await deleteItem(data as Record); + } + if (opts?.callback) { + opts.callback({ + ...payload, + data, + }); + } + }, + ) + .subscribe((status: string) => (statusRef.value = status)); + + channelRef.value = c; + + onCleanup(() => { + if (c) c.unsubscribe(); + }); + }); + + return { channelRef, statusRef }; +} + +export { useSubscriptionQuery }; diff --git a/packages/postgrest-vue-query/src/subscribe/use-subscription.ts b/packages/postgrest-vue-query/src/subscribe/use-subscription.ts new file mode 100644 index 00000000..d4c6ce63 --- /dev/null +++ b/packages/postgrest-vue-query/src/subscribe/use-subscription.ts @@ -0,0 +1,98 @@ +import { GenericTable } from '@supabase/postgrest-js/dist/module/types'; +import { + RealtimePostgresChangesFilter, + RealtimePostgresChangesPayload, + REALTIME_LISTEN_TYPES, + REALTIME_POSTGRES_CHANGES_LISTEN_EVENT, + SupabaseClient, +} from '@supabase/supabase-js'; +import { RevalidateOpts } from '@supabase-cache-helpers/postgrest-core'; +import { MutationOptions as VueQueryMutatorOptions } from '@tanstack/vue-query'; +import { watchEffect, ref } from 'vue'; + +import { useDeleteItem, useUpsertItem } from '../cache'; + +/** + * Options for the `useSubscription` hook. + */ +export type UseSubscriptionOpts = RevalidateOpts< + T['Row'] +> & + VueQueryMutatorOptions & { + callback?: ( + event: RealtimePostgresChangesPayload, + ) => void | Promise; + }; + +/** + * Hook that sets up a real-time subscription to a Postgres database table. + * + * @param channel - The real-time channel to subscribe to. + * @param filter - A filter that specifies the table and conditions for the subscription. + * @param primaryKeys - An array of primary key column names for the table. + * @param opts - Options for the mutation function used to upsert or delete rows in the cache. + * + * @returns An object containing the current status of the subscription. + */ +function useSubscription( + client: SupabaseClient | null, + channelName: string, + filter: Omit< + RealtimePostgresChangesFilter<`${REALTIME_POSTGRES_CHANGES_LISTEN_EVENT.ALL}`>, + 'table' + > & { table: string }, + primaryKeys: (keyof T['Row'])[], + opts?: UseSubscriptionOpts, +) { + const statusRef = ref(); + const deleteItem = useDeleteItem({ + ...opts, + primaryKeys, + table: filter.table, + schema: filter.schema, + }); + const upsertItem = useUpsertItem({ + ...opts, + primaryKeys, + table: filter.table, + schema: filter.schema, + }); + + watchEffect(() => { + if (!client) return; + + const c = client + .channel(channelName) + .on( + REALTIME_LISTEN_TYPES.POSTGRES_CHANGES, + filter, + async (payload) => { + if ( + payload.eventType === + REALTIME_POSTGRES_CHANGES_LISTEN_EVENT.INSERT || + payload.eventType === REALTIME_POSTGRES_CHANGES_LISTEN_EVENT.UPDATE + ) { + await upsertItem(payload.new); + } else if ( + payload.eventType === REALTIME_POSTGRES_CHANGES_LISTEN_EVENT.DELETE + ) { + await deleteItem(payload.old); + } + if (opts?.callback) { + opts.callback({ + ...payload, + }); + } + }, + ) + .subscribe((status: string) => (statusRef.value = status)); + + return () => { + if (c) c.unsubscribe(); + }; + }); + + return { status: statusRef }; +} + +export { useSubscription }; diff --git a/packages/postgrest-vue-query/tsconfig.json b/packages/postgrest-vue-query/tsconfig.json new file mode 100644 index 00000000..c5f94dfa --- /dev/null +++ b/packages/postgrest-vue-query/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "@supabase-cache-helpers/tsconfig/web.json", + "include": ["**/*.ts"], + "exclude": ["node_modules"], + "compilerOptions": { + "noErrorTruncation": true + } +} diff --git a/packages/postgrest-vue-query/tsup.config.ts b/packages/postgrest-vue-query/tsup.config.ts new file mode 100644 index 00000000..7cfe8a38 --- /dev/null +++ b/packages/postgrest-vue-query/tsup.config.ts @@ -0,0 +1,15 @@ +import type { Options } from 'tsup'; + +export const tsup: Options = { + dts: true, + entryPoints: ['src/index.ts'], + external: ['vue', /^@supabase\//], + format: ['cjs', 'esm'], + // inject: ['src/react-shim.js'], + // ! .cjs/.mjs doesn't work with Angular's webpack4 config by default! + legacyOutput: false, + sourcemap: true, + splitting: false, + bundle: true, + clean: true, +}; diff --git a/packages/storage-vue-query/.eslintrc.json b/packages/storage-vue-query/.eslintrc.json new file mode 100644 index 00000000..de95d41d --- /dev/null +++ b/packages/storage-vue-query/.eslintrc.json @@ -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" + ] +} diff --git a/packages/storage-vue-query/CHANGELOG.md b/packages/storage-vue-query/CHANGELOG.md new file mode 100644 index 00000000..202549aa --- /dev/null +++ b/packages/storage-vue-query/CHANGELOG.md @@ -0,0 +1,77 @@ +# @supabase-cache-helpers/storage-vue-query + +## 1.2.1 + +### Patch Changes + +- 40a6327: fix: update typescript to 5.4.2 +- Updated dependencies [40a6327] + - @supabase-cache-helpers/storage-core@0.0.4 + +## 1.2.0 + +### Minor Changes + +- bfbb039: feat: add query opt builder fns for storage + +## 1.1.2 + +### Patch Changes + +- f2ca765: chore: upgrade supabase-js to 2.38.5 +- f2ca765: chore: upgrade storage-js to 2.5.5 +- Updated dependencies [f2ca765] +- Updated dependencies [f2ca765] + - @supabase-cache-helpers/storage-core@0.0.3 + +## 1.1.1 + +### Patch Changes + +- Updated dependencies [f9dd4e4] + - @supabase-cache-helpers/storage-core@0.0.2 + +## 1.1.0 + +### Minor Changes + +- 7a71f52: chore: add support for react-query v5 + +## 1.0.4 + +### Patch Changes + +- 6b1f00c: fix: type configuration parameter and add tests for fallbackData +- 2f1d3cb: refactor: merge internal packages into one core package per product +- Updated dependencies [2f1d3cb] + - @supabase-cache-helpers/storage-core@0.0.1 + +## 1.0.3 + +### Patch Changes + +- ad7efb0: chore: upgrade supabase to latest +- Updated dependencies [ad7efb0] + - @supabase-cache-helpers/storage-fetcher@1.0.9 + +## 1.0.2 + +### Patch Changes + +- 5acf83a: Fix types for mjs when using "moduleResolution" other then "node" (node16, nodenext, bundler) +- Updated dependencies [5acf83a] + - @supabase-cache-helpers/storage-fetcher@1.0.8 + - @supabase-cache-helpers/storage-mutate@1.0.4 + +## 1.0.1 + +### Patch Changes + +- Updated dependencies [9fd9f7e] + - @supabase-cache-helpers/storage-fetcher@1.0.7 + +## 1.0.0 + +### Major Changes + +- 43a126e: feat: initial commit diff --git a/packages/storage-vue-query/README.md b/packages/storage-vue-query/README.md new file mode 100644 index 00000000..1de45a13 --- /dev/null +++ b/packages/storage-vue-query/README.md @@ -0,0 +1,29 @@ +# Supabase Storage Vue Query + +A collection of React Query utilities for working with Supabase. + +Latest build +GitHub Stars +[![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).** diff --git a/packages/storage-vue-query/__tests__/__fixtures__/1.jpg b/packages/storage-vue-query/__tests__/__fixtures__/1.jpg new file mode 100644 index 00000000..a034ac4d Binary files /dev/null and b/packages/storage-vue-query/__tests__/__fixtures__/1.jpg differ diff --git a/packages/storage-vue-query/__tests__/__fixtures__/2.jpg b/packages/storage-vue-query/__tests__/__fixtures__/2.jpg new file mode 100644 index 00000000..c9aa9aac Binary files /dev/null and b/packages/storage-vue-query/__tests__/__fixtures__/2.jpg differ diff --git a/packages/storage-vue-query/__tests__/__fixtures__/3.jpg b/packages/storage-vue-query/__tests__/__fixtures__/3.jpg new file mode 100644 index 00000000..60a0b682 Binary files /dev/null and b/packages/storage-vue-query/__tests__/__fixtures__/3.jpg differ diff --git a/packages/storage-vue-query/__tests__/__fixtures__/4.jpg b/packages/storage-vue-query/__tests__/__fixtures__/4.jpg new file mode 100644 index 00000000..956dfc82 Binary files /dev/null and b/packages/storage-vue-query/__tests__/__fixtures__/4.jpg differ diff --git a/packages/storage-vue-query/__tests__/components/DirectoryPage.vue b/packages/storage-vue-query/__tests__/components/DirectoryPage.vue new file mode 100644 index 00000000..a20630ad --- /dev/null +++ b/packages/storage-vue-query/__tests__/components/DirectoryPage.vue @@ -0,0 +1,24 @@ + + + diff --git a/packages/storage-vue-query/__tests__/components/DirectoryUrlsPage.vue b/packages/storage-vue-query/__tests__/components/DirectoryUrlsPage.vue new file mode 100644 index 00000000..1e7d093f --- /dev/null +++ b/packages/storage-vue-query/__tests__/components/DirectoryUrlsPage.vue @@ -0,0 +1,24 @@ + + + diff --git a/packages/storage-vue-query/__tests__/components/FileUrlPage.vue b/packages/storage-vue-query/__tests__/components/FileUrlPage.vue new file mode 100644 index 00000000..8d5aa3af --- /dev/null +++ b/packages/storage-vue-query/__tests__/components/FileUrlPage.vue @@ -0,0 +1,24 @@ + + + diff --git a/packages/storage-vue-query/__tests__/components/RemoveDirectoryPage.vue b/packages/storage-vue-query/__tests__/components/RemoveDirectoryPage.vue new file mode 100644 index 00000000..608ae9f2 --- /dev/null +++ b/packages/storage-vue-query/__tests__/components/RemoveDirectoryPage.vue @@ -0,0 +1,27 @@ + + + diff --git a/packages/storage-vue-query/__tests__/components/RemoveFilesPage.vue b/packages/storage-vue-query/__tests__/components/RemoveFilesPage.vue new file mode 100644 index 00000000..6fc4bed0 --- /dev/null +++ b/packages/storage-vue-query/__tests__/components/RemoveFilesPage.vue @@ -0,0 +1,31 @@ + + + diff --git a/packages/storage-vue-query/__tests__/components/UploadPage.vue b/packages/storage-vue-query/__tests__/components/UploadPage.vue new file mode 100644 index 00000000..313392bc --- /dev/null +++ b/packages/storage-vue-query/__tests__/components/UploadPage.vue @@ -0,0 +1,29 @@ + + + diff --git a/packages/storage-vue-query/__tests__/lib/decode.spec.ts b/packages/storage-vue-query/__tests__/lib/decode.spec.ts new file mode 100644 index 00000000..9f18c385 --- /dev/null +++ b/packages/storage-vue-query/__tests__/lib/decode.spec.ts @@ -0,0 +1,7 @@ +import { decode } from '../../src'; + +describe('decode', () => { + it('should return null for invalid key', () => { + expect(decode(['some', 'unrelated', 'key'])).toEqual(null); + }); +}); diff --git a/packages/storage-vue-query/__tests__/lib/key.spec.ts b/packages/storage-vue-query/__tests__/lib/key.spec.ts new file mode 100644 index 00000000..898d28d2 --- /dev/null +++ b/packages/storage-vue-query/__tests__/lib/key.spec.ts @@ -0,0 +1,11 @@ +import { assertStorageKeyInput } from '../../src/lib'; + +describe('key', () => { + describe('assertStorageKeyInput', () => { + it('should throw for invalid key', () => { + expect(() => assertStorageKeyInput(['some', 'unrelated', 'key'])).toThrow( + 'Invalid key', + ); + }); + }); +}); diff --git a/packages/storage-vue-query/__tests__/mutate/use-remove-directory.spec.ts b/packages/storage-vue-query/__tests__/mutate/use-remove-directory.spec.ts new file mode 100644 index 00000000..5780966b --- /dev/null +++ b/packages/storage-vue-query/__tests__/mutate/use-remove-directory.spec.ts @@ -0,0 +1,39 @@ +import { createClient, SupabaseClient } from '@supabase/supabase-js'; +import { fetchDirectory } from '@supabase-cache-helpers/storage-core'; +import { fireEvent, screen } from '@testing-library/vue'; +import 'ts-jest/globals'; + +import { cleanup, renderWithConfig, upload } from '../utils'; +import Page from '../components/RemoveDirectoryPage.vue'; + +const TEST_PREFIX = 'postgrest-storage-remove'; + +describe('useRemoveDirectory', () => { + let client: SupabaseClient; + let dirName: string; + let files: string[]; + + beforeAll(async () => { + dirName = `${TEST_PREFIX}-${Math.floor(Math.random() * 100)}`; + client = createClient( + process.env.SUPABASE_URL as string, + process.env.SUPABASE_ANON_KEY as string, + ); + + await Promise.all([ + cleanup(client, 'public_contact_files', dirName), + cleanup(client, 'private_contact_files', dirName), + ]); + + files = await upload(client, 'private_contact_files', dirName); + }); + + it('should remove all files in a directory', async () => { + renderWithConfig(Page, { client, dirName }); + fireEvent.click(screen.getByTestId('remove')); + await screen.findByText('isSuccess: true', {}, { timeout: 10000 }); + await expect( + fetchDirectory(client.storage.from('private_contact_files'), dirName), + ).resolves.toEqual([]); + }); +}); diff --git a/packages/storage-vue-query/__tests__/mutate/use-remove-files.spec.ts b/packages/storage-vue-query/__tests__/mutate/use-remove-files.spec.ts new file mode 100644 index 00000000..e984a20f --- /dev/null +++ b/packages/storage-vue-query/__tests__/mutate/use-remove-files.spec.ts @@ -0,0 +1,39 @@ +import { createClient, SupabaseClient } from '@supabase/supabase-js'; +import { fetchDirectory } from '@supabase-cache-helpers/storage-core'; +import { fireEvent, screen } from '@testing-library/react'; +import 'ts-jest/globals'; + +import { cleanup, renderWithConfig, upload } from '../utils'; +import Page from '../components/RemoveFilesPage.vue'; + +const TEST_PREFIX = 'postgrest-storage-remove'; + +describe('useRemoveFiles', () => { + let client: SupabaseClient; + let dirName: string; + let files: string[]; + + beforeAll(async () => { + dirName = `${TEST_PREFIX}-${Math.floor(Math.random() * 100)}`; + client = createClient( + process.env.SUPABASE_URL as string, + process.env.SUPABASE_ANON_KEY as string, + ); + + await Promise.all([ + cleanup(client, 'public_contact_files', dirName), + cleanup(client, 'private_contact_files', dirName), + ]); + + files = await upload(client, 'private_contact_files', dirName); + }); + + it('should remove files', async () => { + renderWithConfig(Page, { client, dirName, files }); + fireEvent.click(screen.getByTestId('remove')); + await screen.findByText('isSuccess: true', {}, { timeout: 10000 }); + await expect( + fetchDirectory(client.storage.from('private_contact_files'), dirName), + ).resolves.toEqual([]); + }); +}); diff --git a/packages/storage-vue-query/__tests__/mutate/use-upload.spec.ts b/packages/storage-vue-query/__tests__/mutate/use-upload.spec.ts new file mode 100644 index 00000000..e5fce876 --- /dev/null +++ b/packages/storage-vue-query/__tests__/mutate/use-upload.spec.ts @@ -0,0 +1,46 @@ +import { createClient, SupabaseClient } from '@supabase/supabase-js'; +import { fetchDirectory } from '@supabase-cache-helpers/storage-core'; +import { fireEvent, screen } from '@testing-library/react'; +import 'ts-jest/globals'; + +import { cleanup, loadFixtures, renderWithConfig } from '../utils'; +import Page from '../components/UploadPage.vue'; + +const TEST_PREFIX = 'postgrest-storage-upload'; + +describe('useUpload', () => { + let client: SupabaseClient; + let dirName: string; + let fileNames: string[]; + let files: File[]; + + beforeAll(async () => { + dirName = `${TEST_PREFIX}-${Math.floor(Math.random() * 100)}`; + client = createClient( + process.env.SUPABASE_URL as string, + process.env.SUPABASE_ANON_KEY as string, + ); + + await Promise.all([ + cleanup(client, 'public_contact_files', dirName), + cleanup(client, 'private_contact_files', dirName), + ]); + + const fixtures = await loadFixtures(); + fileNames = fixtures.fileNames; + files = fixtures.files; + }); + + it('should upload files', async () => { + renderWithConfig(Page, { client, dirName, files }); + fireEvent.click(screen.getByTestId('upload')); + await screen.findByText('isSuccess: true', {}, { timeout: 10000 }); + await expect( + fetchDirectory(client.storage.from('private_contact_files'), dirName), + ).resolves.toEqual( + expect.arrayContaining( + files.map((f) => expect.objectContaining({ name: f.name })), + ), + ); + }); +}); diff --git a/packages/storage-vue-query/__tests__/query/use-directory-urls.spec.ts b/packages/storage-vue-query/__tests__/query/use-directory-urls.spec.ts new file mode 100644 index 00000000..11f9c2b4 --- /dev/null +++ b/packages/storage-vue-query/__tests__/query/use-directory-urls.spec.ts @@ -0,0 +1,41 @@ +import { createClient, SupabaseClient } from '@supabase/supabase-js'; +import { screen } from '@testing-library/react'; +import 'ts-jest/globals'; + +import { cleanup, renderWithConfig, upload } from '../utils'; +import Page from '../components/DirectoryUrlsPage.vue'; + +const TEST_PREFIX = 'postgrest-storage-directory-urls'; + +describe('useDirectoryFileUrls', () => { + let client: SupabaseClient; + let dirName: string; + let privateFiles: string[]; + let publicFiles: string[]; + + beforeAll(async () => { + dirName = `${TEST_PREFIX}-${Math.floor(Math.random() * 100)}`; + client = createClient( + process.env.SUPABASE_URL as string, + process.env.SUPABASE_ANON_KEY as string, + ); + + await Promise.all([ + cleanup(client, 'public_contact_files', dirName), + cleanup(client, 'private_contact_files', dirName), + ]); + + privateFiles = await upload(client, 'private_contact_files', dirName); + publicFiles = await upload(client, 'public_contact_files', dirName); + }); + + it('should return files', async () => { + renderWithConfig(Page, { client, dirName }); + await Promise.all( + privateFiles.map( + async (f) => + await screen.findByText(`${f}: exists`, {}, { timeout: 10000 }), + ), + ); + }); +}); diff --git a/packages/storage-vue-query/__tests__/query/use-directory.spec.ts b/packages/storage-vue-query/__tests__/query/use-directory.spec.ts new file mode 100644 index 00000000..8f081a45 --- /dev/null +++ b/packages/storage-vue-query/__tests__/query/use-directory.spec.ts @@ -0,0 +1,40 @@ +import { createClient, SupabaseClient } from '@supabase/supabase-js'; +import { screen } from '@testing-library/react'; +import 'ts-jest/globals'; + +import { cleanup, renderWithConfig, upload } from '../utils'; +import Page from '../components/DirectoryPage.vue'; + +const TEST_PREFIX = 'postgrest-storage-directory'; + +describe('useDirectory', () => { + let client: SupabaseClient; + let dirName: string; + let privateFiles: string[]; + let publicFiles: string[]; + + beforeAll(async () => { + dirName = `${TEST_PREFIX}-${Math.floor(Math.random() * 100)}`; + client = createClient( + process.env.SUPABASE_URL as string, + process.env.SUPABASE_ANON_KEY as string, + ); + + await Promise.all([ + cleanup(client, 'public_contact_files', dirName), + cleanup(client, 'private_contact_files', dirName), + ]); + + privateFiles = await upload(client, 'private_contact_files', dirName); + publicFiles = await upload(client, 'public_contact_files', dirName); + }); + + it('should return files', async () => { + renderWithConfig(Page, { client, dirName }); + await Promise.all( + privateFiles.map( + async (f) => await screen.findByText(f, {}, { timeout: 10000 }), + ), + ); + }); +}); diff --git a/packages/storage-vue-query/__tests__/query/use-file-url.spec.ts b/packages/storage-vue-query/__tests__/query/use-file-url.spec.ts new file mode 100644 index 00000000..30bc06c5 --- /dev/null +++ b/packages/storage-vue-query/__tests__/query/use-file-url.spec.ts @@ -0,0 +1,36 @@ +import { createClient, SupabaseClient } from '@supabase/supabase-js'; +import { screen } from '@testing-library/react'; +import 'ts-jest/globals'; + +import { cleanup, renderWithConfig, upload } from '../utils'; +import Page from '../components/FileUrlPage.vue'; + +const TEST_PREFIX = 'postgrest-storage-file-url'; + +describe('useFileUrl', () => { + let client: SupabaseClient; + let dirName: string; + let privateFiles: string[]; + let publicFiles: string[]; + + beforeAll(async () => { + dirName = `${TEST_PREFIX}-${Math.floor(Math.random() * 100)}`; + client = createClient( + process.env.SUPABASE_URL as string, + process.env.SUPABASE_ANON_KEY as string, + ); + + await Promise.all([ + cleanup(client, 'public_contact_files', dirName), + cleanup(client, 'private_contact_files', dirName), + ]); + + privateFiles = await upload(client, 'private_contact_files', dirName); + publicFiles = await upload(client, 'public_contact_files', dirName); + }); + + it('should return file url', async () => { + renderWithConfig(Page, { client, dirName, publicFiles }); + await screen.findByText('URL: exists', {}, { timeout: 10000 }); + }); +}); diff --git a/packages/storage-vue-query/__tests__/utils.ts b/packages/storage-vue-query/__tests__/utils.ts new file mode 100644 index 00000000..21201acc --- /dev/null +++ b/packages/storage-vue-query/__tests__/utils.ts @@ -0,0 +1,65 @@ +import { SupabaseClient } from '@supabase/supabase-js'; +import { QueryClient, VueQueryPlugin } from '@tanstack/vue-query'; +import { render } from '@testing-library/vue'; +import * as dotenv from 'dotenv'; +import { readdir, readFile } from 'node:fs/promises'; +import { resolve, join } from 'node:path'; + +dotenv.config({ path: resolve(__dirname, '../../../.env.local') }); + +export const renderWithConfig = ( + element: any, + props?: { [key: string]: unknown }, + queryClient?: QueryClient, +): ReturnType => { + const client = queryClient ?? new QueryClient(); + return render(element, { + props, + global: { + plugins: [[VueQueryPlugin, { queryClient: client }]], + }, + }); +}; + +export const loadFixtures = async () => { + const fixturesDir = resolve(__dirname, '__fixtures__'); + const fileNames = await readdir(fixturesDir); + return { + fileNames, + files: await Promise.all( + fileNames.map( + async (f) => + new File([(await readFile(join(fixturesDir, f))) as BlobPart], f), + ), + ), + }; +}; + +export const upload = async ( + client: SupabaseClient, + bucketName: string, + dirName: string, +): Promise => { + const fixturesDir = resolve(__dirname, '__fixtures__'); + const fileNames = await readdir(fixturesDir); + await Promise.all( + fileNames.map( + async (f) => + await client.storage + .from(bucketName) + .upload(`${dirName}/${f}`, await readFile(join(fixturesDir, f))), + ), + ); + return fileNames; +}; + +export const cleanup = async ( + client: SupabaseClient, + bucketName: string, + dirName: string, +) => { + const { data } = await client.storage.from(bucketName).list(dirName); + await client.storage + .from(bucketName) + .remove((data ?? []).map((d) => `${dirName}/${d.name}`)); +}; diff --git a/packages/storage-vue-query/package.json b/packages/storage-vue-query/package.json new file mode 100644 index 00000000..4e1bfb8e --- /dev/null +++ b/packages/storage-vue-query/package.json @@ -0,0 +1,81 @@ +{ + "name": "@supabase-cache-helpers/storage-vue-query", + "version": "0.0.1", + "author": "Christian Pannwitz ", + "homepage": "https://supabase-cache-helpers.vercel.app", + "bugs": { + "url": "https://github.com/psteinroe/supabase-cache-helpers/issues" + }, + "main": "./dist/index.js", + "source": "./src/index.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.mjs", + "require": "./dist/index.js" + }, + "./package.json": "./package.json" + }, + "types": "./dist/index.d.ts", + "files": [ + "dist/**" + ], + "publishConfig": { + "access": "public" + }, + "license": "MIT", + "scripts": { + "build": "tsup", + "test": "jest --coverage", + "clean": "rm -rf .turbo && rm -rf lint-results && rm -rf .nyc_output && rm -rf node_modules && rm -rf dist", + "lint": "eslint {src/**,__tests__/**} --no-error-on-unmatched-pattern --ignore-pattern '__tests__/__fixtures__/*'", + "lint:report": "eslint {src/**,__tests__/**} --format json --output-file ./lint-results/storage-vue-query.json --no-error-on-unmatched-pattern --ignore-pattern '__tests__/__fixtures__/*'", + "lint:fix": "eslint {src/**,__tests__/**} --fix --no-error-on-unmatched-pattern --ignore-pattern '__tests__/__fixtures__/*'", + "typecheck": "tsc --pretty --noEmit", + "format:write": "prettier --write \"{src/**/*.{ts,tsx,md},__tests__/**/*.{ts,tsx,md}}\"", + "format:check": "prettier --check \"{src/**/*.{ts,tsx,md},__tests__/**/*.{ts,tsx,md}}\"" + }, + "keywords": [ + "Supabase", + "Storage", + "Cache", + "Tanstack Query", + "Vue Query" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/psteinroe/supabase-cache-helpers.git", + "directory": "packages/storage-vue-query" + }, + "peerDependencies": { + "@supabase/storage-js": "^2.5.5", + "@tanstack/vue-query": "^5.38.0", + "vue": "^3.4.27" + }, + "jest": { + "preset": "@supabase-cache-helpers/jest-presets/jest/node" + }, + "devDependencies": { + "@supabase-cache-helpers/eslint-config-custom": "workspace:*", + "@supabase-cache-helpers/jest-presets": "workspace:*", + "@supabase-cache-helpers/prettier-config": "workspace:*", + "@supabase-cache-helpers/tsconfig": "workspace:*", + "@supabase/storage-js": "2.5.5", + "@supabase/supabase-js": "2.43.4", + "@testing-library/jest-dom": "6.4.5", + "@testing-library/vue": "8.1.0", + "@types/jest": "29.5.12", + "dotenv": "16.4.5", + "eslint": "8.54.0", + "eslint-plugin-vue": "^9.26.0", + "jest": "29.7.0", + "jest-environment-jsdom": "29.7.0", + "ts-jest": "29.1.3", + "tsup": "8.0.2", + "typescript": "5.4.5", + "vue": "3.4.27" + }, + "dependencies": { + "@supabase-cache-helpers/storage-core": "workspace:*" + } +} diff --git a/packages/storage-vue-query/prettier.config.cjs b/packages/storage-vue-query/prettier.config.cjs new file mode 100644 index 00000000..3fb75475 --- /dev/null +++ b/packages/storage-vue-query/prettier.config.cjs @@ -0,0 +1 @@ +module.exports = require("@supabase-cache-helpers/prettier-config/prettier.config.js"); diff --git a/packages/storage-vue-query/src/index.ts b/packages/storage-vue-query/src/index.ts new file mode 100644 index 00000000..55e3ffb8 --- /dev/null +++ b/packages/storage-vue-query/src/index.ts @@ -0,0 +1,3 @@ +export * from './lib'; +export * from './mutate'; +export * from './query'; diff --git a/packages/storage-vue-query/src/lib/constants.ts b/packages/storage-vue-query/src/lib/constants.ts new file mode 100644 index 00000000..1bcb3320 --- /dev/null +++ b/packages/storage-vue-query/src/lib/constants.ts @@ -0,0 +1,2 @@ +export const KEY_PREFIX = 'storage'; +export const KEY_SEPARATOR = '$'; diff --git a/packages/storage-vue-query/src/lib/decode.ts b/packages/storage-vue-query/src/lib/decode.ts new file mode 100644 index 00000000..3ad74e9e --- /dev/null +++ b/packages/storage-vue-query/src/lib/decode.ts @@ -0,0 +1,12 @@ +import { DecodedStorageKey } from '@supabase-cache-helpers/storage-core'; +import { QueryKey } from '@tanstack/vue-query'; + +import { KEY_PREFIX } from './constants'; + +export const decode = (key: QueryKey): DecodedStorageKey | null => { + if (!Array.isArray(key) || key.length !== 3 || key[0] !== KEY_PREFIX) { + return null; + } + const [_, bucketId, path] = key; + return { bucketId, path }; +}; diff --git a/packages/storage-vue-query/src/lib/encode.ts b/packages/storage-vue-query/src/lib/encode.ts new file mode 100644 index 00000000..c13f3712 --- /dev/null +++ b/packages/storage-vue-query/src/lib/encode.ts @@ -0,0 +1,10 @@ +import { QueryKey } from '@tanstack/vue-query'; + +import { KEY_PREFIX } from './constants'; +import { getBucketId } from './get-bucket-id'; +import { assertStorageKeyInput } from './key'; + +export const encode = (key: QueryKey): string[] => { + const [fileApi, path] = assertStorageKeyInput(key); + return [KEY_PREFIX, getBucketId(fileApi), path]; +}; diff --git a/packages/storage-vue-query/src/lib/get-bucket-id.ts b/packages/storage-vue-query/src/lib/get-bucket-id.ts new file mode 100644 index 00000000..ccc1157a --- /dev/null +++ b/packages/storage-vue-query/src/lib/get-bucket-id.ts @@ -0,0 +1,4 @@ +import { StorageFileApi } from './types'; + +export const getBucketId = (fileApi: StorageFileApi) => + fileApi['bucketId'] as string; diff --git a/packages/storage-vue-query/src/lib/index.ts b/packages/storage-vue-query/src/lib/index.ts new file mode 100644 index 00000000..510a94af --- /dev/null +++ b/packages/storage-vue-query/src/lib/index.ts @@ -0,0 +1,7 @@ +export * from './constants'; +export * from './decode'; +export * from './encode'; +export * from './get-bucket-id'; +export * from './key'; +export * from './truthy'; +export * from './types'; diff --git a/packages/storage-vue-query/src/lib/key.ts b/packages/storage-vue-query/src/lib/key.ts new file mode 100644 index 00000000..6640dce9 --- /dev/null +++ b/packages/storage-vue-query/src/lib/key.ts @@ -0,0 +1,16 @@ +import { QueryKey } from '@tanstack/vue-query'; + +import { StorageFileApi } from './types'; + +export const isStorageKeyInput = (key: QueryKey): key is StorageKeyInput => + Array.isArray(key) && + key.length === 2 && + typeof key[1] === 'string' && + Boolean(key[0]['bucketId']); + +export const assertStorageKeyInput = (key: QueryKey): StorageKeyInput => { + if (!isStorageKeyInput(key)) throw new Error('Invalid key'); + return key; +}; + +export type StorageKeyInput = [StorageFileApi, string]; diff --git a/packages/storage-vue-query/src/lib/truthy.ts b/packages/storage-vue-query/src/lib/truthy.ts new file mode 100644 index 00000000..c2eabd7e --- /dev/null +++ b/packages/storage-vue-query/src/lib/truthy.ts @@ -0,0 +1,5 @@ +type Truthy = T extends false | '' | 0 | null | undefined ? never : T; // from lodash + +export function truthy(value: T): value is Truthy { + return !!value; +} diff --git a/packages/storage-vue-query/src/lib/types.ts b/packages/storage-vue-query/src/lib/types.ts new file mode 100644 index 00000000..fc88d47a --- /dev/null +++ b/packages/storage-vue-query/src/lib/types.ts @@ -0,0 +1,3 @@ +import { SupabaseClient } from '@supabase/supabase-js'; + +export type StorageFileApi = ReturnType; diff --git a/packages/storage-vue-query/src/mutate/index.ts b/packages/storage-vue-query/src/mutate/index.ts new file mode 100644 index 00000000..37f5c85a --- /dev/null +++ b/packages/storage-vue-query/src/mutate/index.ts @@ -0,0 +1,3 @@ +export * from './use-remove-directory'; +export * from './use-remove-files'; +export * from './use-upload'; diff --git a/packages/storage-vue-query/src/mutate/use-remove-directory.ts b/packages/storage-vue-query/src/mutate/use-remove-directory.ts new file mode 100644 index 00000000..74ce8a02 --- /dev/null +++ b/packages/storage-vue-query/src/mutate/use-remove-directory.ts @@ -0,0 +1,51 @@ +import { FileObject, StorageError } from '@supabase/storage-js'; +import { + createRemoveDirectoryFetcher, + mutatePaths, +} from '@supabase-cache-helpers/storage-core'; +import { + QueryKey, + useMutation, + UseMutationOptions, + UseMutationResult, + useQueryClient, +} from '@tanstack/react-query'; +import { useCallback } from 'react'; + +import { decode, getBucketId, StorageFileApi } from '../lib'; + +/** + * A hook that provides a mutation function to remove a directory and all its contents. + * @param fileApi The `StorageFileApi` instance to use for the removal. + * @param config Optional configuration options for the React Query mutation. + * @returns An object containing the mutation function, loading state, and error state. + */ +function useRemoveDirectory( + fileApi: StorageFileApi, + config?: Omit< + UseMutationOptions, + 'mutationFn' + >, +): UseMutationResult { + const queryClient = useQueryClient(); + const fetcher = useCallback(createRemoveDirectoryFetcher(fileApi), [fileApi]); + return useMutation({ + mutationFn: async (arg) => { + const result = fetcher(arg); + await mutatePaths(getBucketId(fileApi), [arg], { + cacheKeys: queryClient + .getQueryCache() + .getAll() + .map((c) => c.queryKey), + decode, + mutate: async (key) => { + await queryClient.invalidateQueries({ queryKey: key }); + }, + }); + return result; + }, + ...config, + }); +} + +export { useRemoveDirectory }; diff --git a/packages/storage-vue-query/src/mutate/use-remove-files.ts b/packages/storage-vue-query/src/mutate/use-remove-files.ts new file mode 100644 index 00000000..fb0820cd --- /dev/null +++ b/packages/storage-vue-query/src/mutate/use-remove-files.ts @@ -0,0 +1,51 @@ +import { FileObject, StorageError } from '@supabase/storage-js'; +import { + createRemoveFilesFetcher, + mutatePaths, +} from '@supabase-cache-helpers/storage-core'; +import { + QueryKey, + useMutation, + UseMutationOptions, + UseMutationResult, + useQueryClient, +} from '@tanstack/react-query'; +import { useCallback } from 'react'; + +import { decode, getBucketId, StorageFileApi } from '../lib'; + +/** + * Hook for removing files from storage using React Query mutation + * @param {StorageFileApi} fileApi - The Supabase Storage API + * @param {UseMutationOptions} [config] - The React Query mutation configuration + * @returns {UseMutationOptions} - The React Query mutation response object + */ +function useRemoveFiles( + fileApi: StorageFileApi, + config?: Omit< + UseMutationOptions, + 'mutationFn' + >, +): UseMutationResult { + const queryClient = useQueryClient(); + const fetcher = useCallback(createRemoveFilesFetcher(fileApi), [fileApi]); + return useMutation({ + mutationFn: async (paths) => { + const res = await fetcher(paths); + await mutatePaths(getBucketId(fileApi), paths, { + cacheKeys: queryClient + .getQueryCache() + .getAll() + .map((c) => c.queryKey), + decode, + mutate: async (key) => { + await queryClient.invalidateQueries({ queryKey: key }); + }, + }); + return res; + }, + ...config, + }); +} + +export { useRemoveFiles }; diff --git a/packages/storage-vue-query/src/mutate/use-upload.ts b/packages/storage-vue-query/src/mutate/use-upload.ts new file mode 100644 index 00000000..6f54208d --- /dev/null +++ b/packages/storage-vue-query/src/mutate/use-upload.ts @@ -0,0 +1,69 @@ +import { FileObject, StorageError } from '@supabase/storage-js'; +import { + FileInput, + createUploadFetcher, + UploadFetcherConfig, + UploadFileResponse, + mutatePaths, +} from '@supabase-cache-helpers/storage-core'; +import { + useMutation, + UseMutationOptions, + UseMutationReturnType, + useQueryClient, +} from '@tanstack/vue-query'; + +import { decode, getBucketId, StorageFileApi, truthy } from '../lib'; + +export type { UploadFetcherConfig, UploadFileResponse, FileInput }; + +export type UseUploadInput = { + files: FileList | (File | FileInput)[]; + path?: string; +}; + +/** + * Hook for uploading files to storage using React Query mutation + * @param {StorageFileApi} fileApi - The Supabase Storage API + * @param {UploadFetcherConfig & UseMutationOptions} [config] - The React Query mutation configuration + * @returns {UseMutationResult} - The React Query mutation response object + */ +function useUpload( + fileApi: StorageFileApi, + config?: UploadFetcherConfig & + Omit< + UseMutationOptions, + 'mutationFn' + >, +): UseMutationReturnType< + UploadFileResponse[], + StorageError, + UseUploadInput, + unknown +> { + const queryClient = useQueryClient(); + const fetcher = createUploadFetcher(fileApi, config); + return useMutation({ + mutationFn: async ({ files, path }) => { + const result = await fetcher(files, path); + await mutatePaths( + getBucketId(fileApi), + result.map(({ data }) => data?.path).filter(truthy), + { + cacheKeys: queryClient + .getQueryCache() + .getAll() + .map((c) => c.queryKey), + decode, + mutate: async (key) => { + await queryClient.invalidateQueries({ queryKey: key }); + }, + }, + ); + return result; + }, + ...config, + }); +} + +export { useUpload }; diff --git a/packages/storage-vue-query/src/query/index.ts b/packages/storage-vue-query/src/query/index.ts new file mode 100644 index 00000000..4717db82 --- /dev/null +++ b/packages/storage-vue-query/src/query/index.ts @@ -0,0 +1,3 @@ +export * from './use-directory-urls'; +export * from './use-directory'; +export * from './use-file-url'; diff --git a/packages/storage-vue-query/src/query/use-directory-urls.ts b/packages/storage-vue-query/src/query/use-directory-urls.ts new file mode 100644 index 00000000..690b9df5 --- /dev/null +++ b/packages/storage-vue-query/src/query/use-directory-urls.ts @@ -0,0 +1,70 @@ +import { FileObject, StorageError } from '@supabase/storage-js'; +import { + createDirectoryUrlsFetcher, + StoragePrivacy, + URLFetcherConfig, +} from '@supabase-cache-helpers/storage-core'; +import { + useQuery as useVueQuery, + UseQueryReturnType as UseVueQueryResult, + UseQueryOptions as UseVueQueryOptions, +} from '@tanstack/vue-query'; + +import { encode, StorageFileApi } from '../lib'; + +function buildDirectoryUrlsQueryOpts( + fileApi: StorageFileApi, + path: string, + mode: StoragePrivacy, + config?: Omit< + UseVueQueryOptions< + (FileObject & { url: string })[] | undefined, + StorageError + >, + 'queryKey' | 'queryFn' + > & + Pick, +): UseVueQueryOptions< + (FileObject & { url: string })[] | undefined, + StorageError +> { + return { + queryKey: encode([fileApi, path]), + queryFn: () => createDirectoryUrlsFetcher(mode, config)(fileApi, path), + ...config, + }; +} + +/** + * Convenience hook to fetch all files in a directory, and their corresponding URLs, from Supabase Storage using Vue Query. + * + * @param {StorageFileApi} fileApi - The file API of the storage bucket. + * @param {string|null} path - The path of the directory to fetch files from. + * @param {StoragePrivacy} mode - The privacy mode of the bucket to fetch files from. + * @param {UseQueryOptions & Pick} [config] - Optional SWR configuration and `expiresIn` value to pass to the `createDirectoryUrlsFetcher` function. + * + * @returns {UseQueryResult<(FileObject & { url: string })[] | undefined, StorageError>} A Vue Query response containing an array of file objects with their corresponding URLs. + */ +function useDirectoryFileUrls( + fileApi: StorageFileApi, + path: string, + mode: StoragePrivacy, + config?: Omit< + UseVueQueryOptions< + (FileObject & { url: string })[] | undefined, + StorageError + >, + 'queryKey' | 'queryFn' + > & + Pick, +): UseVueQueryResult< + (FileObject & { url: string })[] | undefined, + StorageError +> { + return useVueQuery< + (FileObject & { url: string })[] | undefined, + StorageError + >(buildDirectoryUrlsQueryOpts(fileApi, path, mode, config)); +} + +export { useDirectoryFileUrls, buildDirectoryUrlsQueryOpts }; diff --git a/packages/storage-vue-query/src/query/use-directory.ts b/packages/storage-vue-query/src/query/use-directory.ts new file mode 100644 index 00000000..1285bb83 --- /dev/null +++ b/packages/storage-vue-query/src/query/use-directory.ts @@ -0,0 +1,45 @@ +import { FileObject, StorageError } from '@supabase/storage-js'; +import { fetchDirectory } from '@supabase-cache-helpers/storage-core'; +import { + useQuery as useVueQuery, + UseQueryReturnType as UseVueQueryResult, + UseQueryOptions as UseVueQueryOptions, +} from '@tanstack/vue-query'; + +import { StorageFileApi, encode } from '../lib'; + +function buildDirectoryQueryOpts( + fileApi: StorageFileApi, + path: string, + config?: Omit< + UseVueQueryOptions, + 'queryKey' | 'queryFn' + >, +): UseVueQueryOptions { + return { + queryKey: encode([fileApi, path]), + queryFn: () => fetchDirectory(fileApi, path), + ...config, + }; +} + +/** + * Convenience hook to fetch a directory from Supabase Storage using Vue Query. + * + * @param fileApi The StorageFileApi instance. + * @param path The path to the directory. + * @param config The Vue Query configuration. + * @returns An UseQueryResult containing an array of FileObjects + */ +function useDirectory( + fileApi: StorageFileApi, + path: string, + config?: Omit< + UseVueQueryOptions, + 'queryKey' | 'queryFn' + >, +): UseVueQueryResult { + return useVueQuery(buildDirectoryQueryOpts(fileApi, path, config)); +} + +export { useDirectory }; diff --git a/packages/storage-vue-query/src/query/use-file-url.ts b/packages/storage-vue-query/src/query/use-file-url.ts new file mode 100644 index 00000000..7bcb2b26 --- /dev/null +++ b/packages/storage-vue-query/src/query/use-file-url.ts @@ -0,0 +1,56 @@ +import { StorageError } from '@supabase/storage-js'; +import { + StoragePrivacy, + createUrlFetcher, + URLFetcherConfig, +} from '@supabase-cache-helpers/storage-core'; +import { + useQuery as useVueQuery, + UseQueryReturnType as UseVueQueryResult, + UseQueryOptions as UseVueQueryOptions, +} from '@tanstack/vue-query'; + +import { StorageFileApi, encode } from '../lib'; + +function buildFileUrlQueryOpts( + fileApi: StorageFileApi, + path: string, + mode: StoragePrivacy, + config?: Omit< + UseVueQueryOptions, + 'queryKey' | 'queryFn' + > & + URLFetcherConfig, +): UseVueQueryOptions { + return { + queryKey: encode([fileApi, path]), + queryFn: () => createUrlFetcher(mode, config)(fileApi, path), + ...config, + }; +} + +/** + * A hook to fetch the URL for a file in the Storage. + * + * @param fileApi - the file API instance from the Supabase client. + * @param path - the path of the file to fetch the URL for. + * @param mode - the privacy mode of the bucket the file is in. + * @param config - the Vue Query configuration options and URL fetcher configuration. + * @returns the Vue Query response for the URL of the file + */ +function useFileUrl( + fileApi: StorageFileApi, + path: string, + mode: StoragePrivacy, + config?: Omit< + UseVueQueryOptions, + 'queryKey' | 'queryFn' + > & + URLFetcherConfig, +): UseVueQueryResult { + return useVueQuery( + buildFileUrlQueryOpts(fileApi, path, mode, config), + ); +} + +export { useFileUrl }; diff --git a/packages/storage-vue-query/tsconfig.json b/packages/storage-vue-query/tsconfig.json new file mode 100644 index 00000000..c5f94dfa --- /dev/null +++ b/packages/storage-vue-query/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "@supabase-cache-helpers/tsconfig/web.json", + "include": ["**/*.ts"], + "exclude": ["node_modules"], + "compilerOptions": { + "noErrorTruncation": true + } +} diff --git a/packages/storage-vue-query/tsup.config.ts b/packages/storage-vue-query/tsup.config.ts new file mode 100644 index 00000000..7cfe8a38 --- /dev/null +++ b/packages/storage-vue-query/tsup.config.ts @@ -0,0 +1,15 @@ +import type { Options } from 'tsup'; + +export const tsup: Options = { + dts: true, + entryPoints: ['src/index.ts'], + external: ['vue', /^@supabase\//], + format: ['cjs', 'esm'], + // inject: ['src/react-shim.js'], + // ! .cjs/.mjs doesn't work with Angular's webpack4 config by default! + legacyOutput: false, + sourcemap: true, + splitting: false, + bundle: true, + clean: true, +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e6e42f88..a2fa519b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -34,7 +34,7 @@ importers: version: 18.2.0 '@types/react-dom': specifier: ^18.0.7 - version: 18.3.0 + version: 18.2.19 '@vercel/analytics': specifier: ^1.0.0 version: 1.2.2(next@14.2.0)(react@18.2.0) @@ -240,7 +240,7 @@ importers: version: 18.2.0 '@types/react-dom': specifier: ^18.0.7 - version: 18.3.0 + version: 18.2.19 '@types/uuid': specifier: ^9.0.2 version: 9.0.2 @@ -445,7 +445,7 @@ importers: version: 18.0.28 '@types/react-dom': specifier: ^18.0.7 - version: 18.3.0 + version: 18.2.19 '@types/uuid': specifier: ^9.0.2 version: 9.0.2 @@ -503,7 +503,7 @@ importers: dependencies: ts-jest: specifier: 29.1.0 - version: 29.1.0(@babel/core@7.24.4)(esbuild@0.19.8)(jest@29.7.0)(typescript@5.4.2) + version: 29.1.0(jest@29.7.0)(typescript@5.4.5) packages/postgrest-core: dependencies: @@ -558,7 +558,7 @@ importers: version: 29.7.0 ts-jest: specifier: 29.1.0 - version: 29.1.0(@babel/core@7.24.4)(esbuild@0.19.8)(jest@29.7.0)(typescript@5.4.2) + version: 29.1.0(@babel/core@7.23.5)(esbuild@0.19.8)(jest@29.7.0)(typescript@5.4.2) tsup: specifier: 8.0.0 version: 8.0.0(typescript@5.4.2) @@ -631,7 +631,7 @@ importers: version: 18.2.0(react@18.2.0) ts-jest: specifier: 29.1.0 - version: 29.1.0(@babel/core@7.24.4)(esbuild@0.19.8)(jest@29.7.0)(typescript@5.4.2) + version: 29.1.0(@babel/core@7.24.0)(esbuild@0.19.8)(jest@29.7.0)(typescript@5.4.2) tsup: specifier: 8.0.0 version: 8.0.0(typescript@5.4.2) @@ -707,7 +707,7 @@ importers: version: 18.2.0(react@18.2.0) ts-jest: specifier: 29.1.0 - version: 29.1.0(@babel/core@7.24.4)(esbuild@0.19.8)(jest@29.7.0)(typescript@5.4.2) + version: 29.1.0(@babel/core@7.24.0)(esbuild@0.19.8)(jest@29.7.0)(typescript@5.4.2) tsup: specifier: 8.0.0 version: 8.0.0(typescript@5.4.2) @@ -715,6 +715,76 @@ importers: specifier: 5.4.2 version: 5.4.2 + packages/postgrest-vue-query: + dependencies: + '@supabase-cache-helpers/postgrest-core': + specifier: workspace:* + version: link:../postgrest-core + '@tanstack/vue-query': + specifier: ^5.38.0 + version: 5.38.0(vue@3.4.27) + flat: + specifier: 5.0.2 + version: 5.0.2 + devDependencies: + '@supabase-cache-helpers/eslint-config-custom': + specifier: workspace:* + version: link:../eslint-config-custom + '@supabase-cache-helpers/jest-presets': + specifier: workspace:* + version: link:../jest-presets + '@supabase-cache-helpers/prettier-config': + specifier: workspace:* + version: link:../prettier-config + '@supabase-cache-helpers/tsconfig': + specifier: workspace:* + version: link:../tsconfig + '@supabase/postgrest-js': + specifier: 1.15.4 + version: 1.15.4 + '@supabase/supabase-js': + specifier: 2.43.4 + version: 2.43.4 + '@testing-library/jest-dom': + specifier: 6.4.5 + version: 6.4.5(@types/jest@29.5.12)(jest@29.7.0) + '@testing-library/vue': + specifier: 8.1.0 + version: 8.1.0(vue@3.4.27) + '@types/flat': + specifier: 5.0.5 + version: 5.0.5 + '@types/jest': + specifier: 29.5.12 + version: 29.5.12 + dotenv: + specifier: 16.4.5 + version: 16.4.5 + eslint: + specifier: 8.54.0 + version: 8.54.0 + eslint-plugin-vue: + specifier: ^9.26.0 + version: 9.26.0(eslint@8.54.0) + jest: + specifier: 29.7.0 + version: 29.7.0 + jest-environment-jsdom: + specifier: 29.7.0 + version: 29.7.0 + ts-jest: + specifier: 29.1.3 + version: 29.1.3(@babel/core@7.24.0)(esbuild@0.19.8)(jest@29.7.0)(typescript@5.4.5) + tsup: + specifier: 8.0.2 + version: 8.0.2(typescript@5.4.5) + typescript: + specifier: 5.4.5 + version: 5.4.5 + vue: + specifier: 3.4.27 + version: 3.4.27(typescript@5.4.5) + packages/prettier-config: devDependencies: prettier: @@ -755,7 +825,7 @@ importers: version: 29.7.0 ts-jest: specifier: 29.1.0 - version: 29.1.0(@babel/core@7.24.4)(esbuild@0.19.8)(jest@29.7.0)(typescript@5.4.2) + version: 29.1.0(@babel/core@7.24.0)(esbuild@0.19.8)(jest@29.7.0)(typescript@5.4.2) tsup: specifier: 8.0.0 version: 8.0.0(typescript@5.4.2) @@ -822,7 +892,7 @@ importers: version: 18.2.0(react@18.2.0) ts-jest: specifier: 29.1.0 - version: 29.1.0(@babel/core@7.24.4)(esbuild@0.19.8)(jest@29.7.0)(typescript@5.4.2) + version: 29.1.0(@babel/core@7.24.0)(esbuild@0.19.8)(jest@29.7.0)(typescript@5.4.2) tsup: specifier: 8.0.0 version: 8.0.0(typescript@5.4.2) @@ -889,7 +959,7 @@ importers: version: 18.2.0(react@18.2.0) ts-jest: specifier: 29.1.0 - version: 29.1.0(@babel/core@7.24.4)(esbuild@0.19.8)(jest@29.7.0)(typescript@5.4.2) + version: 29.1.0(@babel/core@7.24.0)(esbuild@0.19.8)(jest@29.7.0)(typescript@5.4.2) tsup: specifier: 8.0.0 version: 8.0.0(typescript@5.4.2) @@ -897,6 +967,70 @@ importers: specifier: 5.4.2 version: 5.4.2 + packages/storage-vue-query: + dependencies: + '@supabase-cache-helpers/storage-core': + specifier: workspace:* + version: link:../storage-core + '@tanstack/vue-query': + specifier: ^5.38.0 + version: 5.38.0(vue@3.4.27) + devDependencies: + '@supabase-cache-helpers/eslint-config-custom': + specifier: workspace:* + version: link:../eslint-config-custom + '@supabase-cache-helpers/jest-presets': + specifier: workspace:* + version: link:../jest-presets + '@supabase-cache-helpers/prettier-config': + specifier: workspace:* + version: link:../prettier-config + '@supabase-cache-helpers/tsconfig': + specifier: workspace:* + version: link:../tsconfig + '@supabase/storage-js': + specifier: 2.5.5 + version: 2.5.5 + '@supabase/supabase-js': + specifier: 2.43.4 + version: 2.43.4 + '@testing-library/jest-dom': + specifier: 6.4.5 + version: 6.4.5(@types/jest@29.5.12)(jest@29.7.0) + '@testing-library/vue': + specifier: 8.1.0 + version: 8.1.0(vue@3.4.27) + '@types/jest': + specifier: 29.5.12 + version: 29.5.12 + dotenv: + specifier: 16.4.5 + version: 16.4.5 + eslint: + specifier: 8.54.0 + version: 8.54.0 + eslint-plugin-vue: + specifier: ^9.26.0 + version: 9.26.0(eslint@8.54.0) + jest: + specifier: 29.7.0 + version: 29.7.0 + jest-environment-jsdom: + specifier: 29.7.0 + version: 29.7.0 + ts-jest: + specifier: 29.1.3 + version: 29.1.3(@babel/core@7.24.0)(esbuild@0.19.8)(jest@29.7.0)(typescript@5.4.5) + tsup: + specifier: 8.0.2 + version: 8.0.2(typescript@5.4.5) + typescript: + specifier: 5.4.5 + version: 5.4.5 + vue: + specifier: 3.4.27 + version: 3.4.27(typescript@5.4.5) + packages/tsconfig: {} packages: @@ -934,26 +1068,26 @@ packages: '@jridgewell/gen-mapping': 0.3.5 '@jridgewell/trace-mapping': 0.3.25 - /@babel/code-frame@7.23.5: - resolution: {integrity: sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==} + /@babel/code-frame@7.22.13: + resolution: {integrity: sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==} engines: {node: '>=6.9.0'} dependencies: '@babel/highlight': 7.23.4 chalk: 2.4.2 - /@babel/code-frame@7.24.2: - resolution: {integrity: sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==} + /@babel/code-frame@7.23.5: + resolution: {integrity: sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/highlight': 7.24.2 - picocolors: 1.0.0 + '@babel/highlight': 7.23.4 + chalk: 2.4.2 /@babel/compat-data@7.23.2: resolution: {integrity: sha512-0S9TQMmDHlqAZ2ITT95irXKfxN9bncq8ZCoJhun3nHL/lLUxd2NKBJYoNGWH7S0hz6fRQwWlAWn/ILM0C70KZQ==} engines: {node: '>=6.9.0'} - /@babel/compat-data@7.24.4: - resolution: {integrity: sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==} + /@babel/compat-data@7.23.5: + resolution: {integrity: sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==} engines: {node: '>=6.9.0'} /@babel/core@7.23.2: @@ -961,7 +1095,7 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@ampproject/remapping': 2.2.0 - '@babel/code-frame': 7.23.5 + '@babel/code-frame': 7.22.13 '@babel/generator': 7.23.0 '@babel/helper-compilation-targets': 7.22.15 '@babel/helper-module-transforms': 7.23.0(@babel/core@7.23.2) @@ -1000,19 +1134,19 @@ packages: transitivePeerDependencies: - supports-color - /@babel/core@7.24.4: - resolution: {integrity: sha512-MBVlMXP+kkl5394RBLSxxk/iLTeVGuXTV3cIDXavPpMMqnSnt6apKgan/U8O3USWZCWZT/TbgfEpKa4uMgN4Dg==} + /@babel/core@7.24.0: + resolution: {integrity: sha512-fQfkg0Gjkza3nf0c7/w6Xf34BW4YvzNfACRLmmb7XRLa6XHdR+K9AlJlxneFfWYf6uhOzuzZVTjF/8KfndZANw==} engines: {node: '>=6.9.0'} dependencies: '@ampproject/remapping': 2.3.0 - '@babel/code-frame': 7.24.2 - '@babel/generator': 7.24.4 + '@babel/code-frame': 7.23.5 + '@babel/generator': 7.23.6 '@babel/helper-compilation-targets': 7.23.6 - '@babel/helper-module-transforms': 7.23.3(@babel/core@7.24.4) - '@babel/helpers': 7.24.4 - '@babel/parser': 7.24.4 + '@babel/helper-module-transforms': 7.23.3(@babel/core@7.24.0) + '@babel/helpers': 7.24.0 + '@babel/parser': 7.24.0 '@babel/template': 7.24.0 - '@babel/traverse': 7.24.1 + '@babel/traverse': 7.24.0 '@babel/types': 7.24.0 convert-source-map: 2.0.0 debug: 4.3.4 @@ -1040,8 +1174,8 @@ packages: '@jridgewell/trace-mapping': 0.3.20 jsesc: 2.5.2 - /@babel/generator@7.24.4: - resolution: {integrity: sha512-Xd6+v6SnjWVx/nus+y0l1sxMOTOMBkyL4+BIdbALyatQnAe/SRVjANeDPSCYaX+i1iJmuGSKf3Z+E+V/va1Hvw==} + /@babel/generator@7.23.6: + resolution: {integrity: sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==} engines: {node: '>=6.9.0'} dependencies: '@babel/types': 7.24.0 @@ -1063,7 +1197,7 @@ packages: resolution: {integrity: sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==} engines: {node: '>=6.9.0'} dependencies: - '@babel/compat-data': 7.24.4 + '@babel/compat-data': 7.23.5 '@babel/helper-validator-option': 7.23.5 browserslist: 4.23.0 lru-cache: 5.1.1 @@ -1118,13 +1252,13 @@ packages: '@babel/helper-split-export-declaration': 7.22.6 '@babel/helper-validator-identifier': 7.22.20 - /@babel/helper-module-transforms@7.23.3(@babel/core@7.24.4): + /@babel/helper-module-transforms@7.23.3(@babel/core@7.24.0): resolution: {integrity: sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.0 '@babel/helper-environment-visitor': 7.22.20 '@babel/helper-module-imports': 7.22.15 '@babel/helper-simple-access': 7.22.5 @@ -1159,10 +1293,6 @@ packages: resolution: {integrity: sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==} engines: {node: '>=6.9.0'} - /@babel/helper-string-parser@7.24.1: - resolution: {integrity: sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==} - engines: {node: '>=6.9.0'} - /@babel/helper-validator-identifier@7.22.20: resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==} engines: {node: '>=6.9.0'} @@ -1195,12 +1325,12 @@ packages: transitivePeerDependencies: - supports-color - /@babel/helpers@7.24.4: - resolution: {integrity: sha512-FewdlZbSiwaVGlgT1DPANDuCHaDMiOo+D/IDYRFYjHOuv66xMSJ7fQwwODwRNAPkADIO/z1EoF/l2BCWlWABDw==} + /@babel/helpers@7.24.0: + resolution: {integrity: sha512-ulDZdc0Aj5uLc5nETsa7EPx2L7rM0YJM8r7ck7U73AXi7qOV44IHHRAYZHY6iU1rr3C5N4NtTmMRUJP6kwCWeA==} engines: {node: '>=6.9.0'} dependencies: '@babel/template': 7.24.0 - '@babel/traverse': 7.24.1 + '@babel/traverse': 7.24.0 '@babel/types': 7.24.0 transitivePeerDependencies: - supports-color @@ -1213,15 +1343,6 @@ packages: chalk: 2.4.2 js-tokens: 4.0.0 - /@babel/highlight@7.24.2: - resolution: {integrity: sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-validator-identifier': 7.22.20 - chalk: 2.4.2 - js-tokens: 4.0.0 - picocolors: 1.0.0 - /@babel/parser@7.23.0: resolution: {integrity: sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==} engines: {node: '>=6.0.0'} @@ -1236,8 +1357,15 @@ packages: dependencies: '@babel/types': 7.23.5 - /@babel/parser@7.24.4: - resolution: {integrity: sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg==} + /@babel/parser@7.24.0: + resolution: {integrity: sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg==} + engines: {node: '>=6.0.0'} + hasBin: true + dependencies: + '@babel/types': 7.24.0 + + /@babel/parser@7.24.6: + resolution: {integrity: sha512-eNZXdfU35nJC2h24RznROuOpO94h6x8sg9ju0tT9biNtLZ2vuP8SduLqqV+/8+cebSLV9SJEAN5Z3zQbJG/M+Q==} engines: {node: '>=6.0.0'} hasBin: true dependencies: @@ -1251,6 +1379,14 @@ packages: '@babel/core': 7.23.5 '@babel/helper-plugin-utils': 7.18.9 + /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.24.0): + resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.0 + '@babel/helper-plugin-utils': 7.18.9 + /@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.23.5): resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} peerDependencies: @@ -1259,6 +1395,14 @@ packages: '@babel/core': 7.23.5 '@babel/helper-plugin-utils': 7.18.9 + /@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.24.0): + resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.0 + '@babel/helper-plugin-utils': 7.18.9 + /@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.23.5): resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} peerDependencies: @@ -1267,6 +1411,14 @@ packages: '@babel/core': 7.23.5 '@babel/helper-plugin-utils': 7.18.9 + /@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.24.0): + resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.0 + '@babel/helper-plugin-utils': 7.18.9 + /@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.23.5): resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} peerDependencies: @@ -1275,6 +1427,14 @@ packages: '@babel/core': 7.23.5 '@babel/helper-plugin-utils': 7.18.9 + /@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.24.0): + resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.0 + '@babel/helper-plugin-utils': 7.18.9 + /@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.23.5): resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} peerDependencies: @@ -1283,6 +1443,14 @@ packages: '@babel/core': 7.23.5 '@babel/helper-plugin-utils': 7.18.9 + /@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.24.0): + resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.0 + '@babel/helper-plugin-utils': 7.18.9 + /@babel/plugin-syntax-jsx@7.23.3(@babel/core@7.23.5): resolution: {integrity: sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==} engines: {node: '>=6.9.0'} @@ -1300,6 +1468,14 @@ packages: '@babel/core': 7.23.5 '@babel/helper-plugin-utils': 7.18.9 + /@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.24.0): + resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.0 + '@babel/helper-plugin-utils': 7.18.9 + /@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.23.5): resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} peerDependencies: @@ -1308,6 +1484,14 @@ packages: '@babel/core': 7.23.5 '@babel/helper-plugin-utils': 7.18.9 + /@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.24.0): + resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.0 + '@babel/helper-plugin-utils': 7.18.9 + /@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.23.5): resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} peerDependencies: @@ -1316,6 +1500,14 @@ packages: '@babel/core': 7.23.5 '@babel/helper-plugin-utils': 7.18.9 + /@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.24.0): + resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.0 + '@babel/helper-plugin-utils': 7.18.9 + /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.23.5): resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} peerDependencies: @@ -1324,6 +1516,14 @@ packages: '@babel/core': 7.23.5 '@babel/helper-plugin-utils': 7.18.9 + /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.24.0): + resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.0 + '@babel/helper-plugin-utils': 7.18.9 + /@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.23.5): resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} peerDependencies: @@ -1332,6 +1532,14 @@ packages: '@babel/core': 7.23.5 '@babel/helper-plugin-utils': 7.18.9 + /@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.24.0): + resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.0 + '@babel/helper-plugin-utils': 7.18.9 + /@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.23.5): resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} peerDependencies: @@ -1340,6 +1548,14 @@ packages: '@babel/core': 7.23.5 '@babel/helper-plugin-utils': 7.18.9 + /@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.24.0): + resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.0 + '@babel/helper-plugin-utils': 7.18.9 + /@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.23.5): resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} engines: {node: '>=6.9.0'} @@ -1349,6 +1565,15 @@ packages: '@babel/core': 7.23.5 '@babel/helper-plugin-utils': 7.18.9 + /@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.24.0): + resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.0 + '@babel/helper-plugin-utils': 7.18.9 + /@babel/plugin-syntax-typescript@7.23.3(@babel/core@7.23.5): resolution: {integrity: sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ==} engines: {node: '>=6.9.0'} @@ -1398,15 +1623,15 @@ packages: resolution: {integrity: sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/code-frame': 7.24.2 - '@babel/parser': 7.24.4 + '@babel/code-frame': 7.23.5 + '@babel/parser': 7.24.0 '@babel/types': 7.24.0 /@babel/traverse@7.23.2: resolution: {integrity: sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/code-frame': 7.23.5 + '@babel/code-frame': 7.22.13 '@babel/generator': 7.23.0 '@babel/helper-environment-visitor': 7.22.20 '@babel/helper-function-name': 7.23.0 @@ -1436,17 +1661,17 @@ packages: transitivePeerDependencies: - supports-color - /@babel/traverse@7.24.1: - resolution: {integrity: sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ==} + /@babel/traverse@7.24.0: + resolution: {integrity: sha512-HfuJlI8qq3dEDmNU5ChzzpZRWq+oxCZQyMzIMEqLho+AQnhMnKQUzH6ydo3RBl/YjPCuk68Y6s0Gx0AeyULiWw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/code-frame': 7.24.2 - '@babel/generator': 7.24.4 + '@babel/code-frame': 7.23.5 + '@babel/generator': 7.23.6 '@babel/helper-environment-visitor': 7.22.20 '@babel/helper-function-name': 7.23.0 '@babel/helper-hoist-variables': 7.22.5 '@babel/helper-split-export-declaration': 7.22.6 - '@babel/parser': 7.24.4 + '@babel/parser': 7.24.0 '@babel/types': 7.24.0 debug: 4.3.4 globals: 11.12.0 @@ -1473,7 +1698,7 @@ packages: resolution: {integrity: sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==} engines: {node: '>=6.9.0'} dependencies: - '@babel/helper-string-parser': 7.24.1 + '@babel/helper-string-parser': 7.23.4 '@babel/helper-validator-identifier': 7.22.20 to-fast-properties: 2.0.0 @@ -1673,6 +1898,7 @@ packages: cpu: [arm64] os: [android] requiresBuild: true + dev: true optional: true /@esbuild/android-arm@0.19.8: @@ -1681,6 +1907,7 @@ packages: cpu: [arm] os: [android] requiresBuild: true + dev: true optional: true /@esbuild/android-x64@0.19.8: @@ -1689,6 +1916,7 @@ packages: cpu: [x64] os: [android] requiresBuild: true + dev: true optional: true /@esbuild/darwin-arm64@0.19.8: @@ -1697,6 +1925,7 @@ packages: cpu: [arm64] os: [darwin] requiresBuild: true + dev: true optional: true /@esbuild/darwin-x64@0.19.8: @@ -1705,6 +1934,7 @@ packages: cpu: [x64] os: [darwin] requiresBuild: true + dev: true optional: true /@esbuild/freebsd-arm64@0.19.8: @@ -1713,6 +1943,7 @@ packages: cpu: [arm64] os: [freebsd] requiresBuild: true + dev: true optional: true /@esbuild/freebsd-x64@0.19.8: @@ -1721,6 +1952,7 @@ packages: cpu: [x64] os: [freebsd] requiresBuild: true + dev: true optional: true /@esbuild/linux-arm64@0.19.8: @@ -1729,6 +1961,7 @@ packages: cpu: [arm64] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-arm@0.19.8: @@ -1737,6 +1970,7 @@ packages: cpu: [arm] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-ia32@0.19.8: @@ -1745,6 +1979,7 @@ packages: cpu: [ia32] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-loong64@0.19.8: @@ -1753,6 +1988,7 @@ packages: cpu: [loong64] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-mips64el@0.19.8: @@ -1761,6 +1997,7 @@ packages: cpu: [mips64el] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-ppc64@0.19.8: @@ -1769,6 +2006,7 @@ packages: cpu: [ppc64] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-riscv64@0.19.8: @@ -1777,6 +2015,7 @@ packages: cpu: [riscv64] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-s390x@0.19.8: @@ -1785,6 +2024,7 @@ packages: cpu: [s390x] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/linux-x64@0.19.8: @@ -1793,6 +2033,7 @@ packages: cpu: [x64] os: [linux] requiresBuild: true + dev: true optional: true /@esbuild/netbsd-x64@0.19.8: @@ -1801,6 +2042,7 @@ packages: cpu: [x64] os: [netbsd] requiresBuild: true + dev: true optional: true /@esbuild/openbsd-x64@0.19.8: @@ -1809,6 +2051,7 @@ packages: cpu: [x64] os: [openbsd] requiresBuild: true + dev: true optional: true /@esbuild/sunos-x64@0.19.8: @@ -1817,6 +2060,7 @@ packages: cpu: [x64] os: [sunos] requiresBuild: true + dev: true optional: true /@esbuild/win32-arm64@0.19.8: @@ -1825,6 +2069,7 @@ packages: cpu: [arm64] os: [win32] requiresBuild: true + dev: true optional: true /@esbuild/win32-ia32@0.19.8: @@ -1833,6 +2078,7 @@ packages: cpu: [ia32] os: [win32] requiresBuild: true + dev: true optional: true /@esbuild/win32-x64@0.19.8: @@ -1841,6 +2087,7 @@ packages: cpu: [x64] os: [win32] requiresBuild: true + dev: true optional: true /@eslint-community/eslint-utils@4.4.0(eslint@8.54.0): @@ -1981,7 +2228,6 @@ packages: strip-ansi-cjs: /strip-ansi@6.0.1 wrap-ansi: 8.1.0 wrap-ansi-cjs: /wrap-ansi@7.0.0 - dev: false /@istanbuljs/load-nyc-config@1.1.0: resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} @@ -2002,7 +2248,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 - '@types/node': 17.0.45 + '@types/node': 20.12.7 chalk: 4.1.2 jest-message-util: 29.7.0 jest-util: 29.7.0 @@ -2022,14 +2268,14 @@ packages: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.10.1 + '@types/node': 20.12.7 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.9.0 exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.10.1) + jest-config: 29.7.0(@types/node@20.12.7) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -2056,7 +2302,7 @@ packages: dependencies: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.10.1 + '@types/node': 20.12.7 jest-mock: 29.7.0 /@jest/expect-utils@29.7.0: @@ -2080,7 +2326,7 @@ packages: dependencies: '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.3.0 - '@types/node': 20.10.1 + '@types/node': 20.12.7 jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -2110,8 +2356,8 @@ packages: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@jridgewell/trace-mapping': 0.3.20 - '@types/node': 17.0.45 + '@jridgewell/trace-mapping': 0.3.25 + '@types/node': 20.12.7 chalk: 4.1.2 collect-v8-coverage: 1.0.1 exit: 0.1.2 @@ -2168,9 +2414,9 @@ packages: resolution: {integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@babel/core': 7.23.2 + '@babel/core': 7.24.0 '@jest/types': 29.6.3 - '@jridgewell/trace-mapping': 0.3.20 + '@jridgewell/trace-mapping': 0.3.25 babel-plugin-istanbul: 6.1.1 chalk: 4.1.2 convert-source-map: 2.0.0 @@ -2193,7 +2439,7 @@ packages: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 20.10.1 + '@types/node': 20.12.7 '@types/yargs': 17.0.32 chalk: 4.1.2 @@ -2646,11 +2892,14 @@ packages: '@nodelib/fs.scandir': 2.1.5 fastq: 1.15.0 + /@one-ini/wasm@0.1.1: + resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==} + dev: true + /@pkgjs/parseargs@0.11.0: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} requiresBuild: true - dev: false optional: true /@pkgr/utils@2.3.1: @@ -4058,11 +4307,23 @@ packages: jose: 4.15.4 dev: false + /@supabase/auth-js@2.64.2: + resolution: {integrity: sha512-s+lkHEdGiczDrzXJ1YWt2y3bxRi+qIUnXcgkpLSrId7yjBeaXBFygNjTaoZLG02KNcYwbuZ9qkEIqmj2hF7svw==} + dependencies: + '@supabase/node-fetch': 2.6.15 + dev: true + /@supabase/functions-js@2.1.5: resolution: {integrity: sha512-BNzC5XhCzzCaggJ8s53DP+WeHHGT/NfTsx2wUSSGKR2/ikLFQTBCDzMvGz/PxYMqRko/LwncQtKXGOYp1PkPaw==} dependencies: '@supabase/node-fetch': 2.6.15 + /@supabase/functions-js@2.3.1: + resolution: {integrity: sha512-QyzNle/rVzlOi4BbVqxLSH828VdGY1RElqGFAj+XeVypj6+PVtMlD21G8SDnsPQDtlqqTtoGRgdMlQZih5hTuw==} + dependencies: + '@supabase/node-fetch': 2.6.15 + dev: true + /@supabase/gotrue-js@2.62.2: resolution: {integrity: sha512-AP6e6W9rQXFTEJ7sTTNYQrNf0LCcnt1hUW+RIgUK+Uh3jbWvcIST7wAlYyNZiMlS9+PYyymWQ+Ykz/rOYSO0+A==} dependencies: @@ -4080,6 +4341,18 @@ packages: dependencies: whatwg-url: 5.0.0 + /@supabase/postgrest-js@1.15.2: + resolution: {integrity: sha512-9/7pUmXExvGuEK1yZhVYXPZnLEkDTwxgMQHXLrN5BwPZZm4iUCL1YEyep/Z2lIZah8d8M433mVAUEGsihUj5KQ==} + dependencies: + '@supabase/node-fetch': 2.6.15 + dev: true + + /@supabase/postgrest-js@1.15.4: + resolution: {integrity: sha512-Zjj3j5hVmerQRGnlo/Y9jqn7sdqRkiXf23oy5tGT5zi1jwyUDkj9vkydalY3nkgLgcBnVJDaNGzeBDr6Zn3/XQ==} + dependencies: + '@supabase/node-fetch': 2.6.15 + dev: true + /@supabase/postgrest-js@1.9.0: resolution: {integrity: sha512-axP6cU69jDrLbfihJKQ6vU27tklD0gzb9idkMN363MtTXeJVt5DQNT3JnJ58JVNBdL74hgm26rAsFNvHk+tnSw==} dependencies: @@ -4096,6 +4369,18 @@ packages: - bufferutil - utf-8-validate + /@supabase/realtime-js@2.9.5: + resolution: {integrity: sha512-TEHlGwNGGmKPdeMtca1lFTYCedrhTAv3nZVoSjrKQ+wkMmaERuCe57zkC5KSWFzLYkb5FVHW8Hrr+PX1DDwplQ==} + dependencies: + '@supabase/node-fetch': 2.6.15 + '@types/phoenix': 1.6.4 + '@types/ws': 8.5.10 + ws: 8.16.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: true + /@supabase/storage-js@2.5.5: resolution: {integrity: sha512-OpLoDRjFwClwc2cjTJZG8XviTiQH4Ik8sCiMK5v7et0MDu2QlXjCAW3ljxJB5+z/KazdMOTnySi+hysxWUPu3w==} dependencies: @@ -4114,6 +4399,20 @@ packages: - bufferutil - utf-8-validate + /@supabase/supabase-js@2.43.4: + resolution: {integrity: sha512-/pLPaxiIsn5Vaz3s32HC6O/VNwfeddnzS0bZRpOW0AKcPuXroD8pT9G8mpiBlZfpKsMmq6k7tlhW7Sr1PAQ1lw==} + dependencies: + '@supabase/auth-js': 2.64.2 + '@supabase/functions-js': 2.3.1 + '@supabase/node-fetch': 2.6.15 + '@supabase/postgrest-js': 1.15.2 + '@supabase/realtime-js': 2.9.5 + '@supabase/storage-js': 2.5.5 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: true + /@swc/counter@0.1.3: resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} dev: false @@ -4131,10 +4430,21 @@ packages: tslib: 2.5.0 dev: false + /@tanstack/match-sorter-utils@8.15.1: + resolution: {integrity: sha512-PnVV3d2poenUM31ZbZi/yXkBu3J7kd5k2u51CGwwNojag451AjTH9N6n41yjXz2fpLeewleyLBmNS6+HcGDlXw==} + engines: {node: '>=12'} + dependencies: + remove-accents: 0.5.0 + dev: false + /@tanstack/query-core@5.0.0: resolution: {integrity: sha512-Y1BpiA6BblJd/UlVqxEVeAG7IACn568YJuTTItAiecBI7En+33g780kg+/8lhgl+BzcUPN7o+NjBrSRGJoemyQ==} dev: false + /@tanstack/query-core@5.38.0: + resolution: {integrity: sha512-QtkoxvFcu52mNpp3+qOo9H265m3rt83Dgbw5WnNyJvr83cegrQ7zT8haHhL4Rul6ZQkeovxyWbXVW9zI0WYx6g==} + dev: false + /@tanstack/react-query@5.0.0(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-diQoC8FNBcO5Uf5yuaJlXthTtbO1xM8kzOX+pSBUMT9n/cqQ/u1wJGCtukvhDWA+6j07WmIj4bfqNbd2KOB6jQ==} peerDependencies: @@ -4152,6 +4462,22 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@tanstack/vue-query@5.38.0(vue@3.4.27): + resolution: {integrity: sha512-1RTthXW8tqA3LC1HatIN7goRaL4kRdo3eOtI6TBdAl3N82VQRP2KNqa2De7Q1zLQWkHTUZFj56Ds7pMW2RtVvQ==} + peerDependencies: + '@vue/composition-api': ^1.1.2 + vue: ^2.6.0 || ^3.3.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + dependencies: + '@tanstack/match-sorter-utils': 8.15.1 + '@tanstack/query-core': 5.38.0 + '@vue/devtools-api': 6.6.1 + vue: 3.4.27(typescript@5.4.5) + vue-demi: 0.14.7(vue@3.4.27) + dev: false + /@testing-library/dom@9.3.3: resolution: {integrity: sha512-fB0R+fa3AUqbLHWyxXa2kGVtf1Fe1ZZFr0Zp6AIbIAzXb2mKbEXl+PCQNUOaq5lbTab5tfctfXRNsWXxa2f7Aw==} engines: {node: '>=14'} @@ -4199,6 +4525,39 @@ packages: redent: 3.0.0 dev: true + /@testing-library/jest-dom@6.4.5(@types/jest@29.5.12)(jest@29.7.0): + resolution: {integrity: sha512-AguB9yvTXmCnySBP1lWjfNNUwpbElsaQ567lt2VdGqAdHtpieLgjmcVyv1q7PMIvLbgpDdkWV5Ydv3FEejyp2A==} + engines: {node: '>=14', npm: '>=6', yarn: '>=1'} + peerDependencies: + '@jest/globals': '>= 28' + '@types/bun': latest + '@types/jest': '>= 28' + jest: '>= 28' + vitest: '>= 0.32' + peerDependenciesMeta: + '@jest/globals': + optional: true + '@types/bun': + optional: true + '@types/jest': + optional: true + jest: + optional: true + vitest: + optional: true + dependencies: + '@adobe/css-tools': 4.3.2 + '@babel/runtime': 7.23.5 + '@types/jest': 29.5.12 + aria-query: 5.3.0 + chalk: 3.0.0 + css.escape: 1.5.1 + dom-accessibility-api: 0.6.3 + jest: 29.7.0 + lodash: 4.17.21 + redent: 3.0.0 + dev: true + /@testing-library/react@14.3.0(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-AYJGvNFMbCa5vt1UtDCa/dcaABrXq8gph6VN+cffIx0UeA0qiGqS+sT60+sb+Gjc8tGXdECWYQgaF0khf8b+Lg==} engines: {node: '>=14'} @@ -4208,11 +4567,27 @@ packages: dependencies: '@babel/runtime': 7.23.5 '@testing-library/dom': 9.3.3 - '@types/react-dom': 18.3.0 + '@types/react-dom': 18.2.19 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: true + /@testing-library/vue@8.1.0(vue@3.4.27): + resolution: {integrity: sha512-ls4RiHO1ta4mxqqajWRh8158uFObVrrtAPoxk7cIp4HrnQUj/ScKzqz53HxYpG3X6Zb7H2v+0eTGLSoy8HQ2nA==} + engines: {node: '>=14'} + peerDependencies: + '@vue/compiler-sfc': '>= 3' + vue: '>= 3' + peerDependenciesMeta: + '@vue/compiler-sfc': + optional: true + dependencies: + '@babel/runtime': 7.23.5 + '@testing-library/dom': 9.3.3 + '@vue/test-utils': 2.4.5 + vue: 3.4.27(typescript@5.4.5) + dev: true + /@theguild/remark-mermaid@0.0.5(react@18.2.0): resolution: {integrity: sha512-e+ZIyJkEv9jabI4m7q29wZtZv+2iwPGsXJ2d46Zi7e+QcFudiyuqhLhHG/3gX3ZEB+hxTch+fpItyMS8jwbIcw==} peerDependencies: @@ -4250,7 +4625,7 @@ packages: /@types/babel__core@7.1.19: resolution: {integrity: sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw==} dependencies: - '@babel/parser': 7.23.0 + '@babel/parser': 7.24.0 '@babel/types': 7.23.0 '@types/babel__generator': 7.6.4 '@types/babel__template': 7.4.1 @@ -4264,7 +4639,7 @@ packages: /@types/babel__template@7.4.1: resolution: {integrity: sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==} dependencies: - '@babel/parser': 7.23.0 + '@babel/parser': 7.24.0 '@babel/types': 7.23.0 /@types/babel__traverse@7.18.0: @@ -4306,10 +4681,14 @@ packages: resolution: {integrity: sha512-3zsplnP2djeps5P9OyarTxwRpMLoe5Ash8aL9iprw0JxB+FAHjY+ifn4yZUuW4/9hqtnmor6uvjSRzJhiVbrEQ==} dev: true + /@types/flat@5.0.5: + resolution: {integrity: sha512-nPLljZQKSnac53KDUDzuzdRfGI0TDb5qPrb+SrQyN3MtdQrOnGsKniHN1iYZsJEBIVQve94Y6gNz22sgISZq+Q==} + dev: true + /@types/graceful-fs@4.1.9: resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} dependencies: - '@types/node': 20.10.1 + '@types/node': 20.12.7 /@types/hast@2.3.4: resolution: {integrity: sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g==} @@ -4346,6 +4725,13 @@ packages: pretty-format: 29.7.0 dev: true + /@types/jest@29.5.12: + resolution: {integrity: sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==} + dependencies: + expect: 29.7.0 + pretty-format: 29.7.0 + dev: true + /@types/js-yaml@4.0.5: resolution: {integrity: sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==} dev: false @@ -4353,7 +4739,7 @@ packages: /@types/jsdom@20.0.1: resolution: {integrity: sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==} dependencies: - '@types/node': 20.10.1 + '@types/node': 20.12.7 '@types/tough-cookie': 4.0.5 parse5: 7.1.2 dev: true @@ -4407,11 +4793,18 @@ packages: /@types/node@17.0.45: resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==} + dev: true /@types/node@20.10.1: resolution: {integrity: sha512-T2qwhjWwGH81vUEx4EXmBKsTJRXFXNZTL4v0gi01+zyBmCwzE6TyHszqX01m+QHTEq+EZNo13NeJIdEqf+Myrg==} dependencies: undici-types: 5.26.5 + dev: true + + /@types/node@20.12.7: + resolution: {integrity: sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==} + dependencies: + undici-types: 5.26.5 /@types/node@20.9.2: resolution: {integrity: sha512-WHZXKFCEyIUJzAwh3NyyTHYSR35SevJ6mZ1nWwJafKtiQbqRTIKSRcw3Ma3acqgsent3RRDqeVwpHntMk+9irg==} @@ -4432,8 +4825,8 @@ packages: /@types/prop-types@15.7.5: resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==} - /@types/react-dom@18.3.0: - resolution: {integrity: sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==} + /@types/react-dom@18.2.19: + resolution: {integrity: sha512-aZvQL6uUbIJpjZk4U8JZGbau9KDeAwMfmhyWorxgBkqDIEf6ROjRozcmPIicqsUwPUjbkDfHKgGee1Lq65APcA==} dependencies: '@types/react': 18.2.0 @@ -4482,7 +4875,7 @@ packages: /@types/ws@8.5.10: resolution: {integrity: sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==} dependencies: - '@types/node': 20.10.1 + '@types/node': 20.12.7 /@types/yargs-parser@21.0.3: resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} @@ -4514,7 +4907,7 @@ packages: graphemer: 1.4.0 ignore: 5.3.0 natural-compare: 1.4.0 - semver: 7.5.4 + semver: 7.6.0 ts-api-utils: 1.0.3(typescript@5.4.2) typescript: 5.4.2 transitivePeerDependencies: @@ -4639,7 +5032,7 @@ packages: debug: 4.3.4 globby: 11.1.0 is-glob: 4.0.3 - semver: 7.5.4 + semver: 7.6.0 ts-api-utils: 1.0.3(typescript@5.4.2) typescript: 5.4.2 transitivePeerDependencies: @@ -4659,7 +5052,7 @@ packages: '@typescript-eslint/types': 6.8.0 '@typescript-eslint/typescript-estree': 6.8.0(typescript@5.4.2) eslint: 8.54.0 - semver: 7.5.4 + semver: 7.6.0 transitivePeerDependencies: - supports-color - typescript @@ -4707,10 +5100,90 @@ packages: server-only: 0.0.1 dev: false + /@vue/compiler-core@3.4.27: + resolution: {integrity: sha512-E+RyqY24KnyDXsCuQrI+mlcdW3ALND6U7Gqa/+bVwbcpcR3BRRIckFoz7Qyd4TTlnugtwuI7YgjbvsLmxb+yvg==} + dependencies: + '@babel/parser': 7.24.6 + '@vue/shared': 3.4.27 + entities: 4.5.0 + estree-walker: 2.0.2 + source-map-js: 1.2.0 + + /@vue/compiler-dom@3.4.27: + resolution: {integrity: sha512-kUTvochG/oVgE1w5ViSr3KUBh9X7CWirebA3bezTbB5ZKBQZwR2Mwj9uoSKRMFcz4gSMzzLXBPD6KpCLb9nvWw==} + dependencies: + '@vue/compiler-core': 3.4.27 + '@vue/shared': 3.4.27 + + /@vue/compiler-sfc@3.4.27: + resolution: {integrity: sha512-nDwntUEADssW8e0rrmE0+OrONwmRlegDA1pD6QhVeXxjIytV03yDqTey9SBDiALsvAd5U4ZrEKbMyVXhX6mCGA==} + dependencies: + '@babel/parser': 7.24.6 + '@vue/compiler-core': 3.4.27 + '@vue/compiler-dom': 3.4.27 + '@vue/compiler-ssr': 3.4.27 + '@vue/shared': 3.4.27 + estree-walker: 2.0.2 + magic-string: 0.30.10 + postcss: 8.4.38 + source-map-js: 1.2.0 + + /@vue/compiler-ssr@3.4.27: + resolution: {integrity: sha512-CVRzSJIltzMG5FcidsW0jKNQnNRYC8bT21VegyMMtHmhW3UOI7knmUehzswXLrExDLE6lQCZdrhD4ogI7c+vuw==} + dependencies: + '@vue/compiler-dom': 3.4.27 + '@vue/shared': 3.4.27 + + /@vue/devtools-api@6.6.1: + resolution: {integrity: sha512-LgPscpE3Vs0x96PzSSB4IGVSZXZBZHpfxs+ZA1d+VEPwHdOXowy/Y2CsvCAIFrf+ssVU1pD1jidj505EpUnfbA==} + dev: false + + /@vue/reactivity@3.4.27: + resolution: {integrity: sha512-kK0g4NknW6JX2yySLpsm2jlunZJl2/RJGZ0H9ddHdfBVHcNzxmQ0sS0b09ipmBoQpY8JM2KmUw+a6sO8Zo+zIA==} + dependencies: + '@vue/shared': 3.4.27 + + /@vue/runtime-core@3.4.27: + resolution: {integrity: sha512-7aYA9GEbOOdviqVvcuweTLe5Za4qBZkUY7SvET6vE8kyypxVgaT1ixHLg4urtOlrApdgcdgHoTZCUuTGap/5WA==} + dependencies: + '@vue/reactivity': 3.4.27 + '@vue/shared': 3.4.27 + + /@vue/runtime-dom@3.4.27: + resolution: {integrity: sha512-ScOmP70/3NPM+TW9hvVAz6VWWtZJqkbdf7w6ySsws+EsqtHvkhxaWLecrTorFxsawelM5Ys9FnDEMt6BPBDS0Q==} + dependencies: + '@vue/runtime-core': 3.4.27 + '@vue/shared': 3.4.27 + csstype: 3.1.3 + + /@vue/server-renderer@3.4.27(vue@3.4.27): + resolution: {integrity: sha512-dlAMEuvmeA3rJsOMJ2J1kXU7o7pOxgsNHVr9K8hB3ImIkSuBrIdy0vF66h8gf8Tuinf1TK3mPAz2+2sqyf3KzA==} + peerDependencies: + vue: 3.4.27 + dependencies: + '@vue/compiler-ssr': 3.4.27 + '@vue/shared': 3.4.27 + vue: 3.4.27(typescript@5.4.5) + + /@vue/shared@3.4.27: + resolution: {integrity: sha512-DL3NmY2OFlqmYYrzp39yi3LDkKxa5vZVwxWdQ3rG0ekuWscHraeIbnI8t+aZK7qhYqEqWKTUdijadunb9pnrgA==} + + /@vue/test-utils@2.4.5: + resolution: {integrity: sha512-oo2u7vktOyKUked36R93NB7mg2B+N7Plr8lxp2JBGwr18ch6EggFjixSCdIVVLkT6Qr0z359Xvnafc9dcKyDUg==} + dependencies: + js-beautify: 1.15.1 + vue-component-type-helpers: 2.0.10 + dev: true + /abab@2.0.6: resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==} dev: true + /abbrev@2.0.0: + resolution: {integrity: sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + dev: true + /acorn-globals@7.0.1: resolution: {integrity: sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==} dependencies: @@ -4799,7 +5272,6 @@ packages: /ansi-regex@6.0.1: resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} engines: {node: '>=12'} - dev: false /ansi-sequence-parser@1.1.0: resolution: {integrity: sha512-lEm8mt52to2fT8GhciPCGeCXACSz2UwIN4X2e2LJSnZ5uAbn2/dsYdOmUXq0AtWS5cpAupysIneExOgH0Vd2TQ==} @@ -4824,7 +5296,6 @@ packages: /ansi-styles@6.2.1: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} - dev: false /any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} @@ -4993,17 +5464,17 @@ packages: dependencies: deep-equal: 2.2.0 - /babel-jest@29.7.0(@babel/core@7.23.5): + /babel-jest@29.7.0(@babel/core@7.24.0): resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: '@babel/core': ^7.8.0 dependencies: - '@babel/core': 7.23.5 + '@babel/core': 7.24.0 '@jest/transform': 29.7.0 '@types/babel__core': 7.1.19 babel-plugin-istanbul: 6.1.1 - babel-preset-jest: 29.6.3(@babel/core@7.23.5) + babel-preset-jest: 29.6.3(@babel/core@7.24.0) chalk: 4.1.2 graceful-fs: 4.2.11 slash: 3.0.0 @@ -5014,7 +5485,7 @@ packages: resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==} engines: {node: '>=8'} dependencies: - '@babel/helper-plugin-utils': 7.18.9 + '@babel/helper-plugin-utils': 7.22.5 '@istanbuljs/load-nyc-config': 1.1.0 '@istanbuljs/schema': 0.1.3 istanbul-lib-instrument: 5.2.0 @@ -5050,15 +5521,34 @@ packages: '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.23.5) '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.23.5) - /babel-preset-jest@29.6.3(@babel/core@7.23.5): + /babel-preset-current-node-syntax@1.0.1(@babel/core@7.24.0): + resolution: {integrity: sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.24.0 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.24.0) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.24.0) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.24.0) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.24.0) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.24.0) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.24.0) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.24.0) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.24.0) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.24.0) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.24.0) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.24.0) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.24.0) + + /babel-preset-jest@29.6.3(@babel/core@7.24.0): resolution: {integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.23.5 + '@babel/core': 7.24.0 babel-plugin-jest-hoist: 29.6.3 - babel-preset-current-node-syntax: 1.0.1(@babel/core@7.23.5) + babel-preset-current-node-syntax: 1.0.1(@babel/core@7.24.0) /bail@2.0.2: resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} @@ -5100,6 +5590,10 @@ packages: readable-stream: 3.6.1 dev: false + /boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + dev: true + /brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} dependencies: @@ -5110,7 +5604,6 @@ packages: resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} dependencies: balanced-match: 1.0.2 - dev: false /braces@3.0.2: resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} @@ -5140,7 +5633,7 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: - caniuse-lite: 1.0.30001599 + caniuse-lite: 1.0.30001551 electron-to-chromium: 1.4.563 node-releases: 2.0.13 update-browserslist-db: 1.0.13(browserslist@4.22.1) @@ -5150,8 +5643,8 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: - caniuse-lite: 1.0.30001614 - electron-to-chromium: 1.4.750 + caniuse-lite: 1.0.30001599 + electron-to-chromium: 1.4.710 node-releases: 2.0.14 update-browserslist-db: 1.0.13(browserslist@4.23.0) @@ -5239,9 +5732,6 @@ packages: /caniuse-lite@1.0.30001599: resolution: {integrity: sha512-LRAQHZ4yT1+f9LemSMeqdMpMxZcc4RMWdj4tiFe3G8tNkWK+E58g+/tzotb5cU6TbcVJLr4fySiAW7XmxQvZQA==} - /caniuse-lite@1.0.30001614: - resolution: {integrity: sha512-jmZQ1VpmlRwHgdP1/uiKzgiAuGOfLEJsYFP4+GBou/QQ4U6IOJCB4NP1c+1p9RGLpwObcT94jA5/uO+F1vBbog==} - /ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} dev: false @@ -5468,6 +5958,11 @@ packages: resolution: {integrity: sha512-VtDvQpIJBvBatnONUsPzXYFVKQQAhuf3XTNOAsdBxCNO/QCtUUd8LSgjn0GVarBkCad6aJCZfXgrjYbl/KRr7w==} dev: false + /commander@10.0.1: + resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} + engines: {node: '>=14'} + dev: true + /commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} @@ -5489,6 +5984,13 @@ packages: /concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + /config-chain@1.1.13: + resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} + dependencies: + ini: 1.3.8 + proto-list: 1.2.4 + dev: true + /convert-source-map@1.8.0: resolution: {integrity: sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==} dependencies: @@ -5523,7 +6025,7 @@ packages: chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@20.10.1) + jest-config: 29.7.0(@types/node@20.12.7) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -5577,6 +6079,9 @@ packages: /csstype@3.1.2: resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==} + /csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + /csv-generate@3.4.3: resolution: {integrity: sha512-w/T+rqR0vwvHqWs/1ZyMDWtHHSJaN06klRqJXBEpDJaM/+dZkso0OKh1VcuuYvK3XM53KysVNq8Ko/epCK8wOw==} dev: true @@ -6148,9 +6653,24 @@ packages: engines: {node: '>=12'} dev: true + /dotenv@16.4.5: + resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} + engines: {node: '>=12'} + dev: true + /eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - dev: false + + /editorconfig@1.0.4: + resolution: {integrity: sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==} + engines: {node: '>=14'} + hasBin: true + dependencies: + '@one-ini/wasm': 0.1.1 + commander: 10.0.1 + minimatch: 9.0.1 + semver: 7.6.0 + dev: true /electron-to-chromium@1.4.325: resolution: {integrity: sha512-K1C03NT4I7BuzsRdCU5RWkgZxtswnKDYM6/eMhkEXqKu4e5T+ck610x3FPzu1y7HVFSiQKZqP16gnJzPpji1TQ==} @@ -6159,8 +6679,8 @@ packages: /electron-to-chromium@1.4.563: resolution: {integrity: sha512-dg5gj5qOgfZNkPNeyKBZQAQitIQ/xwfIDmEQJHCbXaD9ebTZxwJXUsDYcBlAvZGZLi+/354l35J1wkmP6CqYaw==} - /electron-to-chromium@1.4.750: - resolution: {integrity: sha512-9ItEpeu15hW5m8jKdriL+BQrgwDTXEL9pn4SkillWFu73ZNNNQ2BKKLS+ZHv2vC9UkNhosAeyfxOf/5OSeTCPA==} + /electron-to-chromium@1.4.710: + resolution: {integrity: sha512-w+9yAVHoHhysCa+gln7AzbO9CdjFcL/wN/5dd+XW/Msl2d/4+WisEaCF1nty0xbAKaxdaJfgLB2296U7zZB7BA==} /elkjs@0.8.2: resolution: {integrity: sha512-L6uRgvZTH+4OF5NE/MBbzQx/WYpru1xCBE9respNj6qznEewGUIfhzmm7horWWxbNO2M0WckQypGctR8lH79xQ==} @@ -6201,6 +6721,10 @@ packages: resolution: {integrity: sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==} engines: {node: '>=0.12'} + /entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + /error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} dependencies: @@ -6330,6 +6854,7 @@ packages: '@esbuild/win32-arm64': 0.19.8 '@esbuild/win32-ia32': 0.19.8 '@esbuild/win32-x64': 0.19.8 + dev: true /escalade@3.1.1: resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} @@ -6775,6 +7300,25 @@ packages: eslint: 8.54.0 dev: false + /eslint-plugin-vue@9.26.0(eslint@8.54.0): + resolution: {integrity: sha512-eTvlxXgd4ijE1cdur850G6KalZqk65k1JKoOI2d1kT3hr8sPD07j1q98FRFdNnpxBELGPWxZmInxeHGF/GxtqQ==} + engines: {node: ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.54.0) + eslint: 8.54.0 + globals: 13.24.0 + natural-compare: 1.4.0 + nth-check: 2.1.1 + postcss-selector-parser: 6.0.16 + semver: 7.6.0 + vue-eslint-parser: 9.4.2(eslint@8.54.0) + xml-name-validator: 4.0.0 + transitivePeerDependencies: + - supports-color + dev: true + /eslint-scope@7.2.2: resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -6913,6 +7457,9 @@ packages: '@types/unist': 2.0.6 dev: false + /estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + /estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} dependencies: @@ -7107,7 +7654,6 @@ packages: dependencies: cross-spawn: 7.0.3 signal-exit: 4.1.0 - dev: false /form-data@4.0.0: resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} @@ -7274,7 +7820,6 @@ packages: minimatch: 9.0.3 minipass: 7.0.4 path-scurry: 1.10.1 - dev: false /glob@7.1.6: resolution: {integrity: sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==} @@ -7317,6 +7862,13 @@ packages: dependencies: type-fest: 0.20.2 + /globals@13.24.0: + resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} + engines: {node: '>=8'} + dependencies: + type-fest: 0.20.2 + dev: true + /globalthis@1.0.3: resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==} engines: {node: '>= 0.4'} @@ -7333,7 +7885,7 @@ packages: array-union: 2.1.0 dir-glob: 3.0.1 fast-glob: 3.3.1 - ignore: 5.3.0 + ignore: 5.2.4 merge2: 1.4.1 slash: 3.0.0 @@ -7634,6 +8186,10 @@ packages: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} dev: false + /ignore@5.2.4: + resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} + engines: {node: '>= 4'} + /ignore@5.3.0: resolution: {integrity: sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==} engines: {node: '>= 4'} @@ -7673,7 +8229,6 @@ packages: /ini@1.3.8: resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} - dev: false /inline-style-parser@0.1.1: resolution: {integrity: sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==} @@ -7991,11 +8546,11 @@ packages: resolution: {integrity: sha512-x58orMzEVfzPUKqlbLd1hXCnySCxKdDKa6Rjg97CwuLLRI4g3FHTdnExu1OqffVFay6zeMW+T6/DowFLndWnIw==} engines: {node: '>=10'} dependencies: - '@babel/core': 7.23.2 - '@babel/parser': 7.23.0 + '@babel/core': 7.24.0 + '@babel/parser': 7.24.0 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.0 - semver: 7.5.4 + semver: 7.6.0 transitivePeerDependencies: - supports-color @@ -8040,7 +8595,6 @@ packages: '@isaacs/cliui': 8.0.2 optionalDependencies: '@pkgjs/parseargs': 0.11.0 - dev: false /jest-changed-files@29.7.0: resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==} @@ -8058,7 +8612,7 @@ packages: '@jest/expect': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 17.0.45 + '@types/node': 20.12.7 chalk: 4.1.2 co: 4.6.0 dedent: 1.5.1 @@ -8095,7 +8649,7 @@ packages: create-jest: 29.7.0 exit: 0.1.2 import-local: 3.1.0 - jest-config: 29.7.0(@types/node@20.10.1) + jest-config: 29.7.0(@types/node@20.12.7) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.1 @@ -8105,7 +8659,7 @@ packages: - supports-color - ts-node - /jest-config@29.7.0(@types/node@20.10.1): + /jest-config@29.7.0(@types/node@20.12.7): resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: @@ -8117,11 +8671,11 @@ packages: ts-node: optional: true dependencies: - '@babel/core': 7.23.5 + '@babel/core': 7.24.0 '@jest/test-sequencer': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.10.1 - babel-jest: 29.7.0(@babel/core@7.23.5) + '@types/node': 20.12.7 + babel-jest: 29.7.0(@babel/core@7.24.0) chalk: 4.1.2 ci-info: 3.9.0 deepmerge: 4.3.1 @@ -8199,7 +8753,7 @@ packages: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 17.0.45 + '@types/node': 20.12.7 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -8213,7 +8767,7 @@ packages: dependencies: '@jest/types': 29.6.3 '@types/graceful-fs': 4.1.9 - '@types/node': 20.10.1 + '@types/node': 20.12.7 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -8260,7 +8814,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 - '@types/node': 20.10.1 + '@types/node': 20.12.7 jest-util: 29.7.0 /jest-pnp-resolver@1.2.2(jest-resolve@29.7.0): @@ -8310,7 +8864,7 @@ packages: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.10.1 + '@types/node': 20.12.7 chalk: 4.1.2 emittery: 0.13.1 graceful-fs: 4.2.11 @@ -8340,7 +8894,7 @@ packages: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 17.0.45 + '@types/node': 20.12.7 chalk: 4.1.2 cjs-module-lexer: 1.2.2 collect-v8-coverage: 1.0.1 @@ -8390,7 +8944,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 - '@types/node': 20.10.1 + '@types/node': 20.12.7 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -8413,7 +8967,7 @@ packages: dependencies: '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 17.0.45 + '@types/node': 20.12.7 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -8424,7 +8978,7 @@ packages: resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@types/node': 17.0.45 + '@types/node': 20.12.7 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -8462,6 +9016,23 @@ packages: engines: {node: '>=10'} dev: true + /js-beautify@1.15.1: + resolution: {integrity: sha512-ESjNzSlt/sWE8sciZH8kBF8BPlwXPwhR6pWKAw8bw4Bwj+iZcnKW6ONWUutJ7eObuBZQpiIb8S7OYspWrKt7rA==} + engines: {node: '>=14'} + hasBin: true + dependencies: + config-chain: 1.1.13 + editorconfig: 1.0.4 + glob: 10.3.10 + js-cookie: 3.0.5 + nopt: 7.2.0 + dev: true + + /js-cookie@3.0.5: + resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==} + engines: {node: '>=14'} + dev: true + /js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -8511,7 +9082,7 @@ packages: whatwg-encoding: 2.0.0 whatwg-mimetype: 3.0.0 whatwg-url: 11.0.0 - ws: 8.14.2 + ws: 8.16.0 xml-name-validator: 4.0.0 transitivePeerDependencies: - bufferutil @@ -8703,7 +9274,6 @@ packages: /lru-cache@10.1.0: resolution: {integrity: sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==} engines: {node: 14 || >=16.14} - dev: false /lru-cache@4.1.5: resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==} @@ -8735,6 +9305,11 @@ packages: hasBin: true dev: true + /magic-string@0.30.10: + resolution: {integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==} + dependencies: + '@jridgewell/sourcemap-codec': 1.4.15 + /make-dir@3.1.0: resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} engines: {node: '>=8'} @@ -9449,12 +10024,18 @@ packages: dependencies: brace-expansion: 1.1.11 + /minimatch@9.0.1: + resolution: {integrity: sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==} + engines: {node: '>=16 || 14 >=14.17'} + dependencies: + brace-expansion: 2.0.1 + dev: true + /minimatch@9.0.3: resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} engines: {node: '>=16 || 14 >=14.17'} dependencies: brace-expansion: 2.0.1 - dev: false /minimist-options@4.1.0: resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} @@ -9483,7 +10064,6 @@ packages: /minipass@7.0.4: resolution: {integrity: sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==} engines: {node: '>=16 || 14 >=14.17'} - dev: false /minizlib@2.1.2: resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} @@ -9537,6 +10117,11 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + /nanoid@3.3.7: + resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + /napi-build-utils@1.0.2: resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==} dev: false @@ -9788,6 +10373,14 @@ packages: resolution: {integrity: sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==} dev: false + /nopt@7.2.0: + resolution: {integrity: sha512-CVDtwCdhYIvnAzFoJ6NJ6dX3oga9/HyciQDnG1vQDjSLMeKLJ4A93ZqYKDrgYSr1FBY5/hMYC+2VCi24pgpkGA==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + hasBin: true + dependencies: + abbrev: 2.0.0 + dev: true + /normalize-package-data@2.5.0: resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} dependencies: @@ -9829,6 +10422,12 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: false + /nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + dependencies: + boolbase: 1.0.0 + dev: true + /nwsapi@2.2.7: resolution: {integrity: sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==} dev: true @@ -10061,7 +10660,6 @@ packages: dependencies: lru-cache: 10.1.0 minipass: 7.0.4 - dev: false /path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} @@ -10170,6 +10768,14 @@ packages: cssesc: 3.0.0 util-deprecate: 1.0.2 + /postcss-selector-parser@6.0.16: + resolution: {integrity: sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==} + engines: {node: '>=4'} + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + dev: true + /postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} @@ -10190,6 +10796,14 @@ packages: picocolors: 1.0.0 source-map-js: 1.0.2 + /postcss@8.4.38: + resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.7 + picocolors: 1.0.0 + source-map-js: 1.2.0 + /prebuild-install@7.1.1: resolution: {integrity: sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==} engines: {node: '>=10'} @@ -10282,6 +10896,10 @@ packages: resolution: {integrity: sha512-kma4U7AFCTwpqq5twzC1YVIDXSqg6qQK6JN0smOw8fgRy1OkMi0CYSzFmsy6dnqSenamAtj0CyXMUJ1Mf6oROg==} dev: false + /proto-list@1.2.4: + resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} + dev: true + /protocols@2.0.1: resolution: {integrity: sha512-/XJ368cyBJ7fzLMwLKv1e4vLxOju2MNAIokcr7meSaNcVbWz/CPcW22cP04mwxOErdA5mwjA8Q6w/cdAQxVn7Q==} dev: false @@ -10695,6 +11313,10 @@ packages: resolution: {integrity: sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA==} dev: false + /remove-accents@0.5.0: + resolution: {integrity: sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==} + dev: false + /require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -10867,6 +11489,13 @@ packages: dependencies: lru-cache: 6.0.0 + /semver@7.6.0: + resolution: {integrity: sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==} + engines: {node: '>=10'} + hasBin: true + dependencies: + lru-cache: 6.0.0 + /server-only@0.0.1: resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==} dev: false @@ -10998,6 +11627,10 @@ packages: resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} engines: {node: '>=0.10.0'} + /source-map-js@1.2.0: + resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} + engines: {node: '>=0.10.0'} + /source-map-support@0.5.13: resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} dependencies: @@ -11101,7 +11734,6 @@ packages: eastasianwidth: 0.2.0 emoji-regex: 9.2.2 strip-ansi: 7.1.0 - dev: false /string.prototype.matchall@4.0.8: resolution: {integrity: sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==} @@ -11161,7 +11793,6 @@ packages: engines: {node: '>=12'} dependencies: ansi-regex: 6.0.1 - dev: false /strip-bom-string@1.0.0: resolution: {integrity: sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==} @@ -11562,7 +12193,42 @@ packages: /ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - /ts-jest@29.1.0(@babel/core@7.24.4)(esbuild@0.19.8)(jest@29.7.0)(typescript@5.4.2): + /ts-jest@29.1.0(@babel/core@7.23.5)(esbuild@0.19.8)(jest@29.7.0)(typescript@5.4.2): + resolution: {integrity: sha512-ZhNr7Z4PcYa+JjMl62ir+zPiNJfXJN6E8hSLnaUKhOgqcn8vb3e537cpkd0FuAfRK3sR1LSqM1MOhliXNgOFPA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + '@babel/core': '>=7.0.0-beta.0 <8' + '@jest/types': ^29.0.0 + babel-jest: ^29.0.0 + esbuild: '*' + jest: ^29.0.0 + typescript: '>=4.3 <6' + peerDependenciesMeta: + '@babel/core': + optional: true + '@jest/types': + optional: true + babel-jest: + optional: true + esbuild: + optional: true + dependencies: + '@babel/core': 7.23.5 + bs-logger: 0.2.6 + esbuild: 0.19.8 + fast-json-stable-stringify: 2.1.0 + jest: 29.7.0 + jest-util: 29.7.0 + json5: 2.2.3 + lodash.memoize: 4.1.2 + make-error: 1.3.6 + semver: 7.5.4 + typescript: 5.4.2 + yargs-parser: 21.1.1 + dev: true + + /ts-jest@29.1.0(@babel/core@7.24.0)(esbuild@0.19.8)(jest@29.7.0)(typescript@5.4.2): resolution: {integrity: sha512-ZhNr7Z4PcYa+JjMl62ir+zPiNJfXJN6E8hSLnaUKhOgqcn8vb3e537cpkd0FuAfRK3sR1LSqM1MOhliXNgOFPA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -11583,7 +12249,7 @@ packages: esbuild: optional: true dependencies: - '@babel/core': 7.24.4 + '@babel/core': 7.24.0 bs-logger: 0.2.6 esbuild: 0.19.8 fast-json-stable-stringify: 2.1.0 @@ -11595,6 +12261,78 @@ packages: semver: 7.5.4 typescript: 5.4.2 yargs-parser: 21.1.1 + dev: true + + /ts-jest@29.1.0(jest@29.7.0)(typescript@5.4.5): + resolution: {integrity: sha512-ZhNr7Z4PcYa+JjMl62ir+zPiNJfXJN6E8hSLnaUKhOgqcn8vb3e537cpkd0FuAfRK3sR1LSqM1MOhliXNgOFPA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + '@babel/core': '>=7.0.0-beta.0 <8' + '@jest/types': ^29.0.0 + babel-jest: ^29.0.0 + esbuild: '*' + jest: ^29.0.0 + typescript: '>=4.3 <6' + peerDependenciesMeta: + '@babel/core': + optional: true + '@jest/types': + optional: true + babel-jest: + optional: true + esbuild: + optional: true + dependencies: + bs-logger: 0.2.6 + fast-json-stable-stringify: 2.1.0 + jest: 29.7.0 + jest-util: 29.7.0 + json5: 2.2.3 + lodash.memoize: 4.1.2 + make-error: 1.3.6 + semver: 7.5.4 + typescript: 5.4.5 + yargs-parser: 21.1.1 + dev: false + + /ts-jest@29.1.3(@babel/core@7.24.0)(esbuild@0.19.8)(jest@29.7.0)(typescript@5.4.5): + resolution: {integrity: sha512-6L9qz3ginTd1NKhOxmkP0qU3FyKjj5CPoY+anszfVn6Pmv/RIKzhiMCsH7Yb7UvJR9I2A64rm4zQl531s2F1iw==} + engines: {node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@babel/core': '>=7.0.0-beta.0 <8' + '@jest/transform': ^29.0.0 + '@jest/types': ^29.0.0 + babel-jest: ^29.0.0 + esbuild: '*' + jest: ^29.0.0 + typescript: '>=4.3 <6' + peerDependenciesMeta: + '@babel/core': + optional: true + '@jest/transform': + optional: true + '@jest/types': + optional: true + babel-jest: + optional: true + esbuild: + optional: true + dependencies: + '@babel/core': 7.24.0 + bs-logger: 0.2.6 + esbuild: 0.19.8 + fast-json-stable-stringify: 2.1.0 + jest: 29.7.0 + jest-util: 29.7.0 + json5: 2.2.3 + lodash.memoize: 4.1.2 + make-error: 1.3.6 + semver: 7.6.0 + typescript: 5.4.5 + yargs-parser: 21.1.1 + dev: true /tsconfig-paths@3.14.2: resolution: {integrity: sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==} @@ -11649,6 +12387,45 @@ packages: - ts-node dev: true + /tsup@8.0.2(typescript@5.4.5): + resolution: {integrity: sha512-NY8xtQXdH7hDUAZwcQdY/Vzlw9johQsaqf7iwZ6g1DOUlFYQ5/AtVAjTvihhEyeRlGo4dLRVHtrRaL35M1daqQ==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + '@microsoft/api-extractor': ^7.36.0 + '@swc/core': ^1 + postcss: ^8.4.12 + typescript: '>=4.5.0' + peerDependenciesMeta: + '@microsoft/api-extractor': + optional: true + '@swc/core': + optional: true + postcss: + optional: true + typescript: + optional: true + dependencies: + bundle-require: 4.0.2(esbuild@0.19.8) + cac: 6.7.14 + chokidar: 3.5.3 + debug: 4.3.4 + esbuild: 0.19.8 + execa: 5.1.1 + globby: 11.1.0 + joycon: 3.1.1 + postcss-load-config: 4.0.2(postcss@8.4.31) + resolve-from: 5.0.0 + rollup: 4.6.1 + source-map: 0.8.0-beta.0 + sucrase: 3.34.0 + tree-kill: 1.2.2 + typescript: 5.4.5 + transitivePeerDependencies: + - supports-color + - ts-node + dev: true + /tsutils@3.21.0(typescript@5.4.2): resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} engines: {node: '>= 6'} @@ -11815,6 +12592,11 @@ packages: engines: {node: '>=14.17'} hasBin: true + /typescript@5.4.5: + resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==} + engines: {node: '>=14.17'} + hasBin: true + /unbox-primitive@1.0.2: resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} dependencies: @@ -12202,6 +12984,58 @@ packages: resolution: {integrity: sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==} dev: false + /vue-component-type-helpers@2.0.10: + resolution: {integrity: sha512-FC5fKJjDks3Ue/KRSYBdsiCaZa0kUPQfs8yQpb8W9mlO6BenV8G1z58xobeRMzevnmEcDa09LLwuXDwb4f6NMQ==} + dev: true + + /vue-demi@0.14.7(vue@3.4.27): + resolution: {integrity: sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + peerDependencies: + '@vue/composition-api': ^1.0.0-rc.1 + vue: ^3.0.0-0 || ^2.6.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + dependencies: + vue: 3.4.27(typescript@5.4.5) + dev: false + + /vue-eslint-parser@9.4.2(eslint@8.54.0): + resolution: {integrity: sha512-Ry9oiGmCAK91HrKMtCrKFWmSFWvYkpGglCeFAIqDdr9zdXmMMpJOmUJS7WWsW7fX81h6mwHmUZCQQ1E0PkSwYQ==} + engines: {node: ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '>=6.0.0' + dependencies: + debug: 4.3.4 + eslint: 8.54.0 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.5.0 + lodash: 4.17.21 + semver: 7.6.0 + transitivePeerDependencies: + - supports-color + dev: true + + /vue@3.4.27(typescript@5.4.5): + resolution: {integrity: sha512-8s/56uK6r01r1icG/aEOHqyMVxd1bkYcSe9j8HcKtr/xTOFWvnzIVTehNW+5Yt89f+DLBe4A569pnZLS5HzAMA==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@vue/compiler-dom': 3.4.27 + '@vue/compiler-sfc': 3.4.27 + '@vue/runtime-dom': 3.4.27 + '@vue/server-renderer': 3.4.27(vue@3.4.27) + '@vue/shared': 3.4.27 + typescript: 5.4.5 + /w3c-xmlserializer@4.0.0: resolution: {integrity: sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==} engines: {node: '>=14'} @@ -12380,7 +13214,6 @@ packages: ansi-styles: 6.2.1 string-width: 5.1.2 strip-ansi: 7.1.0 - dev: false /wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} @@ -12400,19 +13233,6 @@ packages: signal-exit: 4.1.0 dev: true - /ws@8.14.2: - resolution: {integrity: sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - dev: true - /ws@8.16.0: resolution: {integrity: sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==} engines: {node: '>=10.0.0'}