From c74dbc5f73af651549db0f30e701e9e40b00189e Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Mon, 6 Oct 2025 13:31:53 +0800 Subject: [PATCH 1/5] Add callout for migration (#39230) * Add callout for migration * Update copy * Update date * remove docs button * Fix conditional - storage upgrade prompt should only show if list v2 is false --- .../DiskManagement/fields/DiskSizeField.tsx | 2 +- .../interfaces/Storage/StorageMenu.tsx | 20 ++- .../StorageListV2MigrationCallout.tsx | 143 ++++++++++++++++++ .../StorageSettings/StorageSettings.tsx | 20 ++- 4 files changed, 179 insertions(+), 6 deletions(-) create mode 100644 apps/studio/components/interfaces/Storage/StorageSettings/StorageListV2MigrationCallout.tsx diff --git a/apps/studio/components/interfaces/DiskManagement/fields/DiskSizeField.tsx b/apps/studio/components/interfaces/DiskManagement/fields/DiskSizeField.tsx index 9b6057676a281..5cb2a24a83f2a 100644 --- a/apps/studio/components/interfaces/DiskManagement/fields/DiskSizeField.tsx +++ b/apps/studio/components/interfaces/DiskManagement/fields/DiskSizeField.tsx @@ -88,7 +88,7 @@ export function DiskSizeField({ const mainDiskUsed = Math.round(((diskUtil?.metrics.fs_used_bytes ?? 0) / GB) * 100) / 100 return ( -
+
{ const { ref, bucketId } = useParams() const { data: projectDetails } = useSelectedProjectQuery() const snap = useStorageExplorerStateSnapshot() + + const showMigrationCallout = useFlag('storageMigrationCallout') + const { data: config } = useProjectStorageConfigQuery( + { projectRef: ref }, + { enabled: showMigrationCallout } + ) + const isListV2UpgradeAvailable = + !!config && !config.capabilities.list_v2 && config.external.upstreamTarget === 'main' + const isBranch = projectDetails?.parent_project_ref !== undefined const [searchText, setSearchText] = useState('') @@ -159,7 +170,12 @@ export const StorageMenu = () => { {IS_PLATFORM && ( -

Settings

+
+

Settings

+ {isListV2UpgradeAvailable && ( + Upgrade available + )} +
)} diff --git a/apps/studio/components/interfaces/Storage/StorageSettings/StorageListV2MigrationCallout.tsx b/apps/studio/components/interfaces/Storage/StorageSettings/StorageListV2MigrationCallout.tsx new file mode 100644 index 0000000000000..4f02d92836b06 --- /dev/null +++ b/apps/studio/components/interfaces/Storage/StorageSettings/StorageListV2MigrationCallout.tsx @@ -0,0 +1,143 @@ +import dayjs from 'dayjs' +import { toast } from 'sonner' + +import { useParams } from 'common' +import { InlineLink } from 'components/ui/InlineLink' +import { useProjectStorageConfigUpdateUpdateMutation } from 'data/config/project-storage-config-update-mutation' +import { useState } from 'react' +import { + Button, + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogSection, + DialogSectionSeparator, + DialogTitle, + DialogTrigger, +} from 'ui' +import { Admonition, TimestampInfo } from 'ui-patterns' + +// [Joshen] Will be decided by Storage team, temp setting to 15th December 2025 UTC (3 months buffer) +const MIGRATION_DEADLINE = '2025-12-15T00:00:00' + +export const StorageListV2MigrationCallout = () => { + const deadline = dayjs(MIGRATION_DEADLINE).utc(true) + const currentDate = dayjs.utc() + const remainingMonths = Math.ceil(deadline.diff(currentDate, 'months', true)) + + return ( + +

+ Get access to the List-V2 endpoint for improved performance and the ability to enable + Analytics buckets to your storage system +

+ {remainingMonths <= 1 && ( +

+ Your project's Storage will be automatically upgraded by{' '} + {' '} + if the upgrade is not completed by then. +

+ )} +
+ +
+
+ ) +} + +export const StorageListV2MigratingCallout = () => { + return ( + +

+ This notice will be closed once the upgrade has been completed - hang tight! +

+
+ ) +} + +const StorageListV2MigrationDialog = () => { + const { ref } = useParams() + + const [open, setOpen] = useState(false) + + const { mutate: updateStorageConfig, isLoading: isUpdating } = + useProjectStorageConfigUpdateUpdateMutation({ + onSuccess: () => { + toast.success(`Project's storage will be upgraded shortly!`) + setOpen(false) + }, + }) + + const onConfirmUpgrade = () => { + if (!ref) return console.error('Project ref is required') + updateStorageConfig({ projectRef: ref, external: { upstreamTarget: 'canary' } }) + } + + return ( + + + + + + + Upgrade your project's Storage + + Get access to Analytics buckets and an improved list method + + + + + + + + +

+ Depending on the number of objects in your Storage, the migration can take up to 24 + hours to finish. +

+ +

+ The upgrade will increase your disk size to about 15 - 25% and IOPS will be used to + create new efficient indexes as well as denormalising tables. +

+ +

+ Ensure that your database instance has not{' '} + + scaled disk + {' '} + within the last 6h and you have at least 60%{' '} + + CPU capacity + {' '} + before proceeding. +

+
+ + + + + +
+
+ ) +} diff --git a/apps/studio/components/interfaces/Storage/StorageSettings/StorageSettings.tsx b/apps/studio/components/interfaces/Storage/StorageSettings/StorageSettings.tsx index 9e0f0836b5752..431f40e6522af 100644 --- a/apps/studio/components/interfaces/Storage/StorageSettings/StorageSettings.tsx +++ b/apps/studio/components/interfaces/Storage/StorageSettings/StorageSettings.tsx @@ -6,7 +6,7 @@ import { SubmitHandler, useForm } from 'react-hook-form' import { toast } from 'sonner' import * as z from 'zod' -import { useParams } from 'common' +import { useFlag, useParams } from 'common' import { ScaffoldSection } from 'components/layouts/Scaffold' import AlertError from 'components/ui/AlertError' import { InlineLink } from 'components/ui/InlineLink' @@ -41,6 +41,10 @@ import { TooltipTrigger, } from 'ui' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' +import { + StorageListV2MigratingCallout, + StorageListV2MigrationCallout, +} from './StorageListV2MigrationCallout' import { STORAGE_FILE_SIZE_LIMIT_MAX_BYTES_CAPPED, STORAGE_FILE_SIZE_LIMIT_MAX_BYTES_FREE_PLAN, @@ -59,6 +63,8 @@ interface StorageSettingsState { export const StorageSettings = () => { const { ref: projectRef } = useParams() + const showMigrationCallout = useFlag('storageMigrationCallout') + const { can: canReadStorageSettings, isLoading: isLoadingPermissions } = useAsyncCheckPermissions( PermissionAction.STORAGE_ADMIN_READ, '*' @@ -75,6 +81,10 @@ export const StorageSettings = () => { isSuccess, isError, } = useProjectStorageConfigQuery({ projectRef }) + const isListV2UpgradeAvailable = + !!config && !config.capabilities.list_v2 && config.external.upstreamTarget === 'main' + const isListV2Upgrading = + !!config && !config.capabilities.list_v2 && config.external.upstreamTarget === 'canary' const { data: organization } = useSelectedOrganizationQuery() const isFreeTier = organization?.plan.id === 'free' @@ -223,6 +233,12 @@ export const StorageSettings = () => { subject="Failed to retrieve project's storage configuration" /> )} + {isSuccess && showMigrationCallout && ( + <> + {isListV2UpgradeAvailable && } + {isListV2Upgrading && } + + )} {isSuccess && (
@@ -448,5 +464,3 @@ export const StorageSettings = () => { ) } - -export default StorageSettings From 1e6941df4dbaed010f76e7b41a87342e4fcb5f64 Mon Sep 17 00:00:00 2001 From: Timothy Lim Date: Mon, 6 Oct 2025 15:30:08 +0800 Subject: [PATCH 2/5] docs: fix Javascript sign out syntax (#39279) --- apps/docs/spec/supabase_js_v2.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/docs/spec/supabase_js_v2.yml b/apps/docs/spec/supabase_js_v2.yml index 249d310b83cbd..169464ae0fc3d 100644 --- a/apps/docs/spec/supabase_js_v2.yml +++ b/apps/docs/spec/supabase_js_v2.yml @@ -1197,14 +1197,14 @@ functions: isSpotlight: false code: | ```js - const { error } = await supabase.auth.signOut('local') + const { error } = await supabase.auth.signOut({ scope: 'local' }) ``` - id: sign-out-other-sessions name: Sign out (other sessions) isSpotlight: false code: | ```js - const { error } = await supabase.auth.signOut('others') + const { error } = await supabase.auth.signOut({ scope: 'others' }) ``` - id: verify-otp title: 'verifyOtp()' From 92ce7fb4e153b9fecc440eb9d8cf7216e7eb2901 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Mon, 6 Oct 2025 15:49:51 +0800 Subject: [PATCH 3/5] chore: cache time usage / do not preload invoice breakdown (#39278) - Increase cache time of usage to 60m - Ensure org is fully loaded before rendering invoice breakdown (this currently leads to starting to load this endpoint and then cancelling it when directly landing on the org billing page) --- .../interfaces/Organization/BillingSettings/BillingSettings.tsx | 2 +- apps/studio/data/usage/org-usage-query.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/BillingSettings.tsx b/apps/studio/components/interfaces/Organization/BillingSettings/BillingSettings.tsx index 884b39bee9ea2..bcb1c3f0f0b48 100644 --- a/apps/studio/components/interfaces/Organization/BillingSettings/BillingSettings.tsx +++ b/apps/studio/components/interfaces/Organization/BillingSettings/BillingSettings.tsx @@ -57,7 +57,7 @@ export const BillingSettings = () => { - {org?.plan.id !== 'free' && ( + {org && org.plan.id !== 'free' && ( diff --git a/apps/studio/data/usage/org-usage-query.ts b/apps/studio/data/usage/org-usage-query.ts index 9b6f4ae434425..4110c6660c2a0 100644 --- a/apps/studio/data/usage/org-usage-query.ts +++ b/apps/studio/data/usage/org-usage-query.ts @@ -44,7 +44,7 @@ export const useOrgUsageQuery = ( ({ signal }) => getOrgUsage({ orgSlug, projectRef, start, end }, signal), { enabled: enabled && IS_PLATFORM && typeof orgSlug !== 'undefined', - staleTime: 1000 * 60 * 30, // 30 mins, underlying usage data only refreshes once an hour, so safe to cache for a while + staleTime: 1000 * 60 * 60, // 60 mins, underlying usage data only refreshes once an hour, so safe to cache for a while ...options, } ) From e4aee0728cd88ba90a1fe0de48732d5503e62efd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Mon, 6 Oct 2025 16:39:49 +0800 Subject: [PATCH 4/5] chore: project daily stats caching / deprecated props (#39277) Remove deprecated props that have no effect on behaviour and fix the caching (based on date instead of timestamp) --- .../Reports/ReportBlock/ChartBlock.tsx | 6 +-- .../Reports/v2/ReportChartUpsell.tsx | 4 +- .../components/ui/Charts/ChartHandler.tsx | 7 ++- .../ui/Charts/ComposedChartHandler.tsx | 2 - .../components/ui/Charts/LogChartHandler.tsx | 13 ++--- .../analytics/project-daily-stats-queries.ts | 5 -- .../analytics/project-daily-stats-query.ts | 53 ++----------------- 7 files changed, 16 insertions(+), 74 deletions(-) diff --git a/apps/studio/components/interfaces/Reports/ReportBlock/ChartBlock.tsx b/apps/studio/components/interfaces/Reports/ReportBlock/ChartBlock.tsx index b8bf0c518bbf7..1e6ea53330551 100644 --- a/apps/studio/components/interfaces/Reports/ReportBlock/ChartBlock.tsx +++ b/apps/studio/components/interfaces/Reports/ReportBlock/ChartBlock.tsx @@ -74,10 +74,8 @@ export const ChartBlock = ({ { projectRef: ref as string, attribute: attribute as ProjectDailyStatsAttribute, - startDate, - endDate, - interval: interval as AnalyticsInterval, - databaseIdentifier, + startDate: dayjs(startDate).format('YYYY-MM-DD'), + endDate: dayjs(endDate).format('YYYY-MM-DD'), }, { enabled: provider === 'daily-stats' } ) diff --git a/apps/studio/components/interfaces/Reports/v2/ReportChartUpsell.tsx b/apps/studio/components/interfaces/Reports/v2/ReportChartUpsell.tsx index 779aa17ee33ef..d5bd366bf0018 100644 --- a/apps/studio/components/interfaces/Reports/v2/ReportChartUpsell.tsx +++ b/apps/studio/components/interfaces/Reports/v2/ReportChartUpsell.tsx @@ -1,8 +1,7 @@ +import { LogChartHandler } from 'components/ui/Charts/LogChartHandler' import Link from 'next/link' import { useRef, useState } from 'react' -import { LogChartHandler } from 'components/ui/Charts/LogChartHandler' -import { ReportConfig } from 'data/reports/v2/reports.types' import { Button, Card, cn } from 'ui' export function ReportChartUpsell({ @@ -78,7 +77,6 @@ export function ReportChartUpsell({ label={''} startDate={startDate} endDate={endDate} - interval={'1d'} data={demoData as any} isLoading={false} highlightedValue={0} diff --git a/apps/studio/components/ui/Charts/ChartHandler.tsx b/apps/studio/components/ui/Charts/ChartHandler.tsx index 90193372084c4..09c292813ec0a 100644 --- a/apps/studio/components/ui/Charts/ChartHandler.tsx +++ b/apps/studio/components/ui/Charts/ChartHandler.tsx @@ -17,6 +17,7 @@ import { Activity, BarChartIcon, Loader2 } from 'lucide-react' import { useDatabaseSelectorStateSnapshot } from 'state/database-selector' import { WarningIcon } from 'ui' import type { ChartData } from './Charts.types' +import dayjs from 'dayjs' interface ChartHandlerProps { id?: string @@ -75,10 +76,8 @@ const ChartHandler = ({ { projectRef: ref as string, attribute: attribute as ProjectDailyStatsAttribute, - startDate, - endDate, - interval: interval as AnalyticsInterval, - databaseIdentifier, + startDate: dayjs(startDate).format('YYYY-MM-DD'), + endDate: dayjs(endDate).format('YYYY-MM-DD'), }, { enabled: provider === 'daily-stats' && data === undefined } ) diff --git a/apps/studio/components/ui/Charts/ComposedChartHandler.tsx b/apps/studio/components/ui/Charts/ComposedChartHandler.tsx index 10a87c65c564b..e931c7ec848cb 100644 --- a/apps/studio/components/ui/Charts/ComposedChartHandler.tsx +++ b/apps/studio/components/ui/Charts/ComposedChartHandler.tsx @@ -343,8 +343,6 @@ const useAttributeQueries = ( ref, startDate, endDate, - interval, - databaseIdentifier, data, isVisible ) diff --git a/apps/studio/components/ui/Charts/LogChartHandler.tsx b/apps/studio/components/ui/Charts/LogChartHandler.tsx index 8802c780dc37c..6a8e4569def80 100644 --- a/apps/studio/components/ui/Charts/LogChartHandler.tsx +++ b/apps/studio/components/ui/Charts/LogChartHandler.tsx @@ -1,19 +1,19 @@ -import React, { PropsWithChildren, useState, useEffect, useRef } from 'react' import { Loader2 } from 'lucide-react' +import React, { PropsWithChildren, useEffect, useRef, useState } from 'react' import { cn, WarningIcon } from 'ui' import Panel from 'components/ui/Panel' import { ComposedChart } from './ComposedChart' -import { AnalyticsInterval, DataPoint } from 'data/analytics/constants' -import { InfraMonitoringAttribute } from 'data/analytics/infra-monitoring-query' +import { AnalyticsInterval } from 'data/analytics/constants' import { useInfraMonitoringQueries } from 'data/analytics/infra-monitoring-queries' -import { ProjectDailyStatsAttribute } from 'data/analytics/project-daily-stats-query' +import { InfraMonitoringAttribute } from 'data/analytics/infra-monitoring-query' import { useProjectDailyStatsQueries } from 'data/analytics/project-daily-stats-queries' +import { ProjectDailyStatsAttribute } from 'data/analytics/project-daily-stats-query' import { useChartHighlight } from './useChartHighlight' -import type { ChartData } from './Charts.types' import type { UpdateDateRange } from 'pages/project/[ref]/reports/database' +import type { ChartData } from './Charts.types' import type { MultiAttribute } from './ComposedChart.utils' interface LogChartHandlerProps { @@ -22,7 +22,6 @@ interface LogChartHandlerProps { attributes: MultiAttribute[] startDate: string endDate: string - interval: string customDateFormat?: string defaultChartStyle?: 'bar' | 'line' | 'stackedAreaLine' hideChartType?: boolean @@ -196,8 +195,6 @@ export const useAttributeQueries = ( ref, startDate, endDate, - interval, - databaseIdentifier, data, isVisible ) diff --git a/apps/studio/data/analytics/project-daily-stats-queries.ts b/apps/studio/data/analytics/project-daily-stats-queries.ts index b373788579392..92e29be93f81c 100644 --- a/apps/studio/data/analytics/project-daily-stats-queries.ts +++ b/apps/studio/data/analytics/project-daily-stats-queries.ts @@ -1,14 +1,11 @@ import { useProjectDailyStatsQuery } from './project-daily-stats-query' import type { ProjectDailyStatsAttribute } from './project-daily-stats-query' -import { AnalyticsInterval } from './constants' export function useProjectDailyStatsQueries( attributes: ProjectDailyStatsAttribute[], ref: string | string[] | undefined, startDate: string, endDate: string, - interval: AnalyticsInterval, - databaseIdentifier: string | undefined, data: any, isVisible: boolean ) { @@ -19,8 +16,6 @@ export function useProjectDailyStatsQueries( attribute, startDate, endDate, - interval, - databaseIdentifier, }, { enabled: data === undefined && isVisible } ) diff --git a/apps/studio/data/analytics/project-daily-stats-query.ts b/apps/studio/data/analytics/project-daily-stats-query.ts index 98f31af24193a..f8bda24d30115 100644 --- a/apps/studio/data/analytics/project-daily-stats-query.ts +++ b/apps/studio/data/analytics/project-daily-stats-query.ts @@ -1,9 +1,8 @@ import { useQuery, UseQueryOptions } from '@tanstack/react-query' -import dayjs from 'dayjs' import { operations } from 'api-types' import { get, handleError } from 'data/fetchers' -import type { AnalyticsData, AnalyticsInterval } from './constants' +import type { AnalyticsData } from './constants' import { analyticsKeys } from './keys' export type ProjectDailyStatsAttribute = @@ -14,21 +13,10 @@ export type ProjectDailyStatsVariables = { attribute: ProjectDailyStatsAttribute startDate?: string endDate?: string - interval?: AnalyticsInterval - dateFormat?: string - databaseIdentifier?: string - modifier?: (x: number) => number } export async function getProjectDailyStats( - { - projectRef, - attribute, - startDate, - endDate, - interval = '1d', - databaseIdentifier, - }: ProjectDailyStatsVariables, + { projectRef, attribute, startDate, endDate }: ProjectDailyStatsVariables, signal?: AbortSignal ) { if (!projectRef) throw new Error('Project ref is required') @@ -43,9 +31,6 @@ export async function getProjectDailyStats( attribute, startDate, endDate, - interval, - // [Joshen] TODO: Once API support is ready - // databaseIdentifier, }, }, signal, @@ -59,16 +44,7 @@ export type ProjectDailyStatsData = Awaited( - { - projectRef, - attribute, - startDate, - endDate, - interval = '1d', - dateFormat = 'DD MMM', - databaseIdentifier, - modifier, - }: ProjectDailyStatsVariables, + { projectRef, attribute, startDate, endDate }: ProjectDailyStatsVariables, { enabled = true, ...options @@ -79,14 +55,8 @@ export const useProjectDailyStatsQuery = ( attribute, startDate, endDate, - interval, - databaseIdentifier, }), - ({ signal }) => - getProjectDailyStats( - { projectRef, attribute, startDate, endDate, interval, databaseIdentifier }, - signal - ), + ({ signal }) => getProjectDailyStats({ projectRef, attribute, startDate, endDate }, signal), { enabled: enabled && @@ -94,20 +64,7 @@ export const useProjectDailyStatsQuery = ( typeof attribute !== 'undefined' && typeof startDate !== 'undefined' && typeof endDate !== 'undefined', - select(data) { - return { - ...data, - data: data.data.map((x) => { - return { - ...x, - [attribute]: - modifier !== undefined ? modifier(Number(x[attribute])) : Number(x[attribute]), - periodStartFormatted: dayjs(x.period_start).format(dateFormat), - } - }), - } as TData - }, - staleTime: 1000 * 60, // default good for a minute + staleTime: 1000 * 60 * 30, // default good for 30m, stats only refresh once a day ...options, } ) From 49094c9cc724f8eb2dabfd0df03c046eded26b67 Mon Sep 17 00:00:00 2001 From: Dylan Jhaveri Date: Mon, 6 Oct 2025 02:50:08 -0700 Subject: [PATCH 5/5] docs: add a new Queues doc for Consuming Messages with Edge Functions (#38936) * add a new Queues doc for Consuming Messages with Edge Functions * pnpm run format * fix relative vs. abosolute URL from linter * Remove vale Co-authored-by: Chris Chinchilla * Re-wording Co-authored-by: Chris Chinchilla * Grammer and typo fix Co-authored-by: Chris Chinchilla * Grammer fix Co-authored-by: Chris Chinchilla * numbering markdown list Co-authored-by: Chris Chinchilla * numbering markdown list Co-authored-by: Chris Chinchilla * update link anchor language Co-authored-by: Chris Chinchilla * grammer fix Co-authored-by: Chris Chinchilla * grammar fix Co-authored-by: Chris Chinchilla --------- Co-authored-by: Chris Chinchilla --- .../NavigationMenu.constants.ts | 8 +- ...consuming-messages-with-edge-functions.mdx | 109 ++++++++++++++++++ 2 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 apps/docs/content/guides/queues/consuming-messages-with-edge-functions.mdx diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts index f4479bb3cc278..53963fb288310 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts @@ -1390,7 +1390,13 @@ export const queues: NavMenuConstant = { { name: 'Getting Started', url: undefined, - items: [{ name: 'Quickstart', url: '/guides/queues/quickstart' }], + items: [ + { name: 'Quickstart', url: '/guides/queues/quickstart' }, + { + name: 'Consuming Messages with Edge Functions', + url: '/guides/queues/consuming-messages-with-edge-functions', + }, + ], }, { name: 'References', diff --git a/apps/docs/content/guides/queues/consuming-messages-with-edge-functions.mdx b/apps/docs/content/guides/queues/consuming-messages-with-edge-functions.mdx new file mode 100644 index 0000000000000..8a890895fe688 --- /dev/null +++ b/apps/docs/content/guides/queues/consuming-messages-with-edge-functions.mdx @@ -0,0 +1,109 @@ +--- +title: Consuming Supabase Queue Messages with Edge Functions +subtitle: 'Learn how to consume Supabase Queue messages server-side with a Supabase Edge Function' +--- + +This guide helps you read & process queue messages server-side with a Supabase Edge Function. Read [Queues API Reference](/docs/guides/queues/api) for more details on our API. + +## Concepts + +Supabase Queues is a pull-based Message Queue consisting of three main components: Queues, Messages, and Queue Types. You should already be familiar with the [Queues Quickstart](/docs/guides/queues/quickstart). + +### Consuming messages in an Edge Function + +This is a Supabase Edge Function that reads 5 messages off the queue, processes each of them, and deletes each message when it is done. + +```tsx +import 'jsr:@supabase/functions-js/edge-runtime.d.ts' +import { createClient } from 'npm:@supabase/supabase-js@2' + +const supabaseUrl = 'supabaseURL' +const supabaseKey = 'supabaseKey' + +const supabase = createClient(supabaseUrl, supabaseKey) +const queueName = 'your_queue_name' + +// Type definition for queue messages +interface QueueMessage { + msg_id: bigint + read_ct: number + vt: string + enqueued_at: string + message: any +} + +async function processMessage(message: QueueMessage) { + // + // Do whatever logic you need to with the message content + // + // Delete the message from the queue + const { error: deleteError } = await supabase.schema('pgmq_public').rpc('delete', { + queue_name: queueName, + msg_id: message.msg_id, + }) + + if (deleteError) { + console.error(`Failed to delete message ${message.msg_id}:`, deleteError) + } else { + console.log(`Message ${message.msg_id} deleted from queue`) + } +} + +Deno.serve(async (req) => { + const { data: messages, error } = await supabase.schema('pgmq_public').rpc('read', { + queue_name: queueName, + sleep_seconds: 0, // Don't wait if queue is empty + n: 5, // Read 5 messages off the queue + }) + + if (error) { + console.error(`Error reading from ${queueName} queue:`, error) + return new Response(JSON.stringify({ error: error.message }), { + status: 500, + headers: { 'Content-Type': 'application/json' }, + }) + } + + if (!messages || messages.length === 0) { + console.log('No messages in workflow_messages queue') + return new Response(JSON.stringify({ message: 'No messages in queue' }), { + status: 200, + headers: { 'Content-Type': 'application/json' }, + }) + } + + console.log(`Found ${messages.length} messages to process`) + + // Process each message that was read off the queue + for (const message of messages) { + try { + await processMessage(message as QueueMessage) + } catch (error) { + console.error(`Error processing message ${message.msg_id}:`, error) + } + } + + // Return immediately while background processing continues + return new Response( + JSON.stringify({ + message: `Processing ${messages.length} messages in background`, + count: messages.length, + }), + { + status: 200, + headers: { 'Content-Type': 'application/json' }, + } + ) +}) +``` + +Every time this Edge Function is run it: + +1. Read 5 messages off the queue +2. Call the `processMessage` function +3. At the end of `processMessage`, the message is deleted from the queue +4. If `processMessage` throws an error, the error is logged. In this case, the message is still in the queue, so the next time this Edge Function runs it reads the message again. + +You might find this kind of setup handy to run with [Supabase Cron](/docs/guides/cron). You can set up Cron so that every N number of minutes or seconds, the Edge Function will run and process a number of messages off the queue. + +Similarly, you can invoke the Edge Function on command at any given time with [`supabase.functions.invoke`](/docs/guides/functions/quickstart-dashboard#usage).