@hot-updater/supabase 0.35.12 → 0.36.1
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/edge.cjs +1 -1
- package/dist/edge.mjs +1 -1
- package/dist/iac/index.cjs +1 -1
- package/dist/iac/index.mjs +1 -1
- package/dist/index.cjs +19 -11
- package/dist/index.mjs +19 -11
- package/dist/{supabaseEdgeFunctionStorage-CO70azPw.mjs → supabaseEdgeFunctionStorage-B3yJ5yys.mjs} +78 -16
- package/dist/{supabaseEdgeFunctionStorage-DRxwDPRy.cjs → supabaseEdgeFunctionStorage-DczYIzj_.cjs} +89 -15
- package/package.json +9 -9
package/dist/edge.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_supabaseEdgeFunctionStorage = require("./supabaseEdgeFunctionStorage-
|
|
2
|
+
const require_supabaseEdgeFunctionStorage = require("./supabaseEdgeFunctionStorage-DczYIzj_.cjs");
|
|
3
3
|
exports.supabaseEdgeFunctionDatabase = require_supabaseEdgeFunctionStorage.supabaseEdgeFunctionDatabase;
|
|
4
4
|
exports.supabaseEdgeFunctionStorage = require_supabaseEdgeFunctionStorage.supabaseEdgeFunctionStorage;
|
package/dist/edge.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { i as supabaseEdgeFunctionDatabase, t as supabaseEdgeFunctionStorage } from "./supabaseEdgeFunctionStorage-B3yJ5yys.mjs";
|
|
2
2
|
export { supabaseEdgeFunctionDatabase, supabaseEdgeFunctionStorage };
|
package/dist/iac/index.cjs
CHANGED
|
@@ -6909,7 +6909,7 @@ const SOURCE_TEMPLATE = `// add this to your App.tsx
|
|
|
6909
6909
|
import { HotUpdater } from "@hot-updater/react-native";
|
|
6910
6910
|
|
|
6911
6911
|
function App() {
|
|
6912
|
-
return
|
|
6912
|
+
return null; // Replace with your app root
|
|
6913
6913
|
}
|
|
6914
6914
|
|
|
6915
6915
|
export default HotUpdater.wrap({
|
package/dist/iac/index.mjs
CHANGED
|
@@ -6904,7 +6904,7 @@ const SOURCE_TEMPLATE = `// add this to your App.tsx
|
|
|
6904
6904
|
import { HotUpdater } from "@hot-updater/react-native";
|
|
6905
6905
|
|
|
6906
6906
|
function App() {
|
|
6907
|
-
return
|
|
6907
|
+
return null; // Replace with your app root
|
|
6908
6908
|
}
|
|
6909
6909
|
|
|
6910
6910
|
export default HotUpdater.wrap({
|
package/dist/index.cjs
CHANGED
|
@@ -21,7 +21,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
21
21
|
enumerable: true
|
|
22
22
|
}) : target, mod));
|
|
23
23
|
//#endregion
|
|
24
|
-
const require_supabaseEdgeFunctionStorage = require("./supabaseEdgeFunctionStorage-
|
|
24
|
+
const require_supabaseEdgeFunctionStorage = require("./supabaseEdgeFunctionStorage-DczYIzj_.cjs");
|
|
25
25
|
let _hot_updater_plugin_core = require("@hot-updater/plugin-core");
|
|
26
26
|
let _supabase_supabase_js = require("@supabase/supabase-js");
|
|
27
27
|
let fs_promises = require("fs/promises");
|
|
@@ -54,16 +54,28 @@ async function verifyObjectCanBeSignedForRuntime({ bucket, key }) {
|
|
|
54
54
|
expiresIn: 3600
|
|
55
55
|
});
|
|
56
56
|
}
|
|
57
|
+
const parseSupabaseStorageUri = (storageUri) => {
|
|
58
|
+
const { bucket, key } = (0, _hot_updater_plugin_core.parseStorageUri)(storageUri, "supabase-storage");
|
|
59
|
+
return {
|
|
60
|
+
bucket,
|
|
61
|
+
key: require_supabaseEdgeFunctionStorage.decodeStorageObjectKey(key)
|
|
62
|
+
};
|
|
63
|
+
};
|
|
57
64
|
const supabaseStorage = (0, _hot_updater_plugin_core.createUniversalStoragePlugin)({
|
|
58
65
|
name: "supabaseStorage",
|
|
59
66
|
supportedProtocol: "supabase-storage",
|
|
60
67
|
factory: (config) => {
|
|
61
68
|
const bucket = (0, _supabase_supabase_js.createClient)(config.supabaseUrl, require_supabaseEdgeFunctionStorage.resolveSupabaseServiceRoleKey(config)).storage.from(config.bucketName);
|
|
62
69
|
const getStorageKey = (0, _hot_updater_plugin_core.createStorageKeyBuilder)(config.basePath);
|
|
70
|
+
const resolveSignedUrl = require_supabaseEdgeFunctionStorage.createSupabaseSignedUrlBatcher({
|
|
71
|
+
createSignedUrls: (_bucketName, keys, expiresIn) => bucket.createSignedUrls(keys, expiresIn),
|
|
72
|
+
expiresIn: 3600,
|
|
73
|
+
formatObjectPath: (_bucketName, key) => key
|
|
74
|
+
});
|
|
63
75
|
return {
|
|
64
76
|
node: {
|
|
65
77
|
async delete(storageUri) {
|
|
66
|
-
const { key, bucket: bucketName } = (
|
|
78
|
+
const { key, bucket: bucketName } = parseSupabaseStorageUri(storageUri);
|
|
67
79
|
if (bucketName !== config.bucketName) throw new Error(`Bucket name mismatch: expected "${config.bucketName}", but found "${bucketName}".`);
|
|
68
80
|
const { error } = await bucket.remove([key]);
|
|
69
81
|
if (error) {
|
|
@@ -88,7 +100,7 @@ const supabaseStorage = (0, _hot_updater_plugin_core.createUniversalStoragePlugi
|
|
|
88
100
|
return { storageUri: `supabase-storage://${upload.data.fullPath}` };
|
|
89
101
|
},
|
|
90
102
|
async exists(storageUri) {
|
|
91
|
-
const { key, bucket: bucketName } = (
|
|
103
|
+
const { key, bucket: bucketName } = parseSupabaseStorageUri(storageUri);
|
|
92
104
|
if (bucketName !== config.bucketName) throw new Error(`Bucket name mismatch: expected "${config.bucketName}", but found "${bucketName}".`);
|
|
93
105
|
const { data, error } = await bucket.exists(key);
|
|
94
106
|
if (data === false) return false;
|
|
@@ -100,7 +112,7 @@ const supabaseStorage = (0, _hot_updater_plugin_core.createUniversalStoragePlugi
|
|
|
100
112
|
return data;
|
|
101
113
|
},
|
|
102
114
|
async downloadFile(storageUri, filePath) {
|
|
103
|
-
const { key, bucket: bucketName } = (
|
|
115
|
+
const { key, bucket: bucketName } = parseSupabaseStorageUri(storageUri);
|
|
104
116
|
if (bucketName !== config.bucketName) throw new Error(`Bucket name mismatch: expected "${config.bucketName}", but found "${bucketName}".`);
|
|
105
117
|
const { data, error } = await bucket.download(key);
|
|
106
118
|
if (error) throw new Error(`Failed to download bundle: ${error.message}`);
|
|
@@ -111,7 +123,7 @@ const supabaseStorage = (0, _hot_updater_plugin_core.createUniversalStoragePlugi
|
|
|
111
123
|
},
|
|
112
124
|
runtime: {
|
|
113
125
|
async readText(storageUri) {
|
|
114
|
-
const { key, bucket: bucketName } = (
|
|
126
|
+
const { key, bucket: bucketName } = parseSupabaseStorageUri(storageUri);
|
|
115
127
|
if (bucketName !== config.bucketName) throw new Error(`Bucket name mismatch: expected "${config.bucketName}", but found "${bucketName}".`);
|
|
116
128
|
const { data, error } = await bucket.download(key);
|
|
117
129
|
if (error) {
|
|
@@ -124,14 +136,10 @@ const supabaseStorage = (0, _hot_updater_plugin_core.createUniversalStoragePlugi
|
|
|
124
136
|
async getDownloadUrl(storageUri) {
|
|
125
137
|
const u = new URL(storageUri);
|
|
126
138
|
if (u.protocol.replace(":", "") !== "supabase-storage") throw new Error("Invalid Supabase storage URI protocol");
|
|
127
|
-
let key = `${u.host}${u.pathname}`.replace(/^\//, "");
|
|
139
|
+
let key = require_supabaseEdgeFunctionStorage.decodeStorageObjectKey(`${u.host}${u.pathname}`.replace(/^\//, ""));
|
|
128
140
|
if (!key) throw new Error("Invalid Supabase storage URI: missing key");
|
|
129
141
|
if (key.startsWith(`${config.bucketName}/`)) key = key.substring(`${config.bucketName}/`.length);
|
|
130
|
-
return { fileUrl: await
|
|
131
|
-
bucket,
|
|
132
|
-
key,
|
|
133
|
-
expiresIn: 3600
|
|
134
|
-
}) };
|
|
142
|
+
return { fileUrl: await resolveSignedUrl(config.bucketName, key) };
|
|
135
143
|
}
|
|
136
144
|
}
|
|
137
145
|
};
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as
|
|
1
|
+
import { a as supabaseDatabase, i as supabaseEdgeFunctionDatabase, n as decodeStorageObjectKey, o as resolveSupabaseServiceRoleKey, r as createSupabaseSignedUrlBatcher, t as supabaseEdgeFunctionStorage } from "./supabaseEdgeFunctionStorage-B3yJ5yys.mjs";
|
|
2
2
|
import { createStorageKeyBuilder, createUniversalStoragePlugin, getContentType, parseStorageUri } from "@hot-updater/plugin-core";
|
|
3
3
|
import { createClient } from "@supabase/supabase-js";
|
|
4
4
|
import fs from "fs/promises";
|
|
@@ -29,16 +29,28 @@ async function verifyObjectCanBeSignedForRuntime({ bucket, key }) {
|
|
|
29
29
|
expiresIn: 3600
|
|
30
30
|
});
|
|
31
31
|
}
|
|
32
|
+
const parseSupabaseStorageUri = (storageUri) => {
|
|
33
|
+
const { bucket, key } = parseStorageUri(storageUri, "supabase-storage");
|
|
34
|
+
return {
|
|
35
|
+
bucket,
|
|
36
|
+
key: decodeStorageObjectKey(key)
|
|
37
|
+
};
|
|
38
|
+
};
|
|
32
39
|
const supabaseStorage = createUniversalStoragePlugin({
|
|
33
40
|
name: "supabaseStorage",
|
|
34
41
|
supportedProtocol: "supabase-storage",
|
|
35
42
|
factory: (config) => {
|
|
36
43
|
const bucket = createClient(config.supabaseUrl, resolveSupabaseServiceRoleKey(config)).storage.from(config.bucketName);
|
|
37
44
|
const getStorageKey = createStorageKeyBuilder(config.basePath);
|
|
45
|
+
const resolveSignedUrl = createSupabaseSignedUrlBatcher({
|
|
46
|
+
createSignedUrls: (_bucketName, keys, expiresIn) => bucket.createSignedUrls(keys, expiresIn),
|
|
47
|
+
expiresIn: 3600,
|
|
48
|
+
formatObjectPath: (_bucketName, key) => key
|
|
49
|
+
});
|
|
38
50
|
return {
|
|
39
51
|
node: {
|
|
40
52
|
async delete(storageUri) {
|
|
41
|
-
const { key, bucket: bucketName } =
|
|
53
|
+
const { key, bucket: bucketName } = parseSupabaseStorageUri(storageUri);
|
|
42
54
|
if (bucketName !== config.bucketName) throw new Error(`Bucket name mismatch: expected "${config.bucketName}", but found "${bucketName}".`);
|
|
43
55
|
const { error } = await bucket.remove([key]);
|
|
44
56
|
if (error) {
|
|
@@ -63,7 +75,7 @@ const supabaseStorage = createUniversalStoragePlugin({
|
|
|
63
75
|
return { storageUri: `supabase-storage://${upload.data.fullPath}` };
|
|
64
76
|
},
|
|
65
77
|
async exists(storageUri) {
|
|
66
|
-
const { key, bucket: bucketName } =
|
|
78
|
+
const { key, bucket: bucketName } = parseSupabaseStorageUri(storageUri);
|
|
67
79
|
if (bucketName !== config.bucketName) throw new Error(`Bucket name mismatch: expected "${config.bucketName}", but found "${bucketName}".`);
|
|
68
80
|
const { data, error } = await bucket.exists(key);
|
|
69
81
|
if (data === false) return false;
|
|
@@ -75,7 +87,7 @@ const supabaseStorage = createUniversalStoragePlugin({
|
|
|
75
87
|
return data;
|
|
76
88
|
},
|
|
77
89
|
async downloadFile(storageUri, filePath) {
|
|
78
|
-
const { key, bucket: bucketName } =
|
|
90
|
+
const { key, bucket: bucketName } = parseSupabaseStorageUri(storageUri);
|
|
79
91
|
if (bucketName !== config.bucketName) throw new Error(`Bucket name mismatch: expected "${config.bucketName}", but found "${bucketName}".`);
|
|
80
92
|
const { data, error } = await bucket.download(key);
|
|
81
93
|
if (error) throw new Error(`Failed to download bundle: ${error.message}`);
|
|
@@ -86,7 +98,7 @@ const supabaseStorage = createUniversalStoragePlugin({
|
|
|
86
98
|
},
|
|
87
99
|
runtime: {
|
|
88
100
|
async readText(storageUri) {
|
|
89
|
-
const { key, bucket: bucketName } =
|
|
101
|
+
const { key, bucket: bucketName } = parseSupabaseStorageUri(storageUri);
|
|
90
102
|
if (bucketName !== config.bucketName) throw new Error(`Bucket name mismatch: expected "${config.bucketName}", but found "${bucketName}".`);
|
|
91
103
|
const { data, error } = await bucket.download(key);
|
|
92
104
|
if (error) {
|
|
@@ -99,14 +111,10 @@ const supabaseStorage = createUniversalStoragePlugin({
|
|
|
99
111
|
async getDownloadUrl(storageUri) {
|
|
100
112
|
const u = new URL(storageUri);
|
|
101
113
|
if (u.protocol.replace(":", "") !== "supabase-storage") throw new Error("Invalid Supabase storage URI protocol");
|
|
102
|
-
let key = `${u.host}${u.pathname}`.replace(/^\//, "");
|
|
114
|
+
let key = decodeStorageObjectKey(`${u.host}${u.pathname}`.replace(/^\//, ""));
|
|
103
115
|
if (!key) throw new Error("Invalid Supabase storage URI: missing key");
|
|
104
116
|
if (key.startsWith(`${config.bucketName}/`)) key = key.substring(`${config.bucketName}/`.length);
|
|
105
|
-
return { fileUrl: await
|
|
106
|
-
bucket,
|
|
107
|
-
key,
|
|
108
|
-
expiresIn: 3600
|
|
109
|
-
}) };
|
|
117
|
+
return { fileUrl: await resolveSignedUrl(config.bucketName, key) };
|
|
110
118
|
}
|
|
111
119
|
}
|
|
112
120
|
};
|
package/dist/{supabaseEdgeFunctionStorage-CO70azPw.mjs → supabaseEdgeFunctionStorage-B3yJ5yys.mjs}
RENAMED
|
@@ -263,17 +263,86 @@ const supabaseEdgeFunctionDatabase = (config, hooks) => {
|
|
|
263
263
|
}, hooks);
|
|
264
264
|
};
|
|
265
265
|
//#endregion
|
|
266
|
-
//#region src/
|
|
266
|
+
//#region src/supabaseSignedUrlBatcher.ts
|
|
267
267
|
function getErrorMessage(error) {
|
|
268
268
|
if (error instanceof Error) return error.message;
|
|
269
269
|
if (error && typeof error === "object" && "message" in error && typeof error.message === "string") return error.message;
|
|
270
270
|
return String(error);
|
|
271
271
|
}
|
|
272
|
+
const createSupabaseSignedUrlBatcher = ({ createSignedUrls, expiresIn, formatObjectPath }) => {
|
|
273
|
+
let pendingByBucket = /* @__PURE__ */ new Map();
|
|
274
|
+
let flushScheduled = false;
|
|
275
|
+
const createSignedUrlError = (bucketName, key, error) => /* @__PURE__ */ new Error(`Failed to generate download URL for "${formatObjectPath(bucketName, key)}": ${getErrorMessage(error)}`);
|
|
276
|
+
const flush = async () => {
|
|
277
|
+
const batches = pendingByBucket;
|
|
278
|
+
pendingByBucket = /* @__PURE__ */ new Map();
|
|
279
|
+
flushScheduled = false;
|
|
280
|
+
await Promise.all([...batches.entries()].map(async ([bucketName, pending]) => {
|
|
281
|
+
try {
|
|
282
|
+
const { data, error } = await createSignedUrls(bucketName, pending.map(({ key }) => key), expiresIn);
|
|
283
|
+
if (error || !data) {
|
|
284
|
+
const batchError = error ?? /* @__PURE__ */ new Error("missing signed URL response");
|
|
285
|
+
for (const request of pending) request.reject(createSignedUrlError(bucketName, request.key, batchError));
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
pending.forEach((request, index) => {
|
|
289
|
+
const result = data[index];
|
|
290
|
+
if (!result?.error && result?.signedUrl) {
|
|
291
|
+
request.resolve(result.signedUrl);
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
request.reject(createSignedUrlError(bucketName, request.key, result?.error ?? /* @__PURE__ */ new Error("missing signed URL")));
|
|
295
|
+
});
|
|
296
|
+
} catch (error) {
|
|
297
|
+
for (const request of pending) request.reject(createSignedUrlError(bucketName, request.key, error));
|
|
298
|
+
}
|
|
299
|
+
}));
|
|
300
|
+
};
|
|
301
|
+
return (bucketName, key) => new Promise((resolve, reject) => {
|
|
302
|
+
const pending = pendingByBucket.get(bucketName) ?? [];
|
|
303
|
+
pending.push({
|
|
304
|
+
key,
|
|
305
|
+
reject,
|
|
306
|
+
resolve
|
|
307
|
+
});
|
|
308
|
+
pendingByBucket.set(bucketName, pending);
|
|
309
|
+
if (!flushScheduled) {
|
|
310
|
+
flushScheduled = true;
|
|
311
|
+
queueMicrotask(() => {
|
|
312
|
+
flush();
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
};
|
|
317
|
+
//#endregion
|
|
318
|
+
//#region src/supabaseStorageKey.ts
|
|
319
|
+
/**
|
|
320
|
+
* Supabase upload responses echo a percent-encoded object key (`data.Key`),
|
|
321
|
+
* so the `supabase-storage://` URIs built from them keep that encoding.
|
|
322
|
+
*
|
|
323
|
+
* Storage API endpoints that carry the key in the request path
|
|
324
|
+
* (`createSignedUrl`, `download`, `exists`) decode it server side, but the
|
|
325
|
+
* ones that carry keys in the JSON body (`createSignedUrls`, `remove`) do
|
|
326
|
+
* not, and look up an object whose name literally contains `%40`.
|
|
327
|
+
*
|
|
328
|
+
* Decoding when the URI is parsed keeps both request shapes pointing at the
|
|
329
|
+
* same object. Keys that are not valid percent-encoded sequences are left
|
|
330
|
+
* untouched.
|
|
331
|
+
*/
|
|
332
|
+
const decodeStorageObjectKey = (key) => {
|
|
333
|
+
try {
|
|
334
|
+
return decodeURIComponent(key);
|
|
335
|
+
} catch {
|
|
336
|
+
return key;
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
//#endregion
|
|
340
|
+
//#region src/supabaseEdgeFunctionStorage.ts
|
|
272
341
|
const parseSupabaseStorageUri = (storageUri) => {
|
|
273
342
|
const storageUrl = new URL(storageUri);
|
|
274
343
|
if (storageUrl.protocol !== "supabase-storage:") throw new Error("Invalid Supabase storage URI protocol");
|
|
275
344
|
const bucketName = storageUrl.host;
|
|
276
|
-
const key = storageUrl.pathname.replace(/^\/+/, "");
|
|
345
|
+
const key = decodeStorageObjectKey(storageUrl.pathname.replace(/^\/+/, ""));
|
|
277
346
|
if (!bucketName || !key) throw new Error("Invalid Supabase storage URI");
|
|
278
347
|
return {
|
|
279
348
|
bucketName,
|
|
@@ -285,6 +354,11 @@ const supabaseEdgeFunctionStorage = createRuntimeStoragePlugin({
|
|
|
285
354
|
supportedProtocol: "supabase-storage",
|
|
286
355
|
factory: (config) => {
|
|
287
356
|
const supabase = createClient(config.supabaseUrl, config.supabaseServiceRoleKey);
|
|
357
|
+
const resolveSignedUrl = createSupabaseSignedUrlBatcher({
|
|
358
|
+
createSignedUrls: (bucketName, keys, expiresIn) => supabase.storage.from(bucketName).createSignedUrls(keys, expiresIn),
|
|
359
|
+
expiresIn: config.signedUrlExpiresIn ?? 3600,
|
|
360
|
+
formatObjectPath: (bucketName, key) => `${bucketName}/${key}`
|
|
361
|
+
});
|
|
288
362
|
return {
|
|
289
363
|
async readText(storageUri) {
|
|
290
364
|
const { bucketName, key } = parseSupabaseStorageUri(storageUri);
|
|
@@ -298,22 +372,10 @@ const supabaseEdgeFunctionStorage = createRuntimeStoragePlugin({
|
|
|
298
372
|
},
|
|
299
373
|
async getDownloadUrl(storageUri) {
|
|
300
374
|
const { bucketName, key } = parseSupabaseStorageUri(storageUri);
|
|
301
|
-
|
|
302
|
-
const expiresIn = config.signedUrlExpiresIn ?? 3600;
|
|
303
|
-
let data = null;
|
|
304
|
-
let error = null;
|
|
305
|
-
try {
|
|
306
|
-
const response = await bucket.createSignedUrl(key, expiresIn);
|
|
307
|
-
data = response.data;
|
|
308
|
-
error = response.error;
|
|
309
|
-
} catch (thrownError) {
|
|
310
|
-
error = thrownError;
|
|
311
|
-
}
|
|
312
|
-
if (!error && data?.signedUrl) return { fileUrl: data.signedUrl };
|
|
313
|
-
throw new Error(`Failed to generate download URL for "${bucketName}/${key}": ${getErrorMessage(error ?? /* @__PURE__ */ new Error("missing signed URL"))}`);
|
|
375
|
+
return { fileUrl: await resolveSignedUrl(bucketName, key) };
|
|
314
376
|
}
|
|
315
377
|
};
|
|
316
378
|
}
|
|
317
379
|
});
|
|
318
380
|
//#endregion
|
|
319
|
-
export {
|
|
381
|
+
export { supabaseDatabase as a, supabaseEdgeFunctionDatabase as i, decodeStorageObjectKey as n, resolveSupabaseServiceRoleKey as o, createSupabaseSignedUrlBatcher as r, supabaseEdgeFunctionStorage as t };
|
package/dist/{supabaseEdgeFunctionStorage-DRxwDPRy.cjs → supabaseEdgeFunctionStorage-DczYIzj_.cjs}
RENAMED
|
@@ -264,17 +264,86 @@ const supabaseEdgeFunctionDatabase = (config, hooks) => {
|
|
|
264
264
|
}, hooks);
|
|
265
265
|
};
|
|
266
266
|
//#endregion
|
|
267
|
-
//#region src/
|
|
267
|
+
//#region src/supabaseSignedUrlBatcher.ts
|
|
268
268
|
function getErrorMessage(error) {
|
|
269
269
|
if (error instanceof Error) return error.message;
|
|
270
270
|
if (error && typeof error === "object" && "message" in error && typeof error.message === "string") return error.message;
|
|
271
271
|
return String(error);
|
|
272
272
|
}
|
|
273
|
+
const createSupabaseSignedUrlBatcher = ({ createSignedUrls, expiresIn, formatObjectPath }) => {
|
|
274
|
+
let pendingByBucket = /* @__PURE__ */ new Map();
|
|
275
|
+
let flushScheduled = false;
|
|
276
|
+
const createSignedUrlError = (bucketName, key, error) => /* @__PURE__ */ new Error(`Failed to generate download URL for "${formatObjectPath(bucketName, key)}": ${getErrorMessage(error)}`);
|
|
277
|
+
const flush = async () => {
|
|
278
|
+
const batches = pendingByBucket;
|
|
279
|
+
pendingByBucket = /* @__PURE__ */ new Map();
|
|
280
|
+
flushScheduled = false;
|
|
281
|
+
await Promise.all([...batches.entries()].map(async ([bucketName, pending]) => {
|
|
282
|
+
try {
|
|
283
|
+
const { data, error } = await createSignedUrls(bucketName, pending.map(({ key }) => key), expiresIn);
|
|
284
|
+
if (error || !data) {
|
|
285
|
+
const batchError = error ?? /* @__PURE__ */ new Error("missing signed URL response");
|
|
286
|
+
for (const request of pending) request.reject(createSignedUrlError(bucketName, request.key, batchError));
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
pending.forEach((request, index) => {
|
|
290
|
+
const result = data[index];
|
|
291
|
+
if (!result?.error && result?.signedUrl) {
|
|
292
|
+
request.resolve(result.signedUrl);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
request.reject(createSignedUrlError(bucketName, request.key, result?.error ?? /* @__PURE__ */ new Error("missing signed URL")));
|
|
296
|
+
});
|
|
297
|
+
} catch (error) {
|
|
298
|
+
for (const request of pending) request.reject(createSignedUrlError(bucketName, request.key, error));
|
|
299
|
+
}
|
|
300
|
+
}));
|
|
301
|
+
};
|
|
302
|
+
return (bucketName, key) => new Promise((resolve, reject) => {
|
|
303
|
+
const pending = pendingByBucket.get(bucketName) ?? [];
|
|
304
|
+
pending.push({
|
|
305
|
+
key,
|
|
306
|
+
reject,
|
|
307
|
+
resolve
|
|
308
|
+
});
|
|
309
|
+
pendingByBucket.set(bucketName, pending);
|
|
310
|
+
if (!flushScheduled) {
|
|
311
|
+
flushScheduled = true;
|
|
312
|
+
queueMicrotask(() => {
|
|
313
|
+
flush();
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
};
|
|
318
|
+
//#endregion
|
|
319
|
+
//#region src/supabaseStorageKey.ts
|
|
320
|
+
/**
|
|
321
|
+
* Supabase upload responses echo a percent-encoded object key (`data.Key`),
|
|
322
|
+
* so the `supabase-storage://` URIs built from them keep that encoding.
|
|
323
|
+
*
|
|
324
|
+
* Storage API endpoints that carry the key in the request path
|
|
325
|
+
* (`createSignedUrl`, `download`, `exists`) decode it server side, but the
|
|
326
|
+
* ones that carry keys in the JSON body (`createSignedUrls`, `remove`) do
|
|
327
|
+
* not, and look up an object whose name literally contains `%40`.
|
|
328
|
+
*
|
|
329
|
+
* Decoding when the URI is parsed keeps both request shapes pointing at the
|
|
330
|
+
* same object. Keys that are not valid percent-encoded sequences are left
|
|
331
|
+
* untouched.
|
|
332
|
+
*/
|
|
333
|
+
const decodeStorageObjectKey = (key) => {
|
|
334
|
+
try {
|
|
335
|
+
return decodeURIComponent(key);
|
|
336
|
+
} catch {
|
|
337
|
+
return key;
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
//#endregion
|
|
341
|
+
//#region src/supabaseEdgeFunctionStorage.ts
|
|
273
342
|
const parseSupabaseStorageUri = (storageUri) => {
|
|
274
343
|
const storageUrl = new URL(storageUri);
|
|
275
344
|
if (storageUrl.protocol !== "supabase-storage:") throw new Error("Invalid Supabase storage URI protocol");
|
|
276
345
|
const bucketName = storageUrl.host;
|
|
277
|
-
const key = storageUrl.pathname.replace(/^\/+/, "");
|
|
346
|
+
const key = decodeStorageObjectKey(storageUrl.pathname.replace(/^\/+/, ""));
|
|
278
347
|
if (!bucketName || !key) throw new Error("Invalid Supabase storage URI");
|
|
279
348
|
return {
|
|
280
349
|
bucketName,
|
|
@@ -286,6 +355,11 @@ const supabaseEdgeFunctionStorage = (0, _hot_updater_plugin_core.createRuntimeSt
|
|
|
286
355
|
supportedProtocol: "supabase-storage",
|
|
287
356
|
factory: (config) => {
|
|
288
357
|
const supabase = (0, _supabase_supabase_js.createClient)(config.supabaseUrl, config.supabaseServiceRoleKey);
|
|
358
|
+
const resolveSignedUrl = createSupabaseSignedUrlBatcher({
|
|
359
|
+
createSignedUrls: (bucketName, keys, expiresIn) => supabase.storage.from(bucketName).createSignedUrls(keys, expiresIn),
|
|
360
|
+
expiresIn: config.signedUrlExpiresIn ?? 3600,
|
|
361
|
+
formatObjectPath: (bucketName, key) => `${bucketName}/${key}`
|
|
362
|
+
});
|
|
289
363
|
return {
|
|
290
364
|
async readText(storageUri) {
|
|
291
365
|
const { bucketName, key } = parseSupabaseStorageUri(storageUri);
|
|
@@ -299,24 +373,24 @@ const supabaseEdgeFunctionStorage = (0, _hot_updater_plugin_core.createRuntimeSt
|
|
|
299
373
|
},
|
|
300
374
|
async getDownloadUrl(storageUri) {
|
|
301
375
|
const { bucketName, key } = parseSupabaseStorageUri(storageUri);
|
|
302
|
-
|
|
303
|
-
const expiresIn = config.signedUrlExpiresIn ?? 3600;
|
|
304
|
-
let data = null;
|
|
305
|
-
let error = null;
|
|
306
|
-
try {
|
|
307
|
-
const response = await bucket.createSignedUrl(key, expiresIn);
|
|
308
|
-
data = response.data;
|
|
309
|
-
error = response.error;
|
|
310
|
-
} catch (thrownError) {
|
|
311
|
-
error = thrownError;
|
|
312
|
-
}
|
|
313
|
-
if (!error && data?.signedUrl) return { fileUrl: data.signedUrl };
|
|
314
|
-
throw new Error(`Failed to generate download URL for "${bucketName}/${key}": ${getErrorMessage(error ?? /* @__PURE__ */ new Error("missing signed URL"))}`);
|
|
376
|
+
return { fileUrl: await resolveSignedUrl(bucketName, key) };
|
|
315
377
|
}
|
|
316
378
|
};
|
|
317
379
|
}
|
|
318
380
|
});
|
|
319
381
|
//#endregion
|
|
382
|
+
Object.defineProperty(exports, "createSupabaseSignedUrlBatcher", {
|
|
383
|
+
enumerable: true,
|
|
384
|
+
get: function() {
|
|
385
|
+
return createSupabaseSignedUrlBatcher;
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
Object.defineProperty(exports, "decodeStorageObjectKey", {
|
|
389
|
+
enumerable: true,
|
|
390
|
+
get: function() {
|
|
391
|
+
return decodeStorageObjectKey;
|
|
392
|
+
}
|
|
393
|
+
});
|
|
320
394
|
Object.defineProperty(exports, "resolveSupabaseServiceRoleKey", {
|
|
321
395
|
enumerable: true,
|
|
322
396
|
get: function() {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hot-updater/supabase",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.36.1",
|
|
5
5
|
"description": "React Native OTA solution for self-hosted",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
7
7
|
"module": "dist/index.mjs",
|
|
@@ -55,10 +55,10 @@
|
|
|
55
55
|
"@supabase/supabase-js": "2.76.1",
|
|
56
56
|
"hono": "4.12.9",
|
|
57
57
|
"uuidv7": "^1.0.2",
|
|
58
|
-
"@hot-updater/core": "0.
|
|
59
|
-
"@hot-updater/plugin-core": "0.
|
|
60
|
-
"@hot-updater/cli-tools": "0.
|
|
61
|
-
"@hot-updater/server": "0.
|
|
58
|
+
"@hot-updater/core": "0.36.1",
|
|
59
|
+
"@hot-updater/plugin-core": "0.36.1",
|
|
60
|
+
"@hot-updater/cli-tools": "0.36.1",
|
|
61
|
+
"@hot-updater/server": "0.36.1"
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
64
|
"@electric-sql/pglite": "0.4.1",
|
|
@@ -68,10 +68,10 @@
|
|
|
68
68
|
"execa": "9.5.2",
|
|
69
69
|
"@types/node": "^20",
|
|
70
70
|
"mime": "^4.0.4",
|
|
71
|
-
"@hot-updater/js": "0.
|
|
72
|
-
"@hot-updater/
|
|
73
|
-
"@hot-updater/
|
|
74
|
-
"@hot-updater/
|
|
71
|
+
"@hot-updater/js": "0.36.1",
|
|
72
|
+
"@hot-updater/test-utils": "0.36.1",
|
|
73
|
+
"@hot-updater/postgres": "0.36.1",
|
|
74
|
+
"@hot-updater/mock": "0.36.1"
|
|
75
75
|
},
|
|
76
76
|
"scripts": {
|
|
77
77
|
"build": "tsdown",
|