diff --git a/apps/database-new/package.json b/apps/database-new/package.json index 26b1fbd3d6a..e1d6f6a9e29 100644 --- a/apps/database-new/package.json +++ b/apps/database-new/package.json @@ -40,6 +40,6 @@ "autoprefixer": "^10.4.14", "nextjs-node-loader": "^1.1.5", "postcss": "^8.4.31", - "typescript": "^5.4.3" + "typescript": "^5.5.0" } } diff --git a/apps/design-system/package.json b/apps/design-system/package.json index 3c1fddaaba7..eb74f256f5d 100644 --- a/apps/design-system/package.json +++ b/apps/design-system/package.json @@ -52,7 +52,7 @@ "shiki": "^1.1.7", "tailwindcss": "^3.3.0", "tsconfig": "*", - "typescript": "^5", + "typescript": "^5.5.0", "unist-builder": "3.0.0" } } diff --git a/apps/docs/app/page.tsx b/apps/docs/app/page.tsx index cbfde7d0394..14a864e6049 100644 --- a/apps/docs/app/page.tsx +++ b/apps/docs/app/page.tsx @@ -122,22 +122,22 @@ const selfHostingOptions = [ { title: 'Auth', icon: 'auth', - href: 'reference/self-hosting-auth/introduction', + href: '/reference/self-hosting-auth/introduction', }, { title: 'Realtime', icon: 'realtime', - href: 'reference/self-hosting-realtime/introduction', + href: '/reference/self-hosting-realtime/introduction', }, { title: 'Storage', icon: 'storage', - href: 'reference/self-hosting-storage/introduction', + href: '/reference/self-hosting-storage/introduction', }, { title: 'Analytics', icon: 'analytics', - href: 'reference/self-hosting-analytics/introduction', + href: '/reference/self-hosting-analytics/introduction', }, ] @@ -145,32 +145,32 @@ const clientLibraries = [ { title: 'Javascript', icon: 'reference-javascript', - href: 'reference/javascript/introduction', + href: '/reference/javascript/introduction', }, { title: 'Flutter', icon: 'reference-dart', - href: 'reference/dart/introduction', + href: '/reference/dart/introduction', }, { title: 'Python', icon: 'reference-python', - href: 'reference/python/introduction', + href: '/reference/python/introduction', }, { title: 'C#', icon: 'reference-csharp', - href: 'reference/csharp/introduction', + href: '/reference/csharp/introduction', }, { title: 'Swift', icon: 'reference-swift', - href: 'reference/swift/introduction', + href: '/reference/swift/introduction', }, { title: 'Kotlin', icon: 'reference-kotlin', - href: 'reference/kotlin/introduction', + href: '/reference/kotlin/introduction', }, ] @@ -179,25 +179,25 @@ const additionalResources = [ title: 'Management API', description: 'Manage your Supabase projects and organizations.', icon: 'reference-api', - href: 'reference/api/introduction', + href: '/reference/api/introduction', }, { title: 'Supabase CLI', description: 'Use the CLI to develop, manage and deploy your projects.', icon: 'reference-cli', - href: 'reference/cli/introduction', + href: '/reference/cli/introduction', }, { title: 'Platform Guides', description: 'Learn more about the tools and services powering Supabase.', icon: 'platform', - href: 'guides/platform', + href: '/guides/platform', }, { title: 'Integrations', description: 'Explore a variety of integrations from Supabase partners.', icon: 'integrations', - href: 'guides/integrations', + href: '/guides/integrations', }, ] diff --git a/apps/docs/content/guides/auth/server-side/nextjs.mdx b/apps/docs/content/guides/auth/server-side/nextjs.mdx index 8663a5f0eab..2925dab980d 100644 --- a/apps/docs/content/guides/auth/server-side/nextjs.mdx +++ b/apps/docs/content/guides/auth/server-side/nextjs.mdx @@ -243,11 +243,15 @@ export async function updateSession(request: NextRequest) { // supabase.auth.getUser(). A simple mistake could make it very hard to debug // issues with users being randomly logged out. - const { data: user, error } = await supabase.auth.getUser() + const { + data: { user }, + } = await supabase.auth.getUser() - if (!user) { + if (!user && !request.nextUrl.pathname.startsWith('/login')) { // no user, potentially respond by redirecting the user to the login page - return NextResponse.redirect('/login') + const url = request.nextUrl.clone() + url.pathname = '/login' + return NextResponse.redirect(url) } // IMPORTANT: You *must* return the supabaseResponse object as it is. If you're diff --git a/apps/docs/content/guides/database/extensions/wrappers/overview.mdx b/apps/docs/content/guides/database/extensions/wrappers/overview.mdx index cf78407db38..87e5bc4bd8f 100644 --- a/apps/docs/content/guides/database/extensions/wrappers/overview.mdx +++ b/apps/docs/content/guides/database/extensions/wrappers/overview.mdx @@ -35,7 +35,7 @@ Examples: - A `subscriptions` table which maps to your Stripe subscriptions. - A `collections` table which maps to a Firebase collection. -Although a Foreign Table behaves like any other table, the data is not stored inside your database. The data remains inside the Remote Server. +Although a foreign table behaves like any other table, the data is not stored inside your database. The data remains inside the Remote Server. ### ETL with Wrappers @@ -111,9 +111,89 @@ This process can be taxing on your database if you are moving large amounts of d ## Security -Foreign Data Wrappers do not provide Row Level Security. We do not advise exposing them via your API. Wrappers should _always_ be stored in a private schema. For example, if you are connecting to your Stripe account, you should create a `stripe` schema to store all of your Foreign Tables inside. This schema should _not_ be added to the “Additional Schemas” setting in the API section. +Foreign Data Wrappers do not provide Row Level Security, thus it is not advised to expose them via your API. Wrappers should _always_ be stored in a private schema. For example, if you are connecting to your Stripe account, you should create a `stripe` schema to store all of your foreign tables inside. This schema should _not_ be added to the “Additional Schemas” setting in the API section. -If you want to expose any of the columns to your public API, create a [View](https://supabase.com/docs/guides/database/tables#views) in the `public` schema. You can also interact with your Foreign Tables inside a [Database Function](https://supabase.com/docs/guides/database/functions). +If you want to expose any of the foreign table columns to your public API, you can create a [Database Function with security definer](https://supabase.com/docs/guides/database/functions#security-definer-vs-invoker) in the `public` schema, and then you can interact with your foreign table through API. For better access control, the function should have appropriate filters on the foreign table to apply security rules based on your business needs. + +As an example, go to [SQL Editor](https://supabase.com/dashboard/project/_/sql/new) and then follow below steps, + +1. Create a Stripe Products foreign table: + + ```sql + create foreign table stripe.stripe_products ( + id text, + name text, + active bool, + default_price text, + description text, + created timestamp, + updated timestamp, + attrs jsonb + ) + server stripe_fdw_server + options ( + object 'products', + rowid_column 'id' + ); + ``` + +2. Create a security definer function that queries the foreign table and filters on the name prefix parameter: + + ```sql + create function public.get_stripe_products(name_prefix text) + returns table ( + id text, + name text, + active boolean, + default_price text, + description text + ) + language plpgsql + security definer set search_path = '' + as $$ + begin + return query + select + t.id, + t.name, + t.active, + t.default_price, + t.description + from + stripe.stripe_products t + where + t.name like name_prefix || '%' + ; + end; + $$; + ``` + +3. Restrict the function execution to a specific role only, for example, the authenticated users: + + + By default, the function created can be executed by any roles like `anon`, that means the + foreign table is public accessible. Always limit the function execution permission to + appropriate roles. + + + ```sql + -- revoke public execute permission + revoke execute on function public.get_stripe_products from public; + revoke execute on function public.get_stripe_products from anon; + + -- grant execute permission to a specific role only + grant execute on function public.get_stripe_products to authenticated; + ``` + +Once the preceding steps are finished, the function can be invoked from Supabase client to query the foreign table: + +```js +const { data, error } = await supabase + .rpc('get_stripe_products', { name_prefix: 'Test' }) + .select('*') +if (error) console.error(error) +else console.log(data) +``` ## Resources diff --git a/apps/docs/content/guides/platform/billing-faq.mdx b/apps/docs/content/guides/platform/billing-faq.mdx index 4c29c53557b..000372dbd3a 100644 --- a/apps/docs/content/guides/platform/billing-faq.mdx +++ b/apps/docs/content/guides/platform/billing-faq.mdx @@ -15,7 +15,7 @@ Read more on [how billing is organized](https://supabase.com/docs/guides/platfor #### How many free projects can I have? -You are entitled to two active free projects. Paused projects do not count towards your quota. Note that within an organization, we count the free project limits from all members that are either Owner or Admin. If you’ve got another organization member with the Admin or Owner role that has already exhausted their free project quota, you won’t be able to launch another free project in that organization. You can create simply create another free plan organization or change the role of the affected member in your [organization’s team settings](https://supabase.com/dashboard/org/_/team). +You are entitled to two active free projects. Paused projects do not count towards your quota. Note that within an organization, we count the free project limits from all members that are either Owner or Admin. If you’ve got another organization member with the Admin or Owner role that has already exhausted their free project quota, you won’t be able to launch another free project in that organization. You can create another free plan organization or change the role of the affected member in your [organization’s team settings](https://supabase.com/dashboard/org/_/team). #### Can I mix free and paid projects in a single organization? @@ -86,15 +86,45 @@ We can transfer the amount as credits to another organization of your choice. Yo #### What will happen when I exceed the free plan quota? -When you exceed the Free plan quota, we will monitor and notify you about the exceeding limits. It is important to take action at this point. If you continue to exceed the limits without reducing your usage, we will pause your projects. To avoid project pausing, you have two options: reduce your usage or upgrade to a paid plan. +You will be notified when you exceed the free plan quota. It is important to take action at this point. If you continue to exceed the limits without reducing your usage, service restrictions will apply. To avoid service restrictions, you have two options: reduce your usage or upgrade to a paid plan. Learn more about restrictions in the [Fair Use Policy](#fair-use-policy) section. #### What will happen when I exceed the Pro plan quota and have the spend cap on? -When you exceed the Pro plan quota, we will monitor and notify you about the exceeding limits. To unblock yourself, you can simply toggle off your spend cap in your [organization’s billing settings](https://supabase.com/dashboard/org/_/billing) to pay for over-usage beyond the Pro plans limits. If you continue to exceed the limits without reducing your usage or turning off the spend cap, we will pause your projects. +You will be notified when you exceed your Pro plan quota. To unblock yourself, you can toggle off your spend cap in your [organization’s billing settings](https://supabase.com/dashboard/org/_/billing) to pay for over-usage beyond the Pro plans limits. If you continue to exceed the limits without reducing your usage or turning off the spend cap, restrictions will apply. Learn more about restrictions in the [Fair Use Policy](#fair-use-policy) section. #### How do I scale beyond the limits of my Pro plan? -The Pro plan has a spend cap enabled by default to keep costs under control. If you want to scale beyond the plans included quota, ~~simply~~ switch off the spend cap to pay for additional usage beyond the plans included limits. You can toggle the spend cap in the [organization's billing settings](https://supabase.com/dashboard/org/_/billing). Read more about the [spend cap](https://supabase.com/docs/guides/platform/spend-cap). +The Pro plan has a spend cap enabled by default to keep costs under control. If you want to scale beyond the plans included quota, switch off the spend cap to pay for additional usage beyond the plans included limits. You can toggle the spend cap in the [organization's billing settings](https://supabase.com/dashboard/org/_/billing). Read more about the [spend cap](https://supabase.com/docs/guides/platform/spend-cap). + +## Fair Use Policy + +#### What is the Fair Use Policy? + +Our Fair Use Policy gives developers the freedom to build and experiment with Supabase, while protecting our infrastructure. Under the Fair Use policy, service restrictions may apply to your organization if: + +- You continually exceed the free plan quota +- You continually exceed Pro plan quota and have the spend cap enabled +- You have overdue invoices +- You have an expired credit card + +You will receive a notification before Fair Use Policy restrictions are applied. However, in some cases, like suspected abuse of our services, restrictions may be applied without prior notice. + +#### How is the Fair Use Policy applied? + +The Fair Use Policy is applied through service restrictions. This could mean: + +- Pausing projects +- Switching databases to read-only mode +- Disabling new project launches/transfers +- Responding with a 402 status code for all API requests + +The Fair Use Policy is generally applied to all projects of the restricted organization. + +#### How can I remove restrictions applied from the Fair Use Policy? + +To remove restrictions, you will need to address the issue that caused the restriction. This could be reducing your usage, paying overdue invoices, updating your payment method, or any other issue that caused the restriction. Once the issue is resolved, the restriction will be lifted. + +Restrictions due to usage limits are lifted with the next billing cycle as your quota refills at the beginning of each cycle. You can see when your current billing cycle ends on the [billing page](https://supabase.com/dashboard/org/_/usage) under "Billing Breakdown". If your organization is on the free plan, you can also lift restrictions immediately by [upgrading](https://supabase.com/dashboard/org/_/billing?panel=subscriptionPlan) to Pro. ## Reports and invoices diff --git a/apps/docs/content/guides/storage/schema/helper-functions.mdx b/apps/docs/content/guides/storage/schema/helper-functions.mdx index a29ab4bb17a..b8fef508159 100644 --- a/apps/docs/content/guides/storage/schema/helper-functions.mdx +++ b/apps/docs/content/guides/storage/schema/helper-functions.mdx @@ -17,7 +17,7 @@ Returns the name of a file. For example, if your file is stored in `public/subfo This example demonstrates how you would allow any user to download a file called `favicon.ico`: ```sql -create policy "Allow authenticated uploads" +create policy "Allow public downloads" on storage.objects for select to public diff --git a/apps/docs/package.json b/apps/docs/package.json index 2351445488a..87c4a3482bf 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -103,6 +103,6 @@ "simple-git": "^3.24.0", "tsconfig": "*", "tsx": "^4.6.2", - "typescript": "^5.4.3" + "typescript": "^5.5.0" } } diff --git a/apps/docs/public/humans.txt b/apps/docs/public/humans.txt index f251af7c9c7..e303ee814d5 100644 --- a/apps/docs/public/humans.txt +++ b/apps/docs/public/humans.txt @@ -18,6 +18,7 @@ Chase Granberry Chris Caruso Chris Copplestone Chris Gwilliams +Craig Cannon Dave Wilson Deji I Div Arora diff --git a/apps/docs/spec/supabase_swift_v2.yml b/apps/docs/spec/supabase_swift_v2.yml index 1d645bcaeb7..2898b170b65 100644 --- a/apps/docs/spec/supabase_swift_v2.yml +++ b/apps/docs/spec/supabase_swift_v2.yml @@ -138,6 +138,32 @@ functions: - When both **Confirm email** and **Confirm phone** (even when phone provider is disabled) are enabled in [your project](/dashboard/project/_/auth/providers), an obfuscated/fake user object is returned. - When either **Confirm email** or **Confirm phone** (even when phone provider is disabled) is disabled, the error message, `User already registered` is returned. - To fetch the currently logged-in user, refer to [`getUser()`](/docs/reference/swift/get-user). + overwriteParams: + - name: email + isOptional: true + type: String + description: One of `email` or `phone` must be provided. + - name: phone + isOptional: true + type: String + description: One of `email` or `phone` must be provided. + - name: password + type: String + - name: data + isOptional: true + type: JSONObject + description: > + A custom data object to store additional user metadata. + - name: redirectTo + isOptional: true + type: URL + description: > + Only for email signups. + The redirect URL embedded in the email link. + Must be a configured redirect URL for your Supabase instance. + - name: captchaToken + isOptional: true + type: String examples: - id: sign-up name: Sign up with email and password @@ -232,7 +258,6 @@ functions: - `PASSWORD_RECOVERY` - Emitted instead of the `SIGNED_IN` event when the user lands on a page that includes a password recovery link in the URL. - Use it to show a UI to the user where they can [reset their password](/docs/guides/auth/passwords#resetting-a-users-password-forgot-password). - examples: - id: listen-to-auth-changes name: Listen to auth changes @@ -292,6 +317,20 @@ functions: title: "signInWithPassword()" notes: | - Requires either an email and password or a phone number and password. + overwriteParams: + - name: email + isOptional: true + type: String + description: One of `email` or `phone` must be provided. + - name: phone + isOptional: true + type: String + description: One of `email` or `phone` must be provided. + - name: password + type: String + - name: captchaToken + isOptional: true + type: String examples: - id: sign-in-with-email-and-password name: Sign in with email and password @@ -322,6 +361,42 @@ functions: ``` - id: sign-in-with-otp title: "signInWithOTP()" + overwriteParams: + - name: email + isOptional: true + type: String + description: One of `email` or `phone` must be provided. + - name: phone + isOptional: true + type: String + description: One of `email` or `phone` must be provided. + - name: redirectTo + isOptional: true + type: String + description: > + Only for email signups. + The redirect URL embedded in the email link. + Must be a configured redirect URL for your Supabase instance. + - name: channel + isOptional: true + type: MessagingChannel + description: > + The channel to use for sending messages. + Only for phone signups. + - name: shouldCreateUser + isOptional: true + type: Bool + description: > + Whether to create the user if they don't already exist. + Defaults to true. + - name: data + isOptional: true + type: JSONObject + description: > + A custom data object to store additional user metadata. + - name: captchaToken + isOptional: true + type: String notes: | - Requires either an email or phone number. - This method is used for passwordless sign-ins where a OTP is sent to the user's email or phone number. @@ -365,6 +440,31 @@ functions: ``` - id: sign-in-with-oauth title: "signInWithOAuth()" + overwriteParams: + - name: provider + type: Provider + description: > + The third-party provider. + - name: redirectTo + type: URL + isOptional: true + description: > + A URL to send the user to after they are confirmed. + - name: scopes + type: String + isOptional: true + description: > + A space-separated list of scopes granted to the OAuth application. + - name: queryParams + type: "[(name: String, value: String?)]" + isOptional: true + description: > + Additional query params. + - name: configure + type: Callback + isOptional: true + description: > + A custom configuration callback for opening the OAuth flow externally. notes: | - This method is used for signing in using a third-party provider. - Supabase supports many different [third-party providers](https://supabase.com/docs/guides/auth#providers). @@ -454,7 +554,27 @@ functions: ) ``` - id: sign-in-with-sso - title: 'signInWithSSO()' + title: "signInWithSSO()" + overwriteParams: + - name: providerId + isOptional: true + type: String + description: > + UUID of the SSO provider. + One of `providerId` or `domain` is required. + - name: domain + isOptional: true + type: String + description: > + Domain name of the organization to use SSO with. + One of `providerId` or `domain` is required. + - name: redirectTo + type: String + description: > + The URL to redirect the user to after they have signed in. + Must be a configured redirect URL for your Supabase instance. + - name: captchaToken + type: String notes: | - Before you can call this method you need to [establish a connection](/docs/guides/auth/sso/auth-sso-saml#managing-saml-20-connections) to an identity provider. Use the [CLI commands](/docs/reference/cli/supabase-sso) to do this. - If you've associated an email domain to the identity provider, you can use the `domain` property to start a sign-in flow. @@ -506,6 +626,37 @@ functions: ``` - id: verify-otp title: "verifyOTP()" + overwriteParams: + - name: email + isOptional: true + type: String + description: One of `phone`, `email`, or `token_hash` must be provided. + - name: phone + isOptional: true + type: String + description: One of `phone`, `email`, or `token_hash` must be provided. + - name: token_hash + isOptional: true + type: String + description: > + The token hash from the user's email link. + One of `phone`, `email`, or `token_hash` must be provided. + - name: type + type: EmailOTPType | MobileOTPType + - name: token + isOptional: true + type: String + description: The OTP sent to the user. Required if using `phone` or `email`. + - name: redirectTo + isOptional: true + type: URL + description: > + A URL to redirect the user to after they are confirmed. + Must be in your configured redirect URLs. + - name: captchaToken + isOptional: true + type: String + description: Deprecated. notes: | - The `verifyOTP` method takes in different verification types. If a phone number is used, the type can either be `sms` or `phone_change`. If an email address is used, the type can be one of the following: `signup`, `magiclink`, `recovery`, `invite`, `email_change`, or `email`. - The verification type used should be determined based on the corresponding auth method called before `verifyOTP` to sign up / sign-in a user. @@ -629,7 +780,7 @@ functions: let identities = try await supabase.auth.userIdentities() ``` - id: link-identity - title: 'linkIdentity()' + title: "linkIdentity()" notes: | - The **Enable Manual Linking** option must be enabled from your [project's authentication settings](/dashboard/project/_/settings/auth). - The user needs to be signed in to call `linkIdentity()`. @@ -657,7 +808,7 @@ functions: } ``` - id: unlink-identity - title: 'unlinkIdentity()' + title: "unlinkIdentity()" notes: | - The **Enable Manual Linking** option must be enabled from your [project's authentication settings](/dashboard/project/_/settings/auth). - The user needs to be signed in to call `unlinkIdentity()`. @@ -928,7 +1079,7 @@ functions: let factors = try await supabase.auth.mfa.listFactors() ``` - id: admin-api - title: 'Overview' + title: "Overview" notes: | - Any method under the `supabase.auth.admin` namespace requires a `service_role` key. - These methods are considered admin methods and should be called on a trusted server. Never expose your `service_role` key in the browser. @@ -949,7 +1100,7 @@ functions: let adminAuthClient = supabase.auth.admin ``` - id: delete-user - title: 'deleteUser()' + title: "deleteUser()" notes: | - The `deleteUser()` method requires the user's ID, which maps to the `auth.users.id` column. examples: diff --git a/apps/studio/components/interfaces/Database/Indexes/CreateIndexSidePanel.tsx b/apps/studio/components/interfaces/Database/Indexes/CreateIndexSidePanel.tsx index 4ac909504bc..d6533a455ba 100644 --- a/apps/studio/components/interfaces/Database/Indexes/CreateIndexSidePanel.tsx +++ b/apps/studio/components/interfaces/Database/Indexes/CreateIndexSidePanel.tsx @@ -99,9 +99,9 @@ const CreateIndexSidePanel = ({ visible, onClose }: CreateIndexSidePanelProps) = }) const generatedSQL = ` -CREATE INDEX ON "${selectedSchema}"."${selectedEntity}" USING ${selectedIndexType} (${selectedColumns.join( - ', ' - )}); +CREATE INDEX ON "${selectedSchema}"."${selectedEntity}" USING ${selectedIndexType} (${selectedColumns + .map((column) => `"${column}"`) + .join(', ')}); `.trim() const onSaveIndex = () => { diff --git a/apps/studio/components/interfaces/Home/Connect/Connect.constants.ts b/apps/studio/components/interfaces/Home/Connect/Connect.constants.ts index 355050b9a7c..50c4465350e 100644 --- a/apps/studio/components/interfaces/Home/Connect/Connect.constants.ts +++ b/apps/studio/components/interfaces/Home/Connect/Connect.constants.ts @@ -206,6 +206,48 @@ export const MOBILES: ConnectionType[] = [ }, ], }, + { + key: 'ionicreact', + label: 'Ionic React', + icon: 'react', + guideLink: 'https://supabase.com/docs/guides/getting-started/tutorials/with-ionic-react', + children: [ + { + key: 'supabasejs', + label: 'Supabase-js', + children: [], + icon: 'supabase', + }, + ], + }, + { + key: 'swift', + label: 'Swift', + icon: 'swift', + guideLink: 'https://supabase.com/docs/guides/getting-started/tutorials/with-swift', + children: [ + { + key: 'supabaseswift', + label: 'supabase-swift', + children: [], + icon: 'supabase', + }, + ], + }, + { + key: 'androidkotlin', + label: 'Android Kotlin', + icon: 'kotlin', + guideLink: 'https://supabase.com/docs/guides/getting-started/tutorials/with-kotlin', + children: [ + { + key: 'supabasekt', + label: 'supabase-kt', + children: [], + icon: 'supabase', + }, + ], + }, ] export const ORMS: ConnectionType[] = [ diff --git a/apps/studio/components/interfaces/Home/Connect/content/androidkotlin/supabasekt/content.tsx b/apps/studio/components/interfaces/Home/Connect/content/androidkotlin/supabasekt/content.tsx new file mode 100644 index 00000000000..7e866e184ea --- /dev/null +++ b/apps/studio/components/interfaces/Home/Connect/content/androidkotlin/supabasekt/content.tsx @@ -0,0 +1,83 @@ +import type { ContentFileProps } from 'components/interfaces/Home/Connect/Connect.types' + +import { + ConnectTabs, + ConnectTabTriggers, + ConnectTabTrigger, + ConnectTabContent, +} from 'components/interfaces/Home/Connect/ConnectTabs' +import SimpleCodeBlock from 'components/to-be-cleaned/SimpleCodeBlock' + +const ContentFile = ({ projectKeys }: ContentFileProps) => { + return ( + + + + + + + + + {` +@Serializable +data class TodoItem(val id: Int, val name: String) + `} + + + + + + {` +val supabase = createSupabaseClient( + supabaseUrl = "${projectKeys.apiUrl ?? 'your-project-url'}", + supabaseKey = "${projectKeys.anonKey ?? 'your-anon-key'}" + ) { + install(Postgrest) +} + +class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + MaterialTheme { + // A surface container using the 'background' color from the theme + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background + ) { + TodoList() + } + } + } + } +} + +@Composable +fun TodoList() { + var items by remember { mutableStateOf>(listOf()) } + LaunchedEffect(Unit) { + withContext(Dispatchers.IO) { + items = supabase.from("todos") + .select().decodeList() + } + } + LazyColumn { + items( + items, + key = { item -> item.id }, + ) { item -> + Text( + item.name, + modifier = Modifier.padding(8.dp), + ) + } + } +} +`} + + + + ) +} + +export default ContentFile diff --git a/apps/studio/components/interfaces/Home/Connect/content/ionicreact/supabasejs/content.tsx b/apps/studio/components/interfaces/Home/Connect/content/ionicreact/supabasejs/content.tsx new file mode 100644 index 00000000000..adaed38bbcc --- /dev/null +++ b/apps/studio/components/interfaces/Home/Connect/content/ionicreact/supabasejs/content.tsx @@ -0,0 +1,115 @@ +import type { ContentFileProps } from 'components/interfaces/Home/Connect/Connect.types' + +import { + ConnectTabs, + ConnectTabTriggers, + ConnectTabTrigger, + ConnectTabContent, +} from 'components/interfaces/Home/Connect/ConnectTabs' +import SimpleCodeBlock from 'components/to-be-cleaned/SimpleCodeBlock' + +const ContentFile = ({ projectKeys }: ContentFileProps) => { + return ( + + + + + + + + + + {` +REACT_APP_SUPABASE_URL=${projectKeys.apiUrl ?? 'your-project-url'} +REACT_APP_SUPABASE_ANON_KEY=${projectKeys.anonKey ?? 'your-anon-key'} + `} + + + + + + {` +import { createClient } from '@supabase/supabase-js' + +const supabaseUrl = process.env.REACT_APP_SUPABASE_URL +const supabaseAnonKey = process.env.REACT_APP_SUPABASE_ANON_KEY + +export const supabase = createClient(supabaseUrl, supabaseAnonKey) +`} + + + + + + {` +import React, { useEffect, useState } from 'react'; +import { setupIonicReact, IonApp } from '@ionic/react'; +import { + IonContent, + IonHeader, + IonTitle, + IonToolbar, + IonList, + IonItem, +} from '@ionic/react'; + +/* Core CSS required for Ionic components to work properly */ +import '@ionic/react/css/core.css'; + +/* Theme variables */ +import './theme/variables.css'; + +import { supabase } from './supabaseClient'; + +setupIonicReact(); + +export default function App() { + const [todos, setTodos] = useState([]); + useEffect(() => { + getTodos(); + }, []); + + const getTodos = async () => { + try { + const { data, error } = await supabase.from('todos').select(); + + if (error) { + console.error('Error fetching todos:', error.message); + return; + } + + if (data) { + setTodos(data); + } + } catch (error) { + console.error('Error fetching todos:', error.message); + } + }; + + return ( + + <> + + + Todos + + + + + {todos.map((todo) => ( + {todo.title} + ))} + + + + + ); +} +`} + + + + ) +} + +export default ContentFile diff --git a/apps/studio/components/interfaces/Home/Connect/content/sveltekit/supabasejs/content.tsx b/apps/studio/components/interfaces/Home/Connect/content/sveltekit/supabasejs/content.tsx index 7a6c11670ce..7a399e2f1f0 100644 --- a/apps/studio/components/interfaces/Home/Connect/content/sveltekit/supabasejs/content.tsx +++ b/apps/studio/components/interfaces/Home/Connect/content/sveltekit/supabasejs/content.tsx @@ -33,7 +33,7 @@ SUPABASE_ANON_KEY=${projectKeys.anonKey ?? 'your-anon-key'} import { createClient } from "@supabase/supabase-js"; const supabaseUrl = process.env.SUPABASE_URL; -const supabaseKey = process.env.SUPABASE_KEY; +const supabaseKey = process.env.SUPABASE_ANON_KEY; export const supabase = createClient(supabaseUrl, supabaseKey); `} diff --git a/apps/studio/components/interfaces/Home/Connect/content/swift/supabaseswift/content.tsx b/apps/studio/components/interfaces/Home/Connect/content/swift/supabaseswift/content.tsx new file mode 100644 index 00000000000..a22a289bccc --- /dev/null +++ b/apps/studio/components/interfaces/Home/Connect/content/swift/supabaseswift/content.tsx @@ -0,0 +1,84 @@ +import type { ContentFileProps } from 'components/interfaces/Home/Connect/Connect.types' + +import { + ConnectTabs, + ConnectTabTriggers, + ConnectTabTrigger, + ConnectTabContent, +} from 'components/interfaces/Home/Connect/ConnectTabs' +import SimpleCodeBlock from 'components/to-be-cleaned/SimpleCodeBlock' + +const ContentFile = ({ projectKeys }: ContentFileProps) => { + return ( + + + + + + + + + + {` +import Foundation +import Supabase + +let supabase = SupabaseClient( + supabaseURL: URL(string: "${projectKeys.apiUrl ?? 'your-project-url'}")!, + supabaseKey: "${projectKeys.anonKey ?? 'your-anon-key'}" +) + `} + + + + + + {` +import Foundation + +struct Todo: Identifiable, Decodable { + var id: Int + var title: String +} +`} + + + + + + {` +import Supabase +import SwiftUI + +struct ContentView: View { + @State var todos: [Todo] = [] + + var body: some View { + NavigationStack { + List(todos) { todo in + Text(todo.title) + } + .navigationTitle("Todos") + .task { + do { + todos = try await supabase.from("todos").select().execute().value + } catch { + debugPrint(error) + } + } + } + } +} + +#Preview { + ContentView() +} + +`} + + + + ) +} + +export default ContentFile diff --git a/apps/studio/components/layouts/AppLayout/ProjectDropdown.tsx b/apps/studio/components/layouts/AppLayout/ProjectDropdown.tsx index 35f7477dc9a..00666dfebc0 100644 --- a/apps/studio/components/layouts/AppLayout/ProjectDropdown.tsx +++ b/apps/studio/components/layouts/AppLayout/ProjectDropdown.tsx @@ -41,9 +41,11 @@ export const sanitizeRoute = (route: string, routerQueries: ParsedUrlQuery) => { // [Joshen] Ideally we shouldn't use hard coded numbers, but temp workaround // for storage bucket route since its longer const isStorageBucketRoute = 'bucketId' in routerQueries + const isSecurityAdvisorRoute = 'preset' in routerQueries + return route .split('/') - .slice(0, isStorageBucketRoute ? 5 : 4) + .slice(0, isStorageBucketRoute || isSecurityAdvisorRoute ? 5 : 4) .join('/') } else { return route diff --git a/apps/studio/package.json b/apps/studio/package.json index c3c1c85feb4..a2054f5baa1 100644 --- a/apps/studio/package.json +++ b/apps/studio/package.json @@ -168,7 +168,7 @@ "sass": "^1.77.6", "storybook-dark-mode": "^3.0.1", "tailwindcss": "^3.4.1", - "typescript": "^5.4.3", + "typescript": "^5.5.0", "vite": "^5.3.2", "vite-plugin-cjs-interop": "^2.1.1" } diff --git a/apps/www/_blog/2024-06-26-postgis-generate-vector-tiles.mdx b/apps/www/_blog/2024-06-26-postgis-generate-vector-tiles.mdx index ae29e4ef30b..103fc2d23c0 100644 --- a/apps/www/_blog/2024-06-26-postgis-generate-vector-tiles.mdx +++ b/apps/www/_blog/2024-06-26-postgis-generate-vector-tiles.mdx @@ -32,7 +32,7 @@ Using PostGIS we can then programmatically generate vector tiles and serve them Vector tiles are packets of geographic data, packaged into pre-defined roughly-square shaped "tiles" for transfer over the web. Map data is requested by a client as a set of "tiles" corresponding to square areas of land of a pre-defined size and location. -Especially for large datasets, this has the benefit that the data transfer is greatly reduced because because only data within the current viewport, and at the current zoom level needs to be transferred. +Especially for large datasets, this has the benefit that the data transfer is greatly reduced because only data within the current viewport, and at the current zoom level needs to be transferred. @@ -71,7 +71,7 @@ PG_USE_COPY=true ogr2ogr -f pgdump places.sql places.geojson Enable the PostGIS extension on your Supabase Database on a dedicated separate `gis` schema. To do so you can navigate to the [SQL Editor](/dashboard/project/_/sql/new) and run the following SQL, or you can enable the extension from the [Database Extensions Settings](/dashboard/project/_/database/extensions). -As PostGIS can be quite compute heavy, we recommend enabling it on a decidacted separate schema, for example names `gis`! +As PostGIS can be quite compute heavy, we recommend enabling it on a dedicated separate schema, for example, named `gis`! ```sql CREATE SCHEMA IF NOT EXISTS "gis"; @@ -149,14 +149,14 @@ END; $$; ``` -To limit the amount of data send over the wire, we limit the amount of metadata to include in the vector tile. For example we add a condition for the zoom level, and only return the place name when the user has zoomed in beyond level 13. +To limit the amount of data sent over the wire, we limit the amount of metadata to include in the vector tile. For example we add a condition for the zoom level, and only return the place name when the user has zoomed in beyond level 13. ## Use supabase-js to fetch vector tiles from MapLibre GL client You can find the full `index.html` code on [GitHub](https://github.com/bdon/supabase-vector-tile/blob/main/index.html). Here we'll highlight how to add a new protocol to MapLibreGL to fetch the bas64 encoded binary vector tile data via supabase-js so that MapLibre GL can fetch and render the data as your users interact with the map: ```js index.html -const client = supabase.createClient('you-supabase-api-url', 'your-supabase-anon-key') +const client = supabase.createClient('your-supabase-api-url', 'your-supabase-anon-key') function base64ToArrayBuffer(base64) { var binaryString = atob(base64) @@ -213,7 +213,7 @@ const map = new maplibregl.Map({ ## On demand fetch additional JSON metadata -To limit the amount of data send over the wire, we don't encode all the metadata in the vector tile itself, but rather set up an onclick handler to fetch the additional metadata on demand within the MapLibre GL popup: +To limit the amount of data sent over the wire, we don't encode all the metadata in the vector tile itself, but rather set up an onclick handler to fetch the additional metadata on demand within the MapLibre GL popup: ```js index.html // .. diff --git a/apps/www/package.json b/apps/www/package.json index 99adbb68d9b..7718b9ba117 100644 --- a/apps/www/package.json +++ b/apps/www/package.json @@ -68,7 +68,7 @@ "swiper": "^11.0.7", "three": "^0.136.0", "typed.js": "^2.0.16", - "typescript": "^5.4.3", + "typescript": "^5.5.0", "ui": "*", "ui-patterns": "*", "use-debounce": "^7.0.1", diff --git a/examples/realtime/nextjs-authorization-demo/README.md b/examples/realtime/nextjs-authorization-demo/README.md index e8826a14405..735b8591549 100644 --- a/examples/realtime/nextjs-authorization-demo/README.md +++ b/examples/realtime/nextjs-authorization-demo/README.md @@ -41,25 +41,23 @@ We'll be using: ```sql CREATE TABLE public.rooms ( id bigint GENERATED BY default AS IDENTITY PRIMARY KEY, - topic text NOT NULL + topic text NOT NULL UNIQUE ); - -CREATE UNIQUE INDEX "unique_topic" on public.rooms (topic); +ALTER TABLE public.rooms ENABLE ROW LEVEL SECURITY; CREATE TABLE public.profiles ( - id bigint GENERATED BY default AS IDENTITY PRIMARY KEY, + id uuid NOT NULL REFERENCES auth.users ON DELETE CASCADE, email text NOT NULL, - user_id uuid REFERENCES auth.users (id) + + PRIMARY KEY (id) ); +ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY; CREATE TABLE public.rooms_users ( user_id uuid REFERENCES auth.users (id), - room_id bigint REFERENCES public.rooms (id), + room_topic text REFERENCES public.rooms (topic), created_at timestamptz DEFAULT CURRENT_TIMESTAMP ); - -ALTER TABLE public.rooms ENABLE ROW LEVEL SECURITY; -ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY; ALTER TABLE public.rooms_users ENABLE ROW LEVEL SECURITY; ``` @@ -82,7 +80,6 @@ AS PERMISSIVE FOR INSERT TO supabase_auth_admin WITH CHECK (true); - CREATE POLICY "authenticated users can read rooms" ON "public"."rooms" AS PERMISSIVE FOR SELECT @@ -95,7 +92,6 @@ AS PERMISSIVE FOR INSERT TO authenticated WITH CHECK (TRUE); - CREATE POLICY "authenticated users can read rooms_users" ON "public"."rooms_users" AS PERMISSIVE FOR SELECT @@ -108,30 +104,30 @@ AS PERMISSIVE FOR INSERT TO authenticated WITH CHECK (TRUE); -CREATE POLICY "authenticated user can enter room" +CREATE POLICY "authenticated user can read broadcast messages and presence state" ON "realtime"."messages" AS PERMISSIVE FOR SELECT TO authenticated USING ( EXISTS ( - SELECT ru.user_id - FROM public.rooms_users ru - JOIN public.rooms r ON r.id = ru.room_id AND r.topic = realtime.topic() - WHERE ru.user_id = auth.uid() + SELECT 1 + FROM public.rooms_users + WHERE user_id = auth.uid() + AND room_topic = realtime.topic() AND realtime.messages.extension in ('broadcast', 'presence') ) ); -CREATE POLICY "authenticated user can broadcast in room" +CREATE POLICY "authenticated user can send broadcast messages and track presence" ON "realtime"."messages" AS PERMISSIVE FOR INSERT TO authenticated WITH CHECK ( EXISTS ( - SELECT ru.user_id - FROM public.rooms_users ru - JOIN public.rooms r ON r.id = ru.room_id AND r.topic = realtime.topic() - WHERE ru.user_id = auth.uid() + SELECT 1 + FROM public.rooms_users + WHERE user_id = auth.uid() + AND room_topic = realtime.topic() AND realtime.messages.extension in ('broadcast', 'presence') ) ); @@ -145,7 +141,7 @@ We need to create a database function and trigger to add an entry to `public.pro CREATE OR REPLACE FUNCTION insert_user() RETURNS TRIGGER AS $$ BEGIN - INSERT INTO public.profiles (user_id, email) VALUES (NEW.id, NEW.email); RETURN NEW; + INSERT INTO public.profiles (id, email) VALUES (NEW.id, NEW.email); RETURN NEW; END; $$ LANGUAGE plpgsql; @@ -159,18 +155,19 @@ GRANT INSERT ON TABLE public.profiles TO supabase_auth_admin; ## Coding Concerns -* Check we're using the `2.10.0` version of `@supabase/realtime-js` +* Check that you're using `@supabase/realtime-js` v2.44.0 or later. * You need to define that the channel is private using the new configuration field during channel creation: - ```typescript - let channel = supabase.realtime.channel(selectedRoom, { - config: { broadcast: { self: true }, private: true }, - }) - ``` -You can check `app/protected/page.tsx` for more information regarding this +```typescript + const channel = supabase.channel('room-1', { + config: { private: true }, + }) +``` + +You can check `app/protected/page.tsx` to see how we've set it up in the demo. ## Adding user to channel Type `/invite ` of an existing user and they will be added to the channel. -![](invite.png) \ No newline at end of file +![invite user slash command](invite.png) diff --git a/examples/realtime/nextjs-authorization-demo/app/protected/page.tsx b/examples/realtime/nextjs-authorization-demo/app/protected/page.tsx index 44ffd952573..0c497343775 100644 --- a/examples/realtime/nextjs-authorization-demo/app/protected/page.tsx +++ b/examples/realtime/nextjs-authorization-demo/app/protected/page.tsx @@ -23,15 +23,15 @@ export default function Chat() { } const addUserToChannel = async (email: string) => { - const user = await supabase.from('profiles').select('user_id').eq('email', email) + const user = await supabase.from('profiles').select('id').eq('email', email) if (!user.data?.length) { addMessage(true, true, `User ${email} not found`) } else { - const room = await supabase.from('rooms').select('id').eq('topic', selectedRoom) + const room = await supabase.from('rooms').select('topic').eq('topic', selectedRoom) await supabase .from('rooms_users') - .upsert({ user_id: user.data?.[0].user_id, room_id: room.data?.[0].id }) + .upsert({ user_id: user.data?.[0].id, room_topic: room.data?.[0].topic }) addMessage(true, true, `Added ${email} to channel ${selectedRoom}`) } } @@ -65,7 +65,7 @@ export default function Chat() { await supabase.auth.getUser() const token = (await supabase.auth.getSession()).data.session?.access_token! supabase.realtime.setAuth(token) - let main = supabase.realtime + let main = supabase .channel('supaslack') .on('broadcast', { event: 'new_room' }, () => getChannels()) .subscribe() @@ -86,7 +86,7 @@ export default function Chat() { channel?.unsubscribe() setUsers(new Set()) - let newChannel = supabase.realtime.channel(selectedRoom, { + let newChannel = supabase.channel(selectedRoom, { config: { broadcast: { self: true }, private: true, // This line will tell the server that you want to use a private channel for this connection diff --git a/examples/realtime/nextjs-authorization-demo/components/create-room-modal.tsx b/examples/realtime/nextjs-authorization-demo/components/create-room-modal.tsx index e37b0f7651a..a59d898b4e4 100644 --- a/examples/realtime/nextjs-authorization-demo/components/create-room-modal.tsx +++ b/examples/realtime/nextjs-authorization-demo/components/create-room-modal.tsx @@ -14,12 +14,12 @@ export default function CreateRoomModal({ channel }: { channel: RealtimeChannel supabase.realtime.setAuth(token) - const rooms_response = await supabase.from('rooms').insert({ topic }).select('id') + const rooms_response = await supabase.from('rooms').insert({ topic }).select('topic') if (rooms_response.data) { await supabase .from('rooms_users') - .insert({ user_id: user.data.user!.id, room_id: rooms_response.data![0].id }) + .insert({ user_id: user.data.user!.id, room_topic: rooms_response.data![0].topic }) await channel?.send({ type: 'broadcast', diff --git a/package-lock.json b/package-lock.json index 153eed7052f..ee062193edc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -62,7 +62,7 @@ "autoprefixer": "^10.4.14", "nextjs-node-loader": "^1.1.5", "postcss": "^8.4.31", - "typescript": "^5.4.3" + "typescript": "^5.5.0" } }, "apps/database-new/node_modules/@tanstack/query-core": { @@ -149,7 +149,7 @@ "shiki": "^1.1.7", "tailwindcss": "^3.3.0", "tsconfig": "*", - "typescript": "^5", + "typescript": "^5.5.0", "unist-builder": "3.0.0" } }, @@ -1011,7 +1011,7 @@ "simple-git": "^3.24.0", "tsconfig": "*", "tsx": "^4.6.2", - "typescript": "^5.4.3" + "typescript": "^5.5.0" } }, "apps/docs/node_modules/@tanstack/query-core": { @@ -1363,7 +1363,7 @@ "sass": "^1.77.6", "storybook-dark-mode": "^3.0.1", "tailwindcss": "^3.4.1", - "typescript": "^5.4.3", + "typescript": "^5.5.0", "vite": "^5.3.2", "vite-plugin-cjs-interop": "^2.1.1" } @@ -2373,7 +2373,7 @@ "swiper": "^11.0.7", "three": "^0.136.0", "typed.js": "^2.0.16", - "typescript": "^5.4.3", + "typescript": "^5.5.0", "ui": "*", "ui-patterns": "*", "use-debounce": "^7.0.1", @@ -42899,9 +42899,9 @@ "license": "MIT" }, "node_modules/typescript": { - "version": "5.4.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz", - "integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==", + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.2.tgz", + "integrity": "sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -45475,7 +45475,7 @@ "sql-formatter": "^15.0.2", "ts-jest": "^29.1.1", "tsconfig": "*", - "typescript": "^5.4.3" + "typescript": "^5.5.0" } }, "packages/ai-commands/node_modules/argparse": { @@ -45584,7 +45584,7 @@ "@types/react": "^18.2.24", "@types/react-dom": "^18.2.8", "tsconfig": "*", - "typescript": "^5.4.3" + "typescript": "^5.5.0" }, "peerDependencies": { "@supabase/auth-js": "^2.63.0", @@ -45623,7 +45623,7 @@ "packages/icons": { "dependencies": { "@supabase/build-icons": "*", - "typescript": "^5.4.3" + "typescript": "^5.5.0" } }, "packages/pg-meta": { @@ -45636,7 +45636,7 @@ }, "devDependencies": { "@types/pg-format": "^1.0.1", - "typescript": "^5.4.3" + "typescript": "^5.5.0" } }, "packages/shared-data": { @@ -45750,7 +45750,7 @@ "ts-jest": "^29.1.1", "tsconfig": "*", "tsconfig-paths-webpack-plugin": "^4.0.1", - "typescript": "^5.4.3" + "typescript": "^5.5.0" }, "peerDependencies": { "next": "*", @@ -46515,7 +46515,7 @@ "jest-environment-jsdom": "^29.7.0", "postgres": "^3.0.5", "ts-jest": "^29.1.1", - "typescript": "^5.4.3" + "typescript": "^5.5.0" } }, "tests/node_modules/@testdeck/core": { diff --git a/packages/ai-commands/package.json b/packages/ai-commands/package.json index c6fd2214ea2..67127512fc3 100644 --- a/packages/ai-commands/package.json +++ b/packages/ai-commands/package.json @@ -34,6 +34,6 @@ "sql-formatter": "^15.0.2", "ts-jest": "^29.1.1", "tsconfig": "*", - "typescript": "^5.4.3" + "typescript": "^5.5.0" } } diff --git a/packages/common/package.json b/packages/common/package.json index 3765018aff6..94c79aa9b0a 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -20,7 +20,7 @@ "@types/react": "^18.2.24", "@types/react-dom": "^18.2.8", "tsconfig": "*", - "typescript": "^5.4.3" + "typescript": "^5.5.0" }, "peerDependencies": { "@supabase/supabase-js": "*", diff --git a/packages/icons/package.json b/packages/icons/package.json index 1588a17be3d..5bbbe5cf4e5 100644 --- a/packages/icons/package.json +++ b/packages/icons/package.json @@ -5,7 +5,7 @@ "build:icons": "build-icons --templateSrc=./scripts/exportTemplate.mjs --renderUniqueKey --iconFileExtension=.ts --exportFileName=index.ts" }, "dependencies": { - "typescript": "^5.4.3", + "typescript": "^5.5.0", "@supabase/build-icons": "*" } } diff --git a/packages/pg-meta/package.json b/packages/pg-meta/package.json index a5ddc5e690b..1f40228e6a6 100644 --- a/packages/pg-meta/package.json +++ b/packages/pg-meta/package.json @@ -11,6 +11,6 @@ }, "devDependencies": { "@types/pg-format": "^1.0.1", - "typescript": "^5.4.3" + "typescript": "^5.5.0" } } diff --git a/packages/ui/package.json b/packages/ui/package.json index e933ac89047..c8d0f04bdd6 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -116,7 +116,7 @@ "ts-jest": "^29.1.1", "tsconfig": "*", "tsconfig-paths-webpack-plugin": "^4.0.1", - "typescript": "^5.4.3" + "typescript": "^5.5.0" }, "peerDependencies": { "next": "*", diff --git a/tests/package.json b/tests/package.json index 4e01114c9ff..15e50147555 100644 --- a/tests/package.json +++ b/tests/package.json @@ -31,6 +31,6 @@ "jest-allure2-adapter": "^0.3.12", "jest-environment-jsdom": "^29.7.0", "postgres": "^3.0.5", - "typescript": "^5.4.3" + "typescript": "^5.5.0" } }