@fileway/supabase 0.0.0-stage → 0.6.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.js ADDED
@@ -0,0 +1,459 @@
1
+ // src/errors.ts
2
+ function classifySupabaseError(status, body) {
3
+ let parsed = {};
4
+ try {
5
+ parsed = JSON.parse(body);
6
+ } catch {
7
+ }
8
+ const text = `${parsed.error ?? ""} ${parsed.message ?? ""} ${parsed.error === void 0 ? body : ""}`.toLowerCase();
9
+ const code = Number(parsed.statusCode ?? status);
10
+ if (text.includes("bucket not found")) return "bucket-not-found";
11
+ if (code === 409 || text.includes("already exists") || text.includes("duplicate")) return "conflict";
12
+ if (text.includes("not found") || text.includes("not_found") || text.includes("no such")) return "not-found";
13
+ if (text.includes("exceeded the maximum allowed size") || text.includes("payload too large") || code === 413) {
14
+ return "size-exceeded";
15
+ }
16
+ if (code === 401 || code === 403 || text.includes("jwt") || text.includes("unauthorized") || text.includes("row-level security")) {
17
+ return "auth-failed";
18
+ }
19
+ if (code === 429 || text.includes("too many requests")) return "rate-limited";
20
+ if (code === 404) return "not-found";
21
+ return "provider-error";
22
+ }
23
+
24
+ // src/index.ts
25
+ import { validateUploadOptions, ValidationError, urlEncodePath, abortError, isAbortError, StorageError, encodeCursor, decodeCursor } from "@fileway/core";
26
+ var randomUUID = () => globalThis.crypto.randomUUID();
27
+ var PROVIDER = "supabase";
28
+ var TUS_CHUNK_SIZE = 6 * 1024 * 1024;
29
+ var TUS_VERSION = "1.0.0";
30
+ var PRESIGN_DEFAULT_EXPIRY = 3600;
31
+ var TOKEN_KEY = "fileway-upload-token";
32
+ var MAX_CHUNK_RESENDS = 3;
33
+ var SupabaseDriver = class {
34
+ name = "supabase";
35
+ api;
36
+ bucket;
37
+ baseUrl;
38
+ headers;
39
+ maxSizeBytes;
40
+ cacheControl;
41
+ constructor(config) {
42
+ if (!config.url || !config.key || !config.bucket) {
43
+ throw new StorageError("config", "SupabaseDriver requires url, key and bucket", { provider: PROVIDER });
44
+ }
45
+ this.api = `${config.url.replace(/\/+$/, "")}/storage/v1`;
46
+ this.bucket = config.bucket;
47
+ this.baseUrl = (config.baseUrl ?? `${this.api}/object/public/${config.bucket}`).replace(/\/+$/, "");
48
+ const bearer = config.accessToken ?? (config.key.startsWith("eyJ") ? config.key : void 0);
49
+ this.headers = { apikey: config.key, ...bearer ? { authorization: `Bearer ${bearer}` } : {} };
50
+ this.maxSizeBytes = config.maxSizeBytes;
51
+ this.cacheControl = `max-age=${config.cacheControl ?? 3600}`;
52
+ }
53
+ async upload(stream, options, context) {
54
+ validateUploadOptions(options);
55
+ if (this.maxSizeBytes !== void 0 && options.size !== void 0 && options.size > this.maxSizeBytes) {
56
+ throw new StorageError("size-exceeded", `file size ${options.size} exceeds maxSizeBytes ${this.maxSizeBytes}`, { provider: PROVIDER });
57
+ }
58
+ const id = options.id ?? randomUUID();
59
+ const path = this.pathFor({ ...options, id });
60
+ const contentType = options.mimeType ?? "application/octet-stream";
61
+ const metadata = { ...options.metadata, ...context?.uploadToken ? { [TOKEN_KEY]: context.uploadToken } : {} };
62
+ const signal = options.signal;
63
+ const reader = stream.getReader();
64
+ let aborted = signal?.aborted ?? false;
65
+ const onAbort = () => {
66
+ aborted = true;
67
+ reader.cancel().catch(() => {
68
+ });
69
+ };
70
+ if (signal && !aborted) signal.addEventListener("abort", onAbort, { once: true });
71
+ let totalBytes = 0;
72
+ const read = async () => {
73
+ const { done, value } = await reader.read();
74
+ if (aborted) throw abortError("upload aborted");
75
+ if (done) return void 0;
76
+ totalBytes += value.byteLength;
77
+ if (this.maxSizeBytes !== void 0 && totalBytes > this.maxSizeBytes) {
78
+ throw new StorageError("size-exceeded", `upload exceeds maxSizeBytes ${this.maxSizeBytes}`, { provider: PROVIDER });
79
+ }
80
+ return value;
81
+ };
82
+ const checkSize = () => {
83
+ if (options.size !== void 0 && totalBytes !== options.size) {
84
+ throw new ValidationError(`stream delivered ${totalBytes} bytes, expected ${options.size}`);
85
+ }
86
+ };
87
+ let tusUrl;
88
+ try {
89
+ let etag;
90
+ if (options.size !== void 0 && options.size > TUS_CHUNK_SIZE) {
91
+ tusUrl = await this.createTusUpload(path, options.size, contentType, metadata, signal);
92
+ let buffer = [];
93
+ let bufferedLen = 0;
94
+ let offset = 0;
95
+ while (true) {
96
+ const value = await read();
97
+ if (totalBytes > options.size) checkSize();
98
+ if (value) {
99
+ buffer.push(value);
100
+ bufferedLen += value.byteLength;
101
+ }
102
+ while (bufferedLen >= TUS_CHUNK_SIZE || !value && bufferedLen > 0) {
103
+ if (!value) checkSize();
104
+ const merged = concatBytes(buffer, bufferedLen);
105
+ const take = Math.min(TUS_CHUNK_SIZE, bufferedLen);
106
+ offset = await this.patchTus(tusUrl, merged.subarray(0, take), offset, signal);
107
+ const rest = merged.subarray(take);
108
+ buffer = [rest];
109
+ bufferedLen = rest.byteLength;
110
+ }
111
+ if (!value) break;
112
+ }
113
+ checkSize();
114
+ } else {
115
+ const first = [];
116
+ let firstLen = 0;
117
+ let ended = false;
118
+ while (firstLen <= TUS_CHUNK_SIZE) {
119
+ const value = await read();
120
+ if (!value) {
121
+ ended = true;
122
+ break;
123
+ }
124
+ first.push(value);
125
+ firstLen += value.byteLength;
126
+ }
127
+ const head = concatBytes(first, firstLen);
128
+ if (ended) {
129
+ checkSize();
130
+ etag = await this.standardUpload(path, head, contentType, metadata, signal);
131
+ } else {
132
+ checkSize();
133
+ const bodyError = {};
134
+ const body = new ReadableStream({
135
+ start(controller) {
136
+ controller.enqueue(head);
137
+ },
138
+ async pull(controller) {
139
+ try {
140
+ const value = await read();
141
+ if (value) controller.enqueue(value);
142
+ else controller.close();
143
+ } catch (err) {
144
+ bodyError.current = err;
145
+ controller.error(err);
146
+ }
147
+ },
148
+ cancel(reason) {
149
+ return reader.cancel(reason);
150
+ }
151
+ });
152
+ try {
153
+ etag = await this.standardUpload(path, body, contentType, metadata, signal);
154
+ } catch (err) {
155
+ if (bodyError.current) throw bodyError.current;
156
+ throw err;
157
+ }
158
+ checkSize();
159
+ }
160
+ }
161
+ return {
162
+ id,
163
+ url: `${this.baseUrl}/${urlEncodePath(path)}`,
164
+ path,
165
+ size: totalBytes,
166
+ meta: { bucket: this.bucket, ...etag ? { etag } : {} }
167
+ };
168
+ } catch (err) {
169
+ reader.cancel().catch(() => {
170
+ });
171
+ if (tusUrl) {
172
+ await fetch(tusUrl, { method: "DELETE", headers: { ...this.headers, "tus-resumable": TUS_VERSION } }).catch(() => {
173
+ });
174
+ }
175
+ throw err;
176
+ } finally {
177
+ signal?.removeEventListener("abort", onAbort);
178
+ }
179
+ }
180
+ /** `POST /object/{bucket}/{path}`, overwriting any existing object. */
181
+ async standardUpload(path, body, contentType, metadata, signal) {
182
+ const res = await fetchWithAbort(this.objectUrl("object", path), {
183
+ method: "POST",
184
+ headers: {
185
+ ...this.headers,
186
+ "content-type": contentType,
187
+ "cache-control": this.cacheControl,
188
+ "x-upsert": "true",
189
+ "x-metadata": base64(JSON.stringify(metadata))
190
+ },
191
+ body,
192
+ // Node's fetch (undici) requires `duplex: "half"` for stream bodies;
193
+ // other runtimes ignore it.
194
+ duplex: "half",
195
+ signal: signal ?? null
196
+ });
197
+ if (!res.ok) throw await supabaseError("Supabase upload failed", res);
198
+ return res.headers.get("etag") ?? void 0;
199
+ }
200
+ /** Creates a TUS upload and returns its URL. */
201
+ async createTusUpload(path, size, contentType, metadata, signal) {
202
+ const endpoint = `${this.api}/upload/resumable`;
203
+ const uploadMetadata = {
204
+ bucketName: this.bucket,
205
+ objectName: path,
206
+ contentType,
207
+ cacheControl: this.cacheControl.replace("max-age=", ""),
208
+ metadata: JSON.stringify(metadata)
209
+ };
210
+ const res = await fetchWithAbort(endpoint, {
211
+ method: "POST",
212
+ headers: {
213
+ ...this.headers,
214
+ "tus-resumable": TUS_VERSION,
215
+ "upload-length": String(size),
216
+ "upload-metadata": Object.entries(uploadMetadata).map(([k, v]) => `${k} ${base64(v)}`).join(","),
217
+ "x-upsert": "true"
218
+ },
219
+ signal: signal ?? null
220
+ });
221
+ if (!res.ok) throw await supabaseError("Supabase resumable upload create failed", res);
222
+ const location = res.headers.get("location");
223
+ if (!location) {
224
+ throw new StorageError("provider-error", "Supabase resumable upload returned no Location header", { provider: PROVIDER });
225
+ }
226
+ return new URL(location, endpoint).href;
227
+ }
228
+ /** PATCHes `bytes` at `offset`, resending whatever the server did not store.
229
+ * Returns the new offset. */
230
+ async patchTus(url, bytes, offset, signal) {
231
+ const end = offset + bytes.byteLength;
232
+ let current = offset;
233
+ for (let attempt = 0; attempt <= MAX_CHUNK_RESENDS && current < end; attempt++) {
234
+ const res = await fetchWithAbort(url, {
235
+ method: "PATCH",
236
+ headers: {
237
+ ...this.headers,
238
+ "tus-resumable": TUS_VERSION,
239
+ "upload-offset": String(current),
240
+ "content-type": "application/offset+octet-stream"
241
+ },
242
+ body: bytes.subarray(current - offset),
243
+ signal: signal ?? null
244
+ });
245
+ if (!res.ok) throw await supabaseError("Supabase resumable upload chunk failed", res);
246
+ const next = Number(res.headers.get("upload-offset") ?? end);
247
+ if (next < current) break;
248
+ current = next;
249
+ }
250
+ if (current < end) {
251
+ throw new StorageError("provider-error", "Supabase did not store the uploaded chunk", { provider: PROVIDER });
252
+ }
253
+ return current;
254
+ }
255
+ /** Deletes the object; a missing object is not an error. */
256
+ async delete(path, options) {
257
+ const res = await fetchWithAbort(`${this.api}/object/${encodeURIComponent(this.bucket)}`, {
258
+ method: "DELETE",
259
+ headers: { ...this.headers, "content-type": "application/json" },
260
+ body: JSON.stringify({ prefixes: [path] }),
261
+ signal: options?.signal ?? null
262
+ });
263
+ if (!res.ok) throw await supabaseError("Supabase delete failed", res);
264
+ }
265
+ async getUrl(path, _options) {
266
+ return `${this.baseUrl}/${urlEncodePath(path)}`;
267
+ }
268
+ /** Streams the object. `options.signal` also cancels reading the body. */
269
+ async get(path, options) {
270
+ const res = await fetchWithAbort(this.objectUrl("object/authenticated", path), {
271
+ headers: this.headers,
272
+ signal: options?.signal ?? null
273
+ });
274
+ if (!res.ok) throw await supabaseError("Supabase get failed", res);
275
+ if (!res.body) {
276
+ throw new StorageError("provider-error", "Supabase get returned no response body", { provider: PROVIDER });
277
+ }
278
+ return res.body;
279
+ }
280
+ /** A signed download URL for `path`, valid for `expiresIn` seconds (positive integer, default 3600). */
281
+ async getPresignedUrl(path, options) {
282
+ const expiresIn = options?.expiresIn ?? PRESIGN_DEFAULT_EXPIRY;
283
+ if (!Number.isInteger(expiresIn) || expiresIn < 1) {
284
+ throw new ValidationError("expiresIn must be a positive integer number of seconds");
285
+ }
286
+ const res = await fetchWithAbort(this.objectUrl("object/sign", path), {
287
+ method: "POST",
288
+ headers: { ...this.headers, "content-type": "application/json" },
289
+ body: JSON.stringify({ expiresIn }),
290
+ signal: options?.signal ?? null
291
+ });
292
+ if (!res.ok) throw await supabaseError("Supabase sign failed", res);
293
+ const { signedURL } = await res.json();
294
+ if (!signedURL) {
295
+ throw new StorageError("provider-error", "Supabase sign returned no signedURL", { provider: PROVIDER });
296
+ }
297
+ return `${this.api}${signedURL.startsWith("/") ? "" : "/"}${signedURL}`;
298
+ }
299
+ /** The object name an upload with `options.id` is stored under: `<path>/<id>.<ext>`. */
300
+ pathFor(options) {
301
+ const ext = options.filename.includes(".") ? options.filename.split(".").pop() : "";
302
+ const filename = ext ? `${options.id}.${ext}` : options.id;
303
+ return options.path ? `${options.path}/${filename}` : filename;
304
+ }
305
+ /** The object at `path` (`GET /object/info`), or `null` if it does not exist. */
306
+ async head(path, options) {
307
+ const res = await fetchWithAbort(this.objectUrl("object/info", path), {
308
+ headers: this.headers,
309
+ signal: options?.signal ?? null
310
+ });
311
+ if (!res.ok) {
312
+ const error = await supabaseError("Supabase info failed", res);
313
+ if (error.code === "not-found") return null;
314
+ throw error;
315
+ }
316
+ const info = await res.json();
317
+ const name = path.split("/").pop() ?? path;
318
+ const token = info.metadata?.[TOKEN_KEY];
319
+ return {
320
+ id: name.includes(".") ? name.slice(0, name.lastIndexOf(".")) : name,
321
+ url: `${this.baseUrl}/${urlEncodePath(path)}`,
322
+ path,
323
+ size: Number(info.size ?? 0),
324
+ meta: { bucket: this.bucket, ...info.etag ? { etag: info.etag } : {} },
325
+ ...token ? { uploadToken: token } : {},
326
+ ...info.last_modified ? { lastModified: new Date(info.last_modified) } : {},
327
+ ...info.content_type ? { contentType: info.content_type } : {}
328
+ };
329
+ }
330
+ /**
331
+ * One page of the objects under `options.prefix`. Supabase lists one folder
332
+ * at a time, so `recursive: true` walks folders in turn: results come folder
333
+ * by folder rather than in one global sort order.
334
+ */
335
+ async list(options) {
336
+ const prefix = options?.prefix ?? "";
337
+ const limit = options?.limit ?? 100;
338
+ const cut = prefix.lastIndexOf("/") + 1;
339
+ const top = prefix.slice(0, cut);
340
+ const namePart = prefix.slice(cut);
341
+ const state = options?.cursor !== void 0 ? decodeCursor(options.cursor) : { queue: [top], offset: 0 };
342
+ const objects = [];
343
+ const folders = [];
344
+ while (state.queue.length > 0 && objects.length + folders.length < limit) {
345
+ const folder = state.queue[0];
346
+ const want = limit - objects.length - folders.length;
347
+ const search = folder === top ? namePart : "";
348
+ const items = await this.listFolder(folder, state.offset, want, search, options?.signal);
349
+ for (const item of items) {
350
+ if (!item.name.startsWith(search)) continue;
351
+ if (item.id === null) {
352
+ if (options?.recursive) state.queue.push(`${folder}${item.name}/`);
353
+ else folders.push(`${folder}${item.name}/`);
354
+ continue;
355
+ }
356
+ const modified = item.metadata?.lastModified ?? item.updated_at;
357
+ const etag = item.metadata?.eTag;
358
+ const type = item.metadata?.mimetype;
359
+ objects.push({
360
+ path: `${folder}${item.name}`,
361
+ size: Number(item.metadata?.size ?? 0),
362
+ ...modified ? { lastModified: new Date(modified) } : {},
363
+ ...etag ? { etag } : {},
364
+ ...type ? { contentType: type } : {}
365
+ });
366
+ }
367
+ if (items.length < want) {
368
+ state.queue.shift();
369
+ state.offset = 0;
370
+ if (!options?.recursive) break;
371
+ } else {
372
+ state.offset += items.length;
373
+ }
374
+ }
375
+ return { objects, folders, ...state.queue.length > 0 ? { cursor: encodeCursor(state) } : {} };
376
+ }
377
+ /**
378
+ * Copies an object in this bucket. With `overwrite: false` the destination is
379
+ * checked first, which is not atomic; with `overwrite: true` an existing
380
+ * destination is replaced.
381
+ */
382
+ async copy(from, to, options) {
383
+ await this.transfer("copy", from, to, options);
384
+ }
385
+ /** Moves (renames) an object in this bucket, with the same overwrite rules as `copy`. */
386
+ async move(from, to, options) {
387
+ await this.transfer("move", from, to, options);
388
+ }
389
+ async transfer(op, from, to, options) {
390
+ if (from === to) throw new ValidationError("from and to must be different paths");
391
+ const signal = options?.signal;
392
+ const exists = await this.head(to, signal ? { signal } : void 0);
393
+ if (exists && !options?.overwrite) {
394
+ throw new StorageError("conflict", `Supabase ${op} failed: ${to} already exists`, { provider: PROVIDER });
395
+ }
396
+ if (exists) await this.delete(to, signal ? { signal } : void 0);
397
+ const res = await fetchWithAbort(`${this.api}/object/${op}`, {
398
+ method: "POST",
399
+ headers: { ...this.headers, "content-type": "application/json" },
400
+ body: JSON.stringify({ bucketId: this.bucket, sourceKey: from, destinationKey: to }),
401
+ signal: signal ?? null
402
+ });
403
+ if (!res.ok) throw await supabaseError(`Supabase ${op} failed`, res);
404
+ }
405
+ async listFolder(folder, offset, limit, search, signal) {
406
+ const res = await fetchWithAbort(`${this.api}/object/list/${encodeURIComponent(this.bucket)}`, {
407
+ method: "POST",
408
+ headers: { ...this.headers, "content-type": "application/json" },
409
+ body: JSON.stringify({
410
+ prefix: folder.replace(/\/$/, ""),
411
+ limit,
412
+ offset,
413
+ sortBy: { column: "name", order: "asc" },
414
+ ...search ? { search } : {}
415
+ }),
416
+ signal: signal ?? null
417
+ });
418
+ if (!res.ok) throw await supabaseError("Supabase list failed", res);
419
+ return await res.json();
420
+ }
421
+ objectUrl(route, path) {
422
+ return `${this.api}/${route}/${encodeURIComponent(this.bucket)}/${urlEncodePath(path)}`;
423
+ }
424
+ };
425
+ async function fetchWithAbort(url, init) {
426
+ try {
427
+ return await fetch(url, init);
428
+ } catch (err) {
429
+ if (isAbortError(err)) throw err;
430
+ const cause = err instanceof Error ? err : new Error(String(err));
431
+ throw new StorageError("network", `${PROVIDER} network error: ${cause.message}`, { provider: PROVIDER, cause });
432
+ }
433
+ }
434
+ async function supabaseError(prefix, res) {
435
+ const body = await res.text().catch(() => "");
436
+ return new StorageError(
437
+ classifySupabaseError(res.status, body),
438
+ `${prefix}: ${res.status} ${res.statusText}${body ? `: ${body}` : ""}`,
439
+ { statusCode: res.status, provider: PROVIDER }
440
+ );
441
+ }
442
+ function base64(value) {
443
+ let binary = "";
444
+ for (const byte of new TextEncoder().encode(value)) binary += String.fromCharCode(byte);
445
+ return btoa(binary);
446
+ }
447
+ function concatBytes(chunks, total) {
448
+ const out = new Uint8Array(total);
449
+ let offset = 0;
450
+ for (const chunk of chunks) {
451
+ out.set(chunk, offset);
452
+ offset += chunk.byteLength;
453
+ }
454
+ return out;
455
+ }
456
+ export {
457
+ SupabaseDriver
458
+ };
459
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts","../src/index.ts"],"sourcesContent":["import type { StorageErrorCode } from \"@fileway/core\";\n\n// Internal: error classification for SupabaseDriver. Not exported from the package.\n\n/**\n * Classifies a Supabase Storage failure. The API often answers 400 with the\n * real status in the JSON body (`{ statusCode: \"404\", error, message }`), so\n * the body is checked before the HTTP status.\n */\nexport function classifySupabaseError(status: number, body: string): StorageErrorCode {\n let parsed: { statusCode?: string | number; error?: string; message?: string } = {};\n try {\n parsed = JSON.parse(body) as typeof parsed;\n } catch {\n // Not JSON (e.g. a TUS error); fall through to text and status checks.\n }\n const text = `${parsed.error ?? \"\"} ${parsed.message ?? \"\"} ${parsed.error === undefined ? body : \"\"}`.toLowerCase();\n const code = Number(parsed.statusCode ?? status);\n\n if (text.includes(\"bucket not found\")) return \"bucket-not-found\";\n if (code === 409 || text.includes(\"already exists\") || text.includes(\"duplicate\")) return \"conflict\";\n if (text.includes(\"not found\") || text.includes(\"not_found\") || text.includes(\"no such\")) return \"not-found\";\n if (text.includes(\"exceeded the maximum allowed size\") || text.includes(\"payload too large\") || code === 413) {\n return \"size-exceeded\";\n }\n if (code === 401 || code === 403 || text.includes(\"jwt\") || text.includes(\"unauthorized\") || text.includes(\"row-level security\")) {\n return \"auth-failed\";\n }\n if (code === 429 || text.includes(\"too many requests\")) return \"rate-limited\";\n if (code === 404) return \"not-found\";\n return \"provider-error\";\n}\n","import type { BaseDriver, CopyOptions, ListedObject, ListOptions, ListPage, OperationOptions, PresignedUrlOptions, StoredObject, UploadContext, UploadOptions, UploadResult } from \"@fileway/core\";\nimport { classifySupabaseError } from \"./errors.js\";\nimport { validateUploadOptions, ValidationError, urlEncodePath, abortError, isAbortError, StorageError, encodeCursor, decodeCursor } from \"@fileway/core\";\n\nconst randomUUID = () => globalThis.crypto.randomUUID();\nconst PROVIDER = \"supabase\";\n\n// Supabase's resumable (TUS) endpoint requires 6 MiB chunks.\nconst TUS_CHUNK_SIZE = 6 * 1024 * 1024;\nconst TUS_VERSION = \"1.0.0\";\nconst PRESIGN_DEFAULT_EXPIRY = 3600;\n// User metadata key carrying FilewayClient's per-upload token.\nconst TOKEN_KEY = \"fileway-upload-token\";\n// How many times a TUS chunk is resent when the server stored only part of it.\nconst MAX_CHUNK_RESENDS = 3;\n\nexport interface SupabaseDriverConfig {\n /** Project URL, e.g. `https://<ref>.supabase.co` (or the direct storage host\n * `https://<ref>.storage.supabase.co`, recommended for large uploads). */\n url: string;\n /** API key: a secret / service role key on the server, or the anon or\n * publishable key together with `accessToken`. */\n key: string;\n /** A signed-in user's JWT, so the bucket's RLS policies apply. */\n accessToken?: string;\n bucket: string;\n /** Public base URL for `getUrl`. Defaults to the bucket's public object URL. */\n baseUrl?: string;\n /** Reject uploads larger than this many bytes with a `size-exceeded` error. */\n maxSizeBytes?: number;\n /** `Cache-Control` max-age in seconds for uploaded objects. Default 3600. */\n cacheControl?: number;\n}\n\nexport type SupabaseMeta = {\n bucket: string;\n etag?: string;\n};\n\n/** Where a paged listing stands: folders left to read and the offset in the first one. */\ninterface ListState {\n queue: string[];\n offset: number;\n}\n\n/** One entry of `POST /object/list`; folders have `id: null`. */\ninterface ListItem {\n name: string;\n id: string | null;\n updated_at?: string | null;\n metadata?: { size?: number; mimetype?: string; eTag?: string; lastModified?: string } | null;\n}\n\n/** The `GET /object/info` response (fields this driver reads). */\ninterface ObjectInfo {\n name?: string;\n size?: number | null;\n content_type?: string | null;\n etag?: string | null;\n last_modified?: string | null;\n metadata?: Record<string, string> | null;\n}\n\nexport class SupabaseDriver implements BaseDriver<SupabaseMeta> {\n readonly name = \"supabase\";\n private api: string;\n private bucket: string;\n private baseUrl: string;\n private headers: Record<string, string>;\n private maxSizeBytes: number | undefined;\n private cacheControl: string;\n\n constructor(config: SupabaseDriverConfig) {\n if (!config.url || !config.key || !config.bucket) {\n throw new StorageError(\"config\", \"SupabaseDriver requires url, key and bucket\", { provider: PROVIDER });\n }\n this.api = `${config.url.replace(/\\/+$/, \"\")}/storage/v1`;\n this.bucket = config.bucket;\n this.baseUrl = (config.baseUrl ?? `${this.api}/object/public/${config.bucket}`).replace(/\\/+$/, \"\");\n // New-style keys (sb_secret_..., sb_publishable_...) are not JWTs and go\n // only in `apikey`; legacy JWT keys and user tokens also go in Authorization.\n const bearer = config.accessToken ?? (config.key.startsWith(\"eyJ\") ? config.key : undefined);\n this.headers = { apikey: config.key, ...(bearer ? { authorization: `Bearer ${bearer}` } : {}) };\n this.maxSizeBytes = config.maxSizeBytes;\n this.cacheControl = `max-age=${config.cacheControl ?? 3600}`;\n }\n\n async upload(\n stream: ReadableStream<Uint8Array>,\n options: UploadOptions,\n context?: UploadContext,\n ): Promise<UploadResult<SupabaseMeta>> {\n validateUploadOptions(options);\n if (this.maxSizeBytes !== undefined && options.size !== undefined && options.size > this.maxSizeBytes) {\n throw new StorageError(\"size-exceeded\", `file size ${options.size} exceeds maxSizeBytes ${this.maxSizeBytes}`, { provider: PROVIDER });\n }\n\n const id = options.id ?? randomUUID();\n const path = this.pathFor({ ...options, id });\n const contentType = options.mimeType ?? \"application/octet-stream\";\n const metadata = { ...options.metadata, ...(context?.uploadToken ? { [TOKEN_KEY]: context.uploadToken } : {}) };\n const signal = options.signal;\n\n const reader = stream.getReader();\n let aborted = signal?.aborted ?? false;\n const onAbort = () => {\n aborted = true;\n // Cancelling the reader wakes any pending `read()` with `done: true`.\n reader.cancel().catch(() => {});\n };\n if (signal && !aborted) signal.addEventListener(\"abort\", onAbort, { once: true });\n\n let totalBytes = 0;\n const read = async (): Promise<Uint8Array | undefined> => {\n const { done, value } = await reader.read();\n if (aborted) throw abortError(\"upload aborted\");\n if (done) return undefined;\n totalBytes += value.byteLength;\n if (this.maxSizeBytes !== undefined && totalBytes > this.maxSizeBytes) {\n throw new StorageError(\"size-exceeded\", `upload exceeds maxSizeBytes ${this.maxSizeBytes}`, { provider: PROVIDER });\n }\n return value;\n };\n const checkSize = () => {\n if (options.size !== undefined && totalBytes !== options.size) {\n throw new ValidationError(`stream delivered ${totalBytes} bytes, expected ${options.size}`);\n }\n };\n\n let tusUrl: string | undefined;\n try {\n let etag: string | undefined;\n\n if (options.size !== undefined && options.size > TUS_CHUNK_SIZE) {\n // Known large size: resumable TUS upload, one 6 MiB chunk in memory at a time.\n tusUrl = await this.createTusUpload(path, options.size, contentType, metadata, signal);\n let buffer: Uint8Array[] = [];\n let bufferedLen = 0;\n let offset = 0;\n while (true) {\n const value = await read();\n if (totalBytes > options.size) checkSize();\n if (value) {\n buffer.push(value);\n bufferedLen += value.byteLength;\n }\n while (bufferedLen >= TUS_CHUNK_SIZE || (!value && bufferedLen > 0)) {\n if (!value) checkSize();\n const merged = concatBytes(buffer, bufferedLen);\n const take = Math.min(TUS_CHUNK_SIZE, bufferedLen);\n offset = await this.patchTus(tusUrl, merged.subarray(0, take), offset, signal);\n const rest = merged.subarray(take);\n buffer = [rest];\n bufferedLen = rest.byteLength;\n }\n if (!value) break;\n }\n checkSize();\n } else {\n // Read the first chunk; if the stream ends inside it, send one buffered request.\n const first: Uint8Array[] = [];\n let firstLen = 0;\n let ended = false;\n while (firstLen <= TUS_CHUNK_SIZE) {\n const value = await read();\n if (!value) {\n ended = true;\n break;\n }\n first.push(value);\n firstLen += value.byteLength;\n }\n const head = concatBytes(first, firstLen);\n\n if (ended) {\n checkSize();\n etag = await this.standardUpload(path, head, contentType, metadata, signal);\n } else {\n // A declared size this small cannot be followed by more data.\n checkSize();\n // Unknown size beyond one chunk: stream the rest in a single request,\n // so memory stays bounded without knowing the total in advance.\n const bodyError: { current?: unknown } = {};\n const body = new ReadableStream<Uint8Array>({\n start(controller) {\n controller.enqueue(head);\n },\n async pull(controller) {\n try {\n const value = await read();\n if (value) controller.enqueue(value);\n else controller.close();\n } catch (err) {\n bodyError.current = err;\n controller.error(err);\n }\n },\n cancel(reason) {\n return reader.cancel(reason);\n },\n });\n try {\n etag = await this.standardUpload(path, body, contentType, metadata, signal);\n } catch (err) {\n // A body error (abort, size limit) surfaces as a generic fetch failure.\n if (bodyError.current) throw bodyError.current;\n throw err;\n }\n checkSize();\n }\n }\n\n return {\n id,\n url: `${this.baseUrl}/${urlEncodePath(path)}`,\n path,\n size: totalBytes,\n meta: { bucket: this.bucket, ...(etag ? { etag } : {}) },\n };\n } catch (err) {\n reader.cancel().catch(() => {});\n if (tusUrl) {\n // Best-effort: terminate the unfinished TUS upload.\n await fetch(tusUrl, { method: \"DELETE\", headers: { ...this.headers, \"tus-resumable\": TUS_VERSION } }).catch(() => {});\n }\n throw err;\n } finally {\n signal?.removeEventListener(\"abort\", onAbort);\n }\n }\n\n /** `POST /object/{bucket}/{path}`, overwriting any existing object. */\n private async standardUpload(\n path: string,\n body: Uint8Array | ReadableStream<Uint8Array>,\n contentType: string,\n metadata: Record<string, string>,\n signal: AbortSignal | undefined,\n ): Promise<string | undefined> {\n const res = await fetchWithAbort(this.objectUrl(\"object\", path), {\n method: \"POST\",\n headers: {\n ...this.headers,\n \"content-type\": contentType,\n \"cache-control\": this.cacheControl,\n \"x-upsert\": \"true\",\n \"x-metadata\": base64(JSON.stringify(metadata)),\n },\n body: body as BodyInit,\n // Node's fetch (undici) requires `duplex: \"half\"` for stream bodies;\n // other runtimes ignore it.\n duplex: \"half\",\n signal: signal ?? null,\n } as RequestInit);\n if (!res.ok) throw await supabaseError(\"Supabase upload failed\", res);\n return res.headers.get(\"etag\") ?? undefined;\n }\n\n /** Creates a TUS upload and returns its URL. */\n private async createTusUpload(\n path: string,\n size: number,\n contentType: string,\n metadata: Record<string, string>,\n signal: AbortSignal | undefined,\n ): Promise<string> {\n const endpoint = `${this.api}/upload/resumable`;\n const uploadMetadata = {\n bucketName: this.bucket,\n objectName: path,\n contentType,\n cacheControl: this.cacheControl.replace(\"max-age=\", \"\"),\n metadata: JSON.stringify(metadata),\n };\n const res = await fetchWithAbort(endpoint, {\n method: \"POST\",\n headers: {\n ...this.headers,\n \"tus-resumable\": TUS_VERSION,\n \"upload-length\": String(size),\n \"upload-metadata\": Object.entries(uploadMetadata)\n .map(([k, v]) => `${k} ${base64(v)}`)\n .join(\",\"),\n \"x-upsert\": \"true\",\n },\n signal: signal ?? null,\n });\n if (!res.ok) throw await supabaseError(\"Supabase resumable upload create failed\", res);\n const location = res.headers.get(\"location\");\n if (!location) {\n throw new StorageError(\"provider-error\", \"Supabase resumable upload returned no Location header\", { provider: PROVIDER });\n }\n return new URL(location, endpoint).href;\n }\n\n /** PATCHes `bytes` at `offset`, resending whatever the server did not store.\n * Returns the new offset. */\n private async patchTus(url: string, bytes: Uint8Array, offset: number, signal: AbortSignal | undefined): Promise<number> {\n const end = offset + bytes.byteLength;\n let current = offset;\n for (let attempt = 0; attempt <= MAX_CHUNK_RESENDS && current < end; attempt++) {\n const res = await fetchWithAbort(url, {\n method: \"PATCH\",\n headers: {\n ...this.headers,\n \"tus-resumable\": TUS_VERSION,\n \"upload-offset\": String(current),\n \"content-type\": \"application/offset+octet-stream\",\n },\n body: bytes.subarray(current - offset) as BodyInit,\n signal: signal ?? null,\n });\n if (!res.ok) throw await supabaseError(\"Supabase resumable upload chunk failed\", res);\n const next = Number(res.headers.get(\"upload-offset\") ?? end);\n if (next < current) break;\n current = next;\n }\n if (current < end) {\n throw new StorageError(\"provider-error\", \"Supabase did not store the uploaded chunk\", { provider: PROVIDER });\n }\n return current;\n }\n\n /** Deletes the object; a missing object is not an error. */\n async delete(path: string, options?: OperationOptions): Promise<void> {\n const res = await fetchWithAbort(`${this.api}/object/${encodeURIComponent(this.bucket)}`, {\n method: \"DELETE\",\n headers: { ...this.headers, \"content-type\": \"application/json\" },\n body: JSON.stringify({ prefixes: [path] }),\n signal: options?.signal ?? null,\n });\n if (!res.ok) throw await supabaseError(\"Supabase delete failed\", res);\n }\n\n async getUrl(path: string, _options?: OperationOptions): Promise<string> {\n return `${this.baseUrl}/${urlEncodePath(path)}`;\n }\n\n /** Streams the object. `options.signal` also cancels reading the body. */\n async get(path: string, options?: OperationOptions): Promise<ReadableStream<Uint8Array>> {\n const res = await fetchWithAbort(this.objectUrl(\"object/authenticated\", path), {\n headers: this.headers,\n signal: options?.signal ?? null,\n });\n if (!res.ok) throw await supabaseError(\"Supabase get failed\", res);\n if (!res.body) {\n throw new StorageError(\"provider-error\", \"Supabase get returned no response body\", { provider: PROVIDER });\n }\n return res.body;\n }\n\n /** A signed download URL for `path`, valid for `expiresIn` seconds (positive integer, default 3600). */\n async getPresignedUrl(path: string, options?: PresignedUrlOptions): Promise<string> {\n const expiresIn = options?.expiresIn ?? PRESIGN_DEFAULT_EXPIRY;\n if (!Number.isInteger(expiresIn) || expiresIn < 1) {\n throw new ValidationError(\"expiresIn must be a positive integer number of seconds\");\n }\n const res = await fetchWithAbort(this.objectUrl(\"object/sign\", path), {\n method: \"POST\",\n headers: { ...this.headers, \"content-type\": \"application/json\" },\n body: JSON.stringify({ expiresIn }),\n signal: options?.signal ?? null,\n });\n if (!res.ok) throw await supabaseError(\"Supabase sign failed\", res);\n const { signedURL } = (await res.json()) as { signedURL?: string };\n if (!signedURL) {\n throw new StorageError(\"provider-error\", \"Supabase sign returned no signedURL\", { provider: PROVIDER });\n }\n return `${this.api}${signedURL.startsWith(\"/\") ? \"\" : \"/\"}${signedURL}`;\n }\n\n /** The object name an upload with `options.id` is stored under: `<path>/<id>.<ext>`. */\n pathFor(options: UploadOptions & { id: string }): string {\n const ext = options.filename.includes(\".\") ? options.filename.split(\".\").pop()! : \"\";\n const filename = ext ? `${options.id}.${ext}` : options.id;\n return options.path ? `${options.path}/${filename}` : filename;\n }\n\n /** The object at `path` (`GET /object/info`), or `null` if it does not exist. */\n async head(path: string, options?: OperationOptions): Promise<StoredObject<SupabaseMeta> | null> {\n const res = await fetchWithAbort(this.objectUrl(\"object/info\", path), {\n headers: this.headers,\n signal: options?.signal ?? null,\n });\n if (!res.ok) {\n const error = await supabaseError(\"Supabase info failed\", res);\n if (error.code === \"not-found\") return null;\n throw error;\n }\n const info = (await res.json()) as ObjectInfo;\n const name = path.split(\"/\").pop() ?? path;\n const token = info.metadata?.[TOKEN_KEY];\n return {\n id: name.includes(\".\") ? name.slice(0, name.lastIndexOf(\".\")) : name,\n url: `${this.baseUrl}/${urlEncodePath(path)}`,\n path,\n size: Number(info.size ?? 0),\n meta: { bucket: this.bucket, ...(info.etag ? { etag: info.etag } : {}) },\n ...(token ? { uploadToken: token } : {}),\n ...(info.last_modified ? { lastModified: new Date(info.last_modified) } : {}),\n ...(info.content_type ? { contentType: info.content_type } : {}),\n };\n }\n\n /**\n * One page of the objects under `options.prefix`. Supabase lists one folder\n * at a time, so `recursive: true` walks folders in turn: results come folder\n * by folder rather than in one global sort order.\n */\n async list(options?: ListOptions): Promise<ListPage> {\n const prefix = options?.prefix ?? \"\";\n const limit = options?.limit ?? 100;\n const cut = prefix.lastIndexOf(\"/\") + 1;\n const top = prefix.slice(0, cut);\n const namePart = prefix.slice(cut);\n const state: ListState = options?.cursor !== undefined\n ? decodeCursor<ListState>(options.cursor)\n : { queue: [top], offset: 0 };\n\n const objects: ListedObject[] = [];\n const folders: string[] = [];\n while (state.queue.length > 0 && objects.length + folders.length < limit) {\n const folder = state.queue[0]!;\n const want = limit - objects.length - folders.length;\n // The name filter applies to the folder the caller asked for, not to subfolders.\n const search = folder === top ? namePart : \"\";\n const items = await this.listFolder(folder, state.offset, want, search, options?.signal);\n for (const item of items) {\n if (!item.name.startsWith(search)) continue;\n if (item.id === null) {\n if (options?.recursive) state.queue.push(`${folder}${item.name}/`);\n else folders.push(`${folder}${item.name}/`);\n continue;\n }\n const modified = item.metadata?.lastModified ?? item.updated_at;\n const etag = item.metadata?.eTag;\n const type = item.metadata?.mimetype;\n objects.push({\n path: `${folder}${item.name}`,\n size: Number(item.metadata?.size ?? 0),\n ...(modified ? { lastModified: new Date(modified) } : {}),\n ...(etag ? { etag } : {}),\n ...(type ? { contentType: type } : {}),\n });\n }\n if (items.length < want) {\n state.queue.shift();\n state.offset = 0;\n if (!options?.recursive) break;\n } else {\n state.offset += items.length;\n }\n }\n return { objects, folders, ...(state.queue.length > 0 ? { cursor: encodeCursor(state) } : {}) };\n }\n\n /**\n * Copies an object in this bucket. With `overwrite: false` the destination is\n * checked first, which is not atomic; with `overwrite: true` an existing\n * destination is replaced.\n */\n async copy(from: string, to: string, options?: CopyOptions): Promise<void> {\n await this.transfer(\"copy\", from, to, options);\n }\n\n /** Moves (renames) an object in this bucket, with the same overwrite rules as `copy`. */\n async move(from: string, to: string, options?: CopyOptions): Promise<void> {\n await this.transfer(\"move\", from, to, options);\n }\n\n private async transfer(op: \"copy\" | \"move\", from: string, to: string, options?: CopyOptions): Promise<void> {\n if (from === to) throw new ValidationError(\"from and to must be different paths\");\n const signal = options?.signal;\n const exists = await this.head(to, signal ? { signal } : undefined);\n if (exists && !options?.overwrite) {\n throw new StorageError(\"conflict\", `Supabase ${op} failed: ${to} already exists`, { provider: PROVIDER });\n }\n // Supabase will not copy or move onto an existing object, so replace it first.\n if (exists) await this.delete(to, signal ? { signal } : undefined);\n const res = await fetchWithAbort(`${this.api}/object/${op}`, {\n method: \"POST\",\n headers: { ...this.headers, \"content-type\": \"application/json\" },\n body: JSON.stringify({ bucketId: this.bucket, sourceKey: from, destinationKey: to }),\n signal: signal ?? null,\n });\n if (!res.ok) throw await supabaseError(`Supabase ${op} failed`, res);\n }\n\n private async listFolder(\n folder: string,\n offset: number,\n limit: number,\n search: string,\n signal: AbortSignal | undefined,\n ): Promise<ListItem[]> {\n const res = await fetchWithAbort(`${this.api}/object/list/${encodeURIComponent(this.bucket)}`, {\n method: \"POST\",\n headers: { ...this.headers, \"content-type\": \"application/json\" },\n body: JSON.stringify({\n prefix: folder.replace(/\\/$/, \"\"),\n limit,\n offset,\n sortBy: { column: \"name\", order: \"asc\" },\n ...(search ? { search } : {}),\n }),\n signal: signal ?? null,\n });\n if (!res.ok) throw await supabaseError(\"Supabase list failed\", res);\n return (await res.json()) as ListItem[];\n }\n\n private objectUrl(route: string, path: string): string {\n return `${this.api}/${route}/${encodeURIComponent(this.bucket)}/${urlEncodePath(path)}`;\n }\n}\n\n/** `fetch` that converts non-abort failures into a typed `network` error,\n * while letting aborts (`AbortError`) pass through untouched. */\nasync function fetchWithAbort(url: string | URL, init: RequestInit): Promise<Response> {\n try {\n return await fetch(url, init);\n } catch (err) {\n if (isAbortError(err)) throw err;\n const cause = err instanceof Error ? err : new Error(String(err));\n throw new StorageError(\"network\", `${PROVIDER} network error: ${cause.message}`, { provider: PROVIDER, cause });\n }\n}\n\nasync function supabaseError(prefix: string, res: Response): Promise<StorageError> {\n const body = await res.text().catch(() => \"\");\n return new StorageError(\n classifySupabaseError(res.status, body),\n `${prefix}: ${res.status} ${res.statusText}${body ? `: ${body}` : \"\"}`,\n { statusCode: res.status, provider: PROVIDER },\n );\n}\n\nfunction base64(value: string): string {\n let binary = \"\";\n for (const byte of new TextEncoder().encode(value)) binary += String.fromCharCode(byte);\n return btoa(binary);\n}\n\nfunction concatBytes(chunks: Uint8Array[], total: number): Uint8Array {\n const out = new Uint8Array(total);\n let offset = 0;\n for (const chunk of chunks) {\n out.set(chunk, offset);\n offset += chunk.byteLength;\n }\n return out;\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,49 @@
1
1
  {
2
2
  "name": "@fileway/supabase",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.6.0",
4
+ "license": "MIT",
5
+ "keywords": [
6
+ "fileway",
7
+ "file-upload",
8
+ "storage",
9
+ "supabase",
10
+ "supabase-storage",
11
+ "tus",
12
+ "stream"
13
+ ],
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/Goldenhub/fileway.git"
17
+ },
18
+ "type": "module",
19
+ "types": "./dist/index.d.ts",
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "worker": "./dist/index.js",
24
+ "deno": "./dist/index.js",
25
+ "bun": "./dist/index.js",
26
+ "import": "./dist/index.js",
27
+ "require": "./dist/index.cjs"
28
+ }
29
+ },
30
+ "files": [
31
+ "dist"
32
+ ],
33
+ "publishConfig": {
34
+ "access": "public",
35
+ "provenance": true
36
+ },
37
+ "dependencies": {
38
+ "@fileway/core": "0.6.0"
39
+ },
40
+ "devDependencies": {
41
+ "tsup": "^8.0.0",
42
+ "typescript": "^5.5.0",
43
+ "vitest": "^1.6.0"
44
+ },
45
+ "scripts": {
46
+ "build": "tsup",
47
+ "test": "vitest run"
48
+ }
6
49
  }