@supabase/storage-js 2.5.5 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/main/lib/fetch.d.ts +2 -1
  2. package/dist/main/lib/fetch.d.ts.map +1 -1
  3. package/dist/main/lib/fetch.js +13 -5
  4. package/dist/main/lib/fetch.js.map +1 -1
  5. package/dist/main/lib/helpers.d.ts +1 -0
  6. package/dist/main/lib/helpers.d.ts.map +1 -1
  7. package/dist/main/lib/helpers.js +16 -1
  8. package/dist/main/lib/helpers.js.map +1 -1
  9. package/dist/main/lib/types.d.ts +31 -0
  10. package/dist/main/lib/types.d.ts.map +1 -1
  11. package/dist/main/lib/version.d.ts +1 -1
  12. package/dist/main/lib/version.js +1 -1
  13. package/dist/main/packages/StorageFileApi.d.ts +37 -4
  14. package/dist/main/packages/StorageFileApi.d.ts.map +1 -1
  15. package/dist/main/packages/StorageFileApi.js +91 -7
  16. package/dist/main/packages/StorageFileApi.js.map +1 -1
  17. package/dist/module/lib/fetch.d.ts +2 -1
  18. package/dist/module/lib/fetch.d.ts.map +1 -1
  19. package/dist/module/lib/fetch.js +11 -4
  20. package/dist/module/lib/fetch.js.map +1 -1
  21. package/dist/module/lib/helpers.d.ts +1 -0
  22. package/dist/module/lib/helpers.d.ts.map +1 -1
  23. package/dist/module/lib/helpers.js +14 -0
  24. package/dist/module/lib/helpers.js.map +1 -1
  25. package/dist/module/lib/types.d.ts +31 -0
  26. package/dist/module/lib/types.d.ts.map +1 -1
  27. package/dist/module/lib/version.d.ts +1 -1
  28. package/dist/module/lib/version.js +1 -1
  29. package/dist/module/packages/StorageFileApi.d.ts +37 -4
  30. package/dist/module/packages/StorageFileApi.d.ts.map +1 -1
  31. package/dist/module/packages/StorageFileApi.js +94 -10
  32. package/dist/module/packages/StorageFileApi.js.map +1 -1
  33. package/dist/umd/supabase.js +1 -1
  34. package/package.json +2 -2
  35. package/src/lib/fetch.ts +30 -5
  36. package/src/lib/helpers.ts +16 -0
  37. package/src/lib/types.ts +38 -0
  38. package/src/lib/version.ts +1 -1
  39. package/src/packages/StorageFileApi.ts +135 -12
package/src/lib/fetch.ts CHANGED
@@ -11,15 +11,19 @@ export interface FetchOptions {
11
11
  noResolveJson?: boolean
12
12
  }
13
13
 
14
- export type RequestMethodType = 'GET' | 'POST' | 'PUT' | 'DELETE'
14
+ export type RequestMethodType = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'HEAD'
15
15
 
16
16
  const _getErrorMessage = (err: any): string =>
17
17
  err.msg || err.message || err.error_description || err.error || JSON.stringify(err)
18
18
 
19
- const handleError = async (error: unknown, reject: (reason?: any) => void) => {
19
+ const handleError = async (
20
+ error: unknown,
21
+ reject: (reason?: any) => void,
22
+ options?: FetchOptions
23
+ ) => {
20
24
  const Res = await resolveResponse()
21
25
 
22
- if (error instanceof Res) {
26
+ if (error instanceof Res && !options?.noResolveJson) {
23
27
  error
24
28
  .json()
25
29
  .then((err) => {
@@ -46,7 +50,10 @@ const _getRequestParams = (
46
50
  }
47
51
 
48
52
  params.headers = { 'Content-Type': 'application/json', ...options?.headers }
49
- params.body = JSON.stringify(body)
53
+
54
+ if (body) {
55
+ params.body = JSON.stringify(body)
56
+ }
50
57
  return { ...params, ...parameters }
51
58
  }
52
59
 
@@ -66,7 +73,7 @@ async function _handleRequest(
66
73
  return result.json()
67
74
  })
68
75
  .then((data) => resolve(data))
69
- .catch((error) => handleError(error, reject))
76
+ .catch((error) => handleError(error, reject, options))
70
77
  })
71
78
  }
72
79
 
@@ -99,6 +106,24 @@ export async function put(
99
106
  return _handleRequest(fetcher, 'PUT', url, options, parameters, body)
100
107
  }
101
108
 
109
+ export async function head(
110
+ fetcher: Fetch,
111
+ url: string,
112
+ options?: FetchOptions,
113
+ parameters?: FetchParameters
114
+ ): Promise<any> {
115
+ return _handleRequest(
116
+ fetcher,
117
+ 'HEAD',
118
+ url,
119
+ {
120
+ ...options,
121
+ noResolveJson: true,
122
+ },
123
+ parameters
124
+ )
125
+ }
126
+
102
127
  export async function remove(
103
128
  fetcher: Fetch,
104
129
  url: string,
@@ -21,3 +21,19 @@ export const resolveResponse = async (): Promise<typeof Response> => {
21
21
 
22
22
  return Response
23
23
  }
24
+
25
+ export const recursiveToCamel = (item: Record<string, any>): unknown => {
26
+ if (Array.isArray(item)) {
27
+ return item.map((el) => recursiveToCamel(el))
28
+ } else if (typeof item === 'function' || item !== Object(item)) {
29
+ return item
30
+ }
31
+
32
+ const result: Record<string, any> = {}
33
+ Object.entries(item).forEach(([key, value]) => {
34
+ const newKey = key.replace(/([-_][a-z])/gi, (c) => c.toUpperCase().replace(/[-_]/g, ''))
35
+ result[newKey] = recursiveToCamel(value)
36
+ })
37
+
38
+ return result
39
+ }
package/src/lib/types.ts CHANGED
@@ -21,6 +21,22 @@ export interface FileObject {
21
21
  buckets: Bucket
22
22
  }
23
23
 
24
+ export interface FileObjectV2 {
25
+ id: string
26
+ version: string
27
+ name: string
28
+ bucket_id: string
29
+ updated_at: string
30
+ created_at: string
31
+ last_accessed_at: string
32
+ size?: number
33
+ cache_control?: string
34
+ content_type?: string
35
+ etag?: string
36
+ last_modified?: string
37
+ metadata?: Record<string, any>
38
+ }
39
+
24
40
  export interface SortBy {
25
41
  column?: string
26
42
  order?: string
@@ -43,6 +59,20 @@ export interface FileOptions {
43
59
  * The duplex option is a string parameter that enables or disables duplex streaming, allowing for both reading and writing data in the same stream. It can be passed as an option to the fetch() method.
44
60
  */
45
61
  duplex?: string
62
+
63
+ /**
64
+ * The metadata option is an object that allows you to store additional information about the file. This information can be used to filter and search for files. The metadata object can contain any key-value pairs you want to store.
65
+ */
66
+ metadata?: Record<string, any>
67
+
68
+ /**
69
+ * Optionally add extra headers
70
+ */
71
+ headers?: Record<string, string>
72
+ }
73
+
74
+ export interface DestinationOptions {
75
+ destinationBucket?: string
46
76
  }
47
77
 
48
78
  export interface SearchOptions {
@@ -109,3 +139,11 @@ export interface TransformOptions {
109
139
  */
110
140
  format?: 'origin'
111
141
  }
142
+
143
+ type CamelCase<S extends string> = S extends `${infer P1}_${infer P2}${infer P3}`
144
+ ? `${Lowercase<P1>}${Uppercase<P2>}${CamelCase<P3>}`
145
+ : S
146
+
147
+ export type Camelize<T> = {
148
+ [K in keyof T as CamelCase<Extract<K, string>>]: T[K]
149
+ }
@@ -1,2 +1,2 @@
1
1
  // generated by genversion
2
- export const version = '2.5.5'
2
+ export const version = '2.7.0'
@@ -1,12 +1,15 @@
1
- import { isStorageError, StorageError } from '../lib/errors'
2
- import { Fetch, get, post, remove } from '../lib/fetch'
3
- import { resolveFetch } from '../lib/helpers'
1
+ import { isStorageError, StorageError, StorageUnknownError } from '../lib/errors'
2
+ import { Fetch, get, head, post, remove } from '../lib/fetch'
3
+ import { recursiveToCamel, resolveFetch } from '../lib/helpers'
4
4
  import {
5
5
  FileObject,
6
6
  FileOptions,
7
7
  SearchOptions,
8
8
  FetchParameters,
9
9
  TransformOptions,
10
+ DestinationOptions,
11
+ FileObjectV2,
12
+ Camelize,
10
13
  } from '../lib/types'
11
14
 
12
15
  const DEFAULT_SEARCH_OPTIONS = {
@@ -79,22 +82,39 @@ export default class StorageFileApi {
79
82
  try {
80
83
  let body
81
84
  const options = { ...DEFAULT_FILE_OPTIONS, ...fileOptions }
82
- const headers: Record<string, string> = {
85
+ let headers: Record<string, string> = {
83
86
  ...this.headers,
84
87
  ...(method === 'POST' && { 'x-upsert': String(options.upsert as boolean) }),
85
88
  }
86
89
 
90
+ const metadata = options.metadata
91
+
87
92
  if (typeof Blob !== 'undefined' && fileBody instanceof Blob) {
88
93
  body = new FormData()
89
94
  body.append('cacheControl', options.cacheControl as string)
90
95
  body.append('', fileBody)
96
+
97
+ if (metadata) {
98
+ body.append('metadata', this.encodeMetadata(metadata))
99
+ }
91
100
  } else if (typeof FormData !== 'undefined' && fileBody instanceof FormData) {
92
101
  body = fileBody
93
102
  body.append('cacheControl', options.cacheControl as string)
103
+ if (metadata) {
104
+ body.append('metadata', this.encodeMetadata(metadata))
105
+ }
94
106
  } else {
95
107
  body = fileBody
96
108
  headers['cache-control'] = `max-age=${options.cacheControl}`
97
109
  headers['content-type'] = options.contentType as string
110
+
111
+ if (metadata) {
112
+ headers['x-metadata'] = this.toBase64(this.encodeMetadata(metadata))
113
+ }
114
+ }
115
+
116
+ if (fileOptions?.headers) {
117
+ headers = { ...headers, ...fileOptions.headers }
98
118
  }
99
119
 
100
120
  const cleanPath = this._removeEmptyFolders(path)
@@ -138,7 +158,7 @@ export default class StorageFileApi {
138
158
  fileOptions?: FileOptions
139
159
  ): Promise<
140
160
  | {
141
- data: { path: string }
161
+ data: { id: string; path: string; fullPath: string }
142
162
  error: null
143
163
  }
144
164
  | {
@@ -219,9 +239,11 @@ export default class StorageFileApi {
219
239
  * Signed upload URLs can be used to upload files to the bucket without further authentication.
220
240
  * They are valid for 2 hours.
221
241
  * @param path The file path, including the current file name. For example `folder/image.png`.
242
+ * @param options.upsert If set to true, allows the file to be overwritten if it already exists.
222
243
  */
223
244
  async createSignedUploadUrl(
224
- path: string
245
+ path: string,
246
+ options?: { upsert: boolean }
225
247
  ): Promise<
226
248
  | {
227
249
  data: { signedUrl: string; token: string; path: string }
@@ -235,11 +257,17 @@ export default class StorageFileApi {
235
257
  try {
236
258
  let _path = this._getFinalPath(path)
237
259
 
260
+ const headers = { ...this.headers }
261
+
262
+ if (options?.upsert) {
263
+ headers['x-upsert'] = 'true'
264
+ }
265
+
238
266
  const data = await post(
239
267
  this.fetch,
240
268
  `${this.url}/object/upload/sign/${_path}`,
241
269
  {},
242
- { headers: this.headers }
270
+ { headers }
243
271
  )
244
272
 
245
273
  const url = new URL(this.url + data.url)
@@ -282,7 +310,7 @@ export default class StorageFileApi {
282
310
  fileOptions?: FileOptions
283
311
  ): Promise<
284
312
  | {
285
- data: { path: string }
313
+ data: { id: string; path: string; fullPath: string }
286
314
  error: null
287
315
  }
288
316
  | {
@@ -298,10 +326,12 @@ export default class StorageFileApi {
298
326
  *
299
327
  * @param fromPath The original file path, including the current file name. For example `folder/image.png`.
300
328
  * @param toPath The new file path, including the new file name. For example `folder/image-new.png`.
329
+ * @param options The destination options.
301
330
  */
302
331
  async move(
303
332
  fromPath: string,
304
- toPath: string
333
+ toPath: string,
334
+ options?: DestinationOptions
305
335
  ): Promise<
306
336
  | {
307
337
  data: { message: string }
@@ -316,7 +346,12 @@ export default class StorageFileApi {
316
346
  const data = await post(
317
347
  this.fetch,
318
348
  `${this.url}/object/move`,
319
- { bucketId: this.bucketId, sourceKey: fromPath, destinationKey: toPath },
349
+ {
350
+ bucketId: this.bucketId,
351
+ sourceKey: fromPath,
352
+ destinationKey: toPath,
353
+ destinationBucket: options?.destinationBucket,
354
+ },
320
355
  { headers: this.headers }
321
356
  )
322
357
  return { data, error: null }
@@ -334,10 +369,12 @@ export default class StorageFileApi {
334
369
  *
335
370
  * @param fromPath The original file path, including the current file name. For example `folder/image.png`.
336
371
  * @param toPath The new file path, including the new file name. For example `folder/image-copy.png`.
372
+ * @param options The destination options.
337
373
  */
338
374
  async copy(
339
375
  fromPath: string,
340
- toPath: string
376
+ toPath: string,
377
+ options?: DestinationOptions
341
378
  ): Promise<
342
379
  | {
343
380
  data: { path: string }
@@ -352,7 +389,12 @@ export default class StorageFileApi {
352
389
  const data = await post(
353
390
  this.fetch,
354
391
  `${this.url}/object/copy`,
355
- { bucketId: this.bucketId, sourceKey: fromPath, destinationKey: toPath },
392
+ {
393
+ bucketId: this.bucketId,
394
+ sourceKey: fromPath,
395
+ destinationKey: toPath,
396
+ destinationBucket: options?.destinationBucket,
397
+ },
356
398
  { headers: this.headers }
357
399
  )
358
400
  return { data: { path: data.Key }, error: null }
@@ -502,6 +544,76 @@ export default class StorageFileApi {
502
544
  }
503
545
  }
504
546
 
547
+ /**
548
+ * Retrieves the details of an existing file.
549
+ * @param path
550
+ */
551
+ async info(
552
+ path: string
553
+ ): Promise<
554
+ | {
555
+ data: Camelize<FileObjectV2>
556
+ error: null
557
+ }
558
+ | {
559
+ data: null
560
+ error: StorageError
561
+ }
562
+ > {
563
+ const _path = this._getFinalPath(path)
564
+
565
+ try {
566
+ const data = await get(this.fetch, `${this.url}/object/info/${_path}`, {
567
+ headers: this.headers,
568
+ })
569
+
570
+ return { data: recursiveToCamel(data) as Camelize<FileObjectV2>, error: null }
571
+ } catch (error) {
572
+ if (isStorageError(error)) {
573
+ return { data: null, error }
574
+ }
575
+
576
+ throw error
577
+ }
578
+ }
579
+
580
+ /**
581
+ * Checks the existence of a file.
582
+ * @param path
583
+ */
584
+ async exists(
585
+ path: string
586
+ ): Promise<
587
+ | {
588
+ data: boolean
589
+ error: null
590
+ }
591
+ | {
592
+ data: boolean
593
+ error: StorageError
594
+ }
595
+ > {
596
+ const _path = this._getFinalPath(path)
597
+
598
+ try {
599
+ await head(this.fetch, `${this.url}/object/${_path}`, {
600
+ headers: this.headers,
601
+ })
602
+
603
+ return { data: true, error: null }
604
+ } catch (error) {
605
+ if (isStorageError(error) && error instanceof StorageUnknownError) {
606
+ const originalError = (error.originalError as unknown) as { status: number }
607
+
608
+ if ([400, 404].includes(originalError?.status)) {
609
+ return { data: false, error }
610
+ }
611
+ }
612
+
613
+ throw error
614
+ }
615
+ }
616
+
505
617
  /**
506
618
  * A simple convenience function to get the URL for an asset in a public bucket. If you do not want to use this function, you can construct the public URL by concatenating the bucket URL with the path to the asset.
507
619
  * This function does not verify if the bucket is public. If a public URL is created for a bucket which is not public, you will not be able to download the asset.
@@ -677,6 +789,17 @@ export default class StorageFileApi {
677
789
  }
678
790
  }
679
791
 
792
+ protected encodeMetadata(metadata: Record<string, any>) {
793
+ return JSON.stringify(metadata)
794
+ }
795
+
796
+ toBase64(data: string) {
797
+ if (typeof Buffer !== 'undefined') {
798
+ return Buffer.from(data).toString('base64')
799
+ }
800
+ return btoa(data)
801
+ }
802
+
680
803
  private _getFinalPath(path: string) {
681
804
  return `${this.bucketId}/${path}`
682
805
  }