Files
supabase/apps/studio/data/storage/keys.ts
T
Francesco Sansalvadore 25708ed67a chore(studio): storage versioning override conflict (#51593)
## Problem

When uploading a file with same name of already existing file, the ui
automatically creates a new file with incremental number and doesn't
consider the scenario of giving the possibility of storing the new
upload as a version of the already existing file.

## Solution

Provide modal to chose the behaviour when name conflict happens when
uploading a file:
- Replace existing file (same file with version history)
- Keep both (new file with incremental numbering)


https://github.com/user-attachments/assets/ee192be9-4087-4e14-89f1-1c82acf550cd

Manage edge case of clashes with files created when bucket had
versioning enabled (so with `is_versioned` flag to true) and clashing
upload happens when bucket is in SUSPENDED state.


https://github.com/user-attachments/assets/00694c77-07c1-405e-bd54-bdf0bf99d274

Unrelated fix:
- add "Delete permanently" action when selecting multiple files with
versioning enabled

<img width="510" height="203" alt="Screenshot 2026-10-13 at 15 59 27"
src="https://github.com/user-attachments/assets/5692fc23-2fd3-4259-9de2-3932aecbccd0"
/>
2026-10-13 17:14:30 +02:00

128 lines
4.1 KiB
TypeScript

export const storageKeys = {
bucket: (projectRef: string | undefined, bucketId: string | undefined) =>
['projects', projectRef, 'buckets', bucketId] as const,
buckets: (projectRef: string | undefined) => ['projects', projectRef, 'buckets'] as const,
bucketsList: (
projectRef: string | undefined,
params: {
limit?: number
search?: string
sortColumn?: string
sortOrder?: string
} = {}
) =>
[
'projects',
projectRef,
'buckets',
'list',
{
limit: params.limit,
search: params.search,
sortColumn: params.sortColumn,
sortOrder: params.sortOrder,
},
] as const,
analyticsBuckets: (projectRef: string | undefined) =>
['projects', projectRef, 'analytics-buckets'] as const,
vectorBuckets: (projectRef: string | undefined) =>
['projects', projectRef, 'vector-buckets'] as const,
vectorBucket: (projectRef: string | undefined, vectorbucketName: string | undefined) =>
['projects', projectRef, 'vector-bucket', vectorbucketName] as const,
vectorBucketsIndexes: (projectRef: string | undefined, vectorBucketName: string | undefined) =>
['projects', projectRef, 'vector-buckets', vectorBucketName, 'indexes'] as const,
archive: (projectRef: string | undefined) => ['projects', projectRef, 'archive'] as const,
publicBucketsWithSelectPolicies: (projectRef: string | undefined, bucketId: string | undefined) =>
['projects', projectRef, 'public-buckets-with-select-policies', bucketId] as const,
objects: (
projectRef: string | undefined,
bucketId: string | undefined,
path: string,
params: {
limit?: number
search?: string
sortColumn?: string
sortOrder?: string
} = {}
) =>
[
'projects',
projectRef,
'buckets',
bucketId,
'objects',
...(path ? [path] : []),
...(params ? [params] : []),
] as const,
folders: (projectRef: string | undefined, bucketId: string | undefined) =>
['projects', projectRef, 'buckets', bucketId, 'folders'] as const,
bucketLifecycle: (projectRef: string | undefined, bucketId: string | undefined) =>
['projects', projectRef, 'buckets', bucketId, 'lifecycle'] as const,
/** The policy is part of the key: the API derives each version's expiry from it. */
objectVersions: (
projectRef: string | undefined,
bucketId: string | undefined,
objectName: string | undefined,
lifecyclePolicy?: { expiryDays: number | null; maxVersions: number | null }
) =>
[
'projects',
projectRef,
'buckets',
bucketId,
'object-versions',
objectName,
...(lifecyclePolicy ? [lifecyclePolicy] : []),
] as const,
/** Omit `versionId` for a prefix that matches every version's URL for one object. */
fileUrl: ({
projectRef,
isBucketPublic,
bucketId,
path,
versionId,
}: {
projectRef?: string
isBucketPublic?: boolean
bucketId?: string
path: string
versionId?: string
}) =>
[
projectRef,
'buckets',
isBucketPublic,
bucketId,
'file',
path,
...(versionId === undefined ? [] : [versionId]),
] as const,
archivedObjects: (projectRef: string | undefined, bucketId: string | undefined) =>
['projects', projectRef, 'buckets', bucketId, 'archived-objects'] as const,
archivedObjectNames: (
projectRef: string | undefined,
bucketId: string | undefined,
path: string
) => ['projects', projectRef, 'buckets', bucketId, 'archived-object-names', path] as const,
icebergNamespaces: ({ projectRef, warehouse }: { projectRef?: string; warehouse?: string }) =>
[projectRef, 'warehouse', warehouse, 'namespaces'] as const,
icebergNamespace: ({
projectRef,
warehouse,
namespace,
}: {
projectRef?: string
warehouse: string
namespace: string
}) => [projectRef, 'warehouse', warehouse, 'namespaces', namespace] as const,
icebergNamespaceTables: ({
projectRef,
warehouse,
namespace,
}: {
projectRef?: string
warehouse?: string
namespace?: string
}) => [projectRef, 'warehouse', warehouse, 'namespaces', namespace, 'tables'] as const,
}