@directus/storage-driver-supabase 2.0.1 → 2.1.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.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Driver, Range } from '@directus/storage';
1
+ import { TusDriver, ReadOptions, ChunkedUploadContext } from '@directus/storage';
2
2
  import { Readable } from 'node:stream';
3
3
 
4
4
  type DriverSupabaseConfig = {
@@ -8,18 +8,23 @@ type DriverSupabaseConfig = {
8
8
  /** Allows a custom Supabase endpoint for self-hosting */
9
9
  endpoint?: string;
10
10
  root?: string;
11
+ tus?: {
12
+ chunkSize?: number;
13
+ };
11
14
  };
12
- declare class DriverSupabase implements Driver {
15
+ declare class DriverSupabase implements TusDriver {
13
16
  private config;
14
17
  private client;
15
18
  private bucket;
19
+ private readonly preferredChunkSize;
16
20
  constructor(config: DriverSupabaseConfig);
17
21
  private get endpoint();
18
22
  private getClient;
19
23
  private getBucket;
20
24
  private fullPath;
21
25
  private getAuthenticatedUrl;
22
- read(filepath: string, range?: Range): Promise<Readable>;
26
+ private getResumableUrl;
27
+ read(filepath: string, options?: ReadOptions): Promise<Readable>;
23
28
  stat(filepath: string): Promise<{
24
29
  size: any;
25
30
  modified: Date;
@@ -31,6 +36,11 @@ declare class DriverSupabase implements Driver {
31
36
  delete(filepath: string): Promise<void>;
32
37
  list(prefix?: string): AsyncIterable<string>;
33
38
  listGenerator(prefix: string): AsyncIterable<string>;
39
+ get tusExtensions(): string[];
40
+ createChunkedUpload(_filepath: string, context: ChunkedUploadContext): Promise<ChunkedUploadContext>;
41
+ writeChunk(filepath: string, content: Readable, offset: number, context: ChunkedUploadContext): Promise<number>;
42
+ finishChunkedUpload(_filepath: string, _context: ChunkedUploadContext): Promise<void>;
43
+ deleteChunkedUpload(filepath: string, _context: ChunkedUploadContext): Promise<void>;
34
44
  }
35
45
 
36
46
  export { DriverSupabase, type DriverSupabaseConfig, DriverSupabase as default };
package/dist/index.js CHANGED
@@ -1,18 +1,23 @@
1
1
  // src/index.ts
2
2
  import { normalizePath } from "@directus/utils";
3
3
  import { StorageClient } from "@supabase/storage-js";
4
+ import * as tus from "tus-js-client";
4
5
  import { join } from "node:path";
5
6
  import { Readable } from "node:stream";
6
7
  import { fetch } from "undici";
8
+ var DEFAULT_CHUNK_SIZE = 1e7;
7
9
  var DriverSupabase = class {
8
10
  config;
9
11
  client;
10
12
  bucket;
13
+ // TUS specific members
14
+ preferredChunkSize;
11
15
  constructor(config) {
12
16
  this.config = {
13
17
  ...config,
14
18
  root: normalizePath(config.root ?? "", { removeLeading: true })
15
19
  };
20
+ this.preferredChunkSize = this.config.tus?.chunkSize ?? DEFAULT_CHUNK_SIZE;
16
21
  this.client = this.getClient();
17
22
  this.bucket = this.getBucket();
18
23
  }
@@ -45,7 +50,11 @@ var DriverSupabase = class {
45
50
  getAuthenticatedUrl(filepath) {
46
51
  return `${this.endpoint}/${join("object/authenticated", this.config.bucket, this.fullPath(filepath))}`;
47
52
  }
48
- async read(filepath, range) {
53
+ getResumableUrl() {
54
+ return `${this.endpoint}/upload/resumable`;
55
+ }
56
+ async read(filepath, options) {
57
+ const { range } = options || {};
49
58
  const requestInit = { method: "GET" };
50
59
  requestInit.headers = {
51
60
  Authorization: `Bearer ${this.config.serviceRole}`
@@ -129,11 +138,85 @@ var DriverSupabase = class {
129
138
  }
130
139
  } while (itemCount === limit);
131
140
  }
141
+ get tusExtensions() {
142
+ return ["creation", "termination", "expiration"];
143
+ }
144
+ async createChunkedUpload(_filepath, context) {
145
+ return context;
146
+ }
147
+ async writeChunk(filepath, content, offset, context) {
148
+ let bytesUploaded = offset || 0;
149
+ const metadata = {
150
+ bucketName: this.config.bucket,
151
+ objectName: this.fullPath(filepath),
152
+ contentType: context.metadata["type"] ?? "image/png",
153
+ cacheControl: "3600"
154
+ };
155
+ await new Promise((resolve, reject) => {
156
+ const upload = new tus.Upload(content, {
157
+ endpoint: this.getResumableUrl(),
158
+ // @ts-expect-error
159
+ fileReader: new FileReader(),
160
+ headers: {
161
+ Authorization: `Bearer ${this.config.serviceRole}`,
162
+ "x-upsert": "true"
163
+ },
164
+ metadata,
165
+ chunkSize: this.preferredChunkSize,
166
+ uploadSize: context.size,
167
+ retryDelays: null,
168
+ onError(error) {
169
+ reject(error);
170
+ },
171
+ onChunkComplete: function(chunkSize) {
172
+ bytesUploaded += chunkSize;
173
+ resolve(null);
174
+ },
175
+ onSuccess() {
176
+ resolve(null);
177
+ },
178
+ onUploadUrlAvailable() {
179
+ if (!context.metadata["upload-url"]) {
180
+ context.metadata["upload-url"] = upload.url;
181
+ }
182
+ }
183
+ });
184
+ if (context.metadata["upload-url"]) {
185
+ upload.resumeFromPreviousUpload({
186
+ size: context.size,
187
+ creationTime: context.metadata["creation_date"],
188
+ metadata,
189
+ uploadUrl: context.metadata["upload-url"]
190
+ });
191
+ }
192
+ upload.start();
193
+ });
194
+ return bytesUploaded;
195
+ }
196
+ async finishChunkedUpload(_filepath, _context) {
197
+ }
198
+ async deleteChunkedUpload(filepath, _context) {
199
+ await this.delete(filepath);
200
+ }
132
201
  };
133
202
  var src_default = DriverSupabase;
134
203
  function dirname(path) {
135
204
  return path.split("/").slice(0, -1).join("/");
136
205
  }
206
+ var StreamSource2 = class extends tus.StreamSource {
207
+ _streamEnded = false;
208
+ // @ts-expect-error
209
+ async slice(start, end) {
210
+ if (this._streamEnded) return null;
211
+ this._streamEnded = true;
212
+ return super.slice(0, end - start);
213
+ }
214
+ };
215
+ var FileReader = class {
216
+ async openFile(input, _) {
217
+ return new StreamSource2(input);
218
+ }
219
+ };
137
220
  export {
138
221
  DriverSupabase,
139
222
  src_default as default
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@directus/storage-driver-supabase",
3
- "version": "2.0.1",
3
+ "version": "2.1.0",
4
4
  "description": "Supabase file storage abstraction for `@directus/storage`",
5
5
  "homepage": "https://directus.io",
6
6
  "repository": {
@@ -21,16 +21,17 @@
21
21
  "dist"
22
22
  ],
23
23
  "dependencies": {
24
- "@supabase/storage-js": "2.6.0",
25
- "undici": "6.19.5",
26
- "@directus/utils": "12.0.1",
27
- "@directus/storage": "11.0.0"
24
+ "@supabase/storage-js": "2.7.1",
25
+ "tus-js-client": "4.2.3",
26
+ "undici": "6.20.0",
27
+ "@directus/storage": "11.0.1",
28
+ "@directus/utils": "12.0.3"
28
29
  },
29
30
  "devDependencies": {
30
31
  "@ngneat/falso": "7.2.0",
31
32
  "@vitest/coverage-v8": "2.1.2",
32
- "tsup": "8.2.4",
33
- "typescript": "5.6.2",
33
+ "tsup": "8.3.0",
34
+ "typescript": "5.6.3",
34
35
  "vitest": "2.1.2",
35
36
  "@directus/tsconfig": "2.0.0"
36
37
  },