@hot-updater/supabase 0.31.4 → 0.32.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/edge.cjs CHANGED
@@ -1,4 +1,4 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_supabaseEdgeFunctionStorage = require("./supabaseEdgeFunctionStorage-BZC0Z0XP.cjs");
2
+ const require_supabaseEdgeFunctionStorage = require("./supabaseEdgeFunctionStorage-D933mGy9.cjs");
3
3
  exports.supabaseEdgeFunctionDatabase = require_supabaseEdgeFunctionStorage.supabaseEdgeFunctionDatabase;
4
4
  exports.supabaseEdgeFunctionStorage = require_supabaseEdgeFunctionStorage.supabaseEdgeFunctionStorage;
package/dist/edge.d.cts CHANGED
@@ -1,2 +1,2 @@
1
- import { i as supabaseEdgeFunctionDatabase, n as supabaseEdgeFunctionStorage } from "./supabaseEdgeFunctionStorage-hkokSgXP.cjs";
1
+ import { i as supabaseEdgeFunctionDatabase, n as supabaseEdgeFunctionStorage } from "./supabaseEdgeFunctionStorage-CU396KO3.cjs";
2
2
  export { supabaseEdgeFunctionDatabase, supabaseEdgeFunctionStorage };
package/dist/edge.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { i as supabaseEdgeFunctionDatabase, n as supabaseEdgeFunctionStorage } from "./supabaseEdgeFunctionStorage-CXmcRBZ2.mjs";
1
+ import { i as supabaseEdgeFunctionDatabase, n as supabaseEdgeFunctionStorage } from "./supabaseEdgeFunctionStorage-BRxGvt-r.mjs";
2
2
  export { supabaseEdgeFunctionDatabase, supabaseEdgeFunctionStorage };
package/dist/edge.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { n as supabaseEdgeFunctionDatabase, t as supabaseEdgeFunctionStorage } from "./supabaseEdgeFunctionStorage-BKU_mLzA.mjs";
1
+ import { n as supabaseEdgeFunctionDatabase, t as supabaseEdgeFunctionStorage } from "./supabaseEdgeFunctionStorage-B4KN0khj.mjs";
2
2
  export { supabaseEdgeFunctionDatabase, supabaseEdgeFunctionStorage };
@@ -6,7 +6,7 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __getProtoOf = Object.getPrototypeOf;
8
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
9
- var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
9
+ var __commonJSMin = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
10
10
  var __copyProps = (to, from, except, desc) => {
11
11
  if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
12
12
  key = keys[i];
@@ -23,21 +23,21 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
23
  }) : target, mod));
24
24
  //#endregion
25
25
  let fs_promises = require("fs/promises");
26
- fs_promises = __toESM(fs_promises, 1);
26
+ fs_promises = __toESM(fs_promises);
27
27
  let node_module = require("node:module");
28
28
  let path = require("path");
29
- path = __toESM(path, 1);
29
+ path = __toESM(path);
30
30
  let _hot_updater_cli_tools = require("@hot-updater/cli-tools");
31
31
  let node_url = require("node:url");
32
32
  let node_child_process = require("node:child_process");
33
33
  let node_string_decoder = require("node:string_decoder");
34
34
  let node_util = require("node:util");
35
35
  let node_process = require("node:process");
36
- node_process = __toESM(node_process, 1);
36
+ node_process = __toESM(node_process);
37
37
  let node_tty = require("node:tty");
38
- node_tty = __toESM(node_tty, 1);
38
+ node_tty = __toESM(node_tty);
39
39
  let node_path = require("node:path");
40
- node_path = __toESM(node_path, 1);
40
+ node_path = __toESM(node_path);
41
41
  let node_timers_promises = require("node:timers/promises");
42
42
  let node_os = require("node:os");
43
43
  let node_events = require("node:events");
@@ -6233,8 +6233,8 @@ createExeca(mapScriptAsync, {}, deepScriptOptions, setScriptSync);
6233
6233
  const { sendMessage, getOneMessage, getEachMessage, getCancelSignal } = getIpcExport();
6234
6234
  //#endregion
6235
6235
  //#region iac/supabaseApi.ts
6236
- const supabaseApi = (supabaseUrl, supabaseAnonKey) => {
6237
- const supabase = (0, _supabase_supabase_js.createClient)(supabaseUrl, supabaseAnonKey);
6236
+ const supabaseApi = (supabaseUrl, supabaseServiceRoleKey) => {
6237
+ const supabase = (0, _supabase_supabase_js.createClient)(supabaseUrl, supabaseServiceRoleKey);
6238
6238
  return {
6239
6239
  listBuckets: async () => {
6240
6240
  const { data, error } = await supabase.storage.listBuckets();
@@ -6268,7 +6268,7 @@ const getConfigScaffold = (build) => {
6268
6268
  }],
6269
6269
  configString: `supabaseStorage({
6270
6270
  supabaseUrl: process.env.HOT_UPDATER_SUPABASE_URL!,
6271
- supabaseAnonKey: process.env.HOT_UPDATER_SUPABASE_ANON_KEY!,
6271
+ supabaseServiceRoleKey: process.env.HOT_UPDATER_SUPABASE_SERVICE_ROLE_KEY!,
6272
6272
  bucketName: process.env.HOT_UPDATER_SUPABASE_BUCKET_NAME!,
6273
6273
  })`
6274
6274
  }).setDatabase({
@@ -6278,10 +6278,27 @@ const getConfigScaffold = (build) => {
6278
6278
  }],
6279
6279
  configString: `supabaseDatabase({
6280
6280
  supabaseUrl: process.env.HOT_UPDATER_SUPABASE_URL!,
6281
- supabaseAnonKey: process.env.HOT_UPDATER_SUPABASE_ANON_KEY!,
6281
+ supabaseServiceRoleKey: process.env.HOT_UPDATER_SUPABASE_SERVICE_ROLE_KEY!,
6282
6282
  })`
6283
6283
  }));
6284
6284
  };
6285
+ const getLegacySupabaseConfigReference = (configText) => {
6286
+ if (configText.includes("HOT_UPDATER_SUPABASE_ANON_KEY")) return "HOT_UPDATER_SUPABASE_ANON_KEY";
6287
+ if (/\bsupabaseAnonKey\s*:/.test(configText)) return "supabaseAnonKey";
6288
+ return null;
6289
+ };
6290
+ const assertSkippedConfigDoesNotUseLegacySupabaseKey = async (configWriteResult) => {
6291
+ if (configWriteResult.status !== "skipped") return;
6292
+ const configText = await fs_promises.default.readFile(configWriteResult.path, "utf-8").catch((error) => {
6293
+ if (error.code === "ENOENT") return null;
6294
+ throw error;
6295
+ });
6296
+ const legacyReference = configText === null ? null : getLegacySupabaseConfigReference(configText);
6297
+ if (!legacyReference) return;
6298
+ _hot_updater_cli_tools.p.log.error(`Existing '${configWriteResult.path}' still references '${legacyReference}'.`);
6299
+ _hot_updater_cli_tools.p.log.message("Update it to use 'supabaseServiceRoleKey' with 'HOT_UPDATER_SUPABASE_SERVICE_ROLE_KEY', then run init again.");
6300
+ process.exit(1);
6301
+ };
6285
6302
  const SOURCE_TEMPLATE = `// add this to your App.tsx
6286
6303
  import { HotUpdater } from "@hot-updater/react-native";
6287
6304
 
@@ -6642,9 +6659,9 @@ const runInit = async ({ build }) => {
6642
6659
  process.exit(1);
6643
6660
  }
6644
6661
  spinner.stop();
6645
- const serviceRoleKey = apiKeys.find((key) => key.name === "service_role");
6646
- if (!serviceRoleKey) throw new Error("Service role key not found, is your project paused?");
6647
- const bucket = await selectBucket(supabaseApi(`https://${project.id}.supabase.co`, serviceRoleKey.api_key));
6662
+ const serviceRoleApiKey = apiKeys.find((key) => key.name === "service_role");
6663
+ if (!serviceRoleApiKey) throw new Error("Service role key not found, is your project paused?");
6664
+ const bucket = await selectBucket(supabaseApi(`https://${project.id}.supabase.co`, serviceRoleApiKey.api_key));
6648
6665
  const { tmpDir, removeTmpDir } = await (0, _hot_updater_cli_tools.copyDirToTmp)(path.default.dirname(path.default.resolve(require$1.resolve("@hot-updater/supabase/scaffold"))), "supabase");
6649
6666
  const migrationPath = await path.default.join(tmpDir, "supabase", "migrations");
6650
6667
  const migrationFiles = await fs_promises.default.readdir(migrationPath);
@@ -6663,8 +6680,9 @@ const runInit = async ({ build }) => {
6663
6680
  await deployEdgeFunction(tmpDir, project.id);
6664
6681
  await removeTmpDir();
6665
6682
  const configWriteResult = await (0, _hot_updater_cli_tools.writeHotUpdaterConfig)(getConfigScaffold(build));
6683
+ await assertSkippedConfigDoesNotUseLegacySupabaseKey(configWriteResult);
6666
6684
  await (0, _hot_updater_cli_tools.makeEnv)({
6667
- HOT_UPDATER_SUPABASE_ANON_KEY: serviceRoleKey.api_key,
6685
+ HOT_UPDATER_SUPABASE_SERVICE_ROLE_KEY: serviceRoleApiKey.api_key,
6668
6686
  HOT_UPDATER_SUPABASE_BUCKET_NAME: bucket.name,
6669
6687
  HOT_UPDATER_SUPABASE_URL: `https://${project.id}.supabase.co`
6670
6688
  });
@@ -6677,6 +6695,7 @@ const runInit = async ({ build }) => {
6677
6695
  _hot_updater_cli_tools.p.log.success("Done! 🎉");
6678
6696
  };
6679
6697
  //#endregion
6698
+ exports.getLegacySupabaseConfigReference = getLegacySupabaseConfigReference;
6680
6699
  exports.resolveEdgeFunctionDenoConfig = resolveEdgeFunctionDenoConfig;
6681
6700
  exports.runInit = runInit;
6682
6701
  exports.selectBucket = selectBucket;
@@ -16,6 +16,7 @@ interface SupabaseApi {
16
16
  }
17
17
  //#endregion
18
18
  //#region iac/index.d.ts
19
+ declare const getLegacySupabaseConfigReference: (configText: string) => "HOT_UPDATER_SUPABASE_ANON_KEY" | "supabaseAnonKey" | null;
19
20
  declare const resolveEdgeFunctionDenoConfig: (targetDir: string) => Promise<{
20
21
  imports: Record<string, string>;
21
22
  }>;
@@ -34,4 +35,4 @@ declare const runInit: ({
34
35
  build: BuildType;
35
36
  }) => Promise<void>;
36
37
  //#endregion
37
- export { resolveEdgeFunctionDenoConfig, runInit, selectBucket, selectProject };
38
+ export { getLegacySupabaseConfigReference, resolveEdgeFunctionDenoConfig, runInit, selectBucket, selectProject };
@@ -16,6 +16,7 @@ interface SupabaseApi {
16
16
  }
17
17
  //#endregion
18
18
  //#region iac/index.d.ts
19
+ declare const getLegacySupabaseConfigReference: (configText: string) => "HOT_UPDATER_SUPABASE_ANON_KEY" | "supabaseAnonKey" | null;
19
20
  declare const resolveEdgeFunctionDenoConfig: (targetDir: string) => Promise<{
20
21
  imports: Record<string, string>;
21
22
  }>;
@@ -34,4 +35,4 @@ declare const runInit: ({
34
35
  build: BuildType;
35
36
  }) => Promise<void>;
36
37
  //#endregion
37
- export { resolveEdgeFunctionDenoConfig, runInit, selectBucket, selectProject };
38
+ export { getLegacySupabaseConfigReference, resolveEdgeFunctionDenoConfig, runInit, selectBucket, selectProject };
@@ -25,7 +25,7 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
25
25
  var __getOwnPropNames = Object.getOwnPropertyNames;
26
26
  var __getProtoOf = Object.getPrototypeOf;
27
27
  var __hasOwnProp = Object.prototype.hasOwnProperty;
28
- var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
28
+ var __commonJSMin = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
29
29
  var __copyProps = (to, from, except, desc) => {
30
30
  if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
31
31
  key = keys[i];
@@ -6228,8 +6228,8 @@ createExeca(mapScriptAsync, {}, deepScriptOptions, setScriptSync);
6228
6228
  const { sendMessage, getOneMessage, getEachMessage, getCancelSignal } = getIpcExport();
6229
6229
  //#endregion
6230
6230
  //#region iac/supabaseApi.ts
6231
- const supabaseApi = (supabaseUrl, supabaseAnonKey) => {
6232
- const supabase = createClient(supabaseUrl, supabaseAnonKey);
6231
+ const supabaseApi = (supabaseUrl, supabaseServiceRoleKey) => {
6232
+ const supabase = createClient(supabaseUrl, supabaseServiceRoleKey);
6233
6233
  return {
6234
6234
  listBuckets: async () => {
6235
6235
  const { data, error } = await supabase.storage.listBuckets();
@@ -6263,7 +6263,7 @@ const getConfigScaffold = (build) => {
6263
6263
  }],
6264
6264
  configString: `supabaseStorage({
6265
6265
  supabaseUrl: process.env.HOT_UPDATER_SUPABASE_URL!,
6266
- supabaseAnonKey: process.env.HOT_UPDATER_SUPABASE_ANON_KEY!,
6266
+ supabaseServiceRoleKey: process.env.HOT_UPDATER_SUPABASE_SERVICE_ROLE_KEY!,
6267
6267
  bucketName: process.env.HOT_UPDATER_SUPABASE_BUCKET_NAME!,
6268
6268
  })`
6269
6269
  }).setDatabase({
@@ -6273,10 +6273,27 @@ const getConfigScaffold = (build) => {
6273
6273
  }],
6274
6274
  configString: `supabaseDatabase({
6275
6275
  supabaseUrl: process.env.HOT_UPDATER_SUPABASE_URL!,
6276
- supabaseAnonKey: process.env.HOT_UPDATER_SUPABASE_ANON_KEY!,
6276
+ supabaseServiceRoleKey: process.env.HOT_UPDATER_SUPABASE_SERVICE_ROLE_KEY!,
6277
6277
  })`
6278
6278
  }));
6279
6279
  };
6280
+ const getLegacySupabaseConfigReference = (configText) => {
6281
+ if (configText.includes("HOT_UPDATER_SUPABASE_ANON_KEY")) return "HOT_UPDATER_SUPABASE_ANON_KEY";
6282
+ if (/\bsupabaseAnonKey\s*:/.test(configText)) return "supabaseAnonKey";
6283
+ return null;
6284
+ };
6285
+ const assertSkippedConfigDoesNotUseLegacySupabaseKey = async (configWriteResult) => {
6286
+ if (configWriteResult.status !== "skipped") return;
6287
+ const configText = await fs.readFile(configWriteResult.path, "utf-8").catch((error) => {
6288
+ if (error.code === "ENOENT") return null;
6289
+ throw error;
6290
+ });
6291
+ const legacyReference = configText === null ? null : getLegacySupabaseConfigReference(configText);
6292
+ if (!legacyReference) return;
6293
+ p.log.error(`Existing '${configWriteResult.path}' still references '${legacyReference}'.`);
6294
+ p.log.message("Update it to use 'supabaseServiceRoleKey' with 'HOT_UPDATER_SUPABASE_SERVICE_ROLE_KEY', then run init again.");
6295
+ process.exit(1);
6296
+ };
6280
6297
  const SOURCE_TEMPLATE = `// add this to your App.tsx
6281
6298
  import { HotUpdater } from "@hot-updater/react-native";
6282
6299
 
@@ -6637,9 +6654,9 @@ const runInit = async ({ build }) => {
6637
6654
  process.exit(1);
6638
6655
  }
6639
6656
  spinner.stop();
6640
- const serviceRoleKey = apiKeys.find((key) => key.name === "service_role");
6641
- if (!serviceRoleKey) throw new Error("Service role key not found, is your project paused?");
6642
- const bucket = await selectBucket(supabaseApi(`https://${project.id}.supabase.co`, serviceRoleKey.api_key));
6657
+ const serviceRoleApiKey = apiKeys.find((key) => key.name === "service_role");
6658
+ if (!serviceRoleApiKey) throw new Error("Service role key not found, is your project paused?");
6659
+ const bucket = await selectBucket(supabaseApi(`https://${project.id}.supabase.co`, serviceRoleApiKey.api_key));
6643
6660
  const { tmpDir, removeTmpDir } = await copyDirToTmp(path.dirname(path.resolve(require$1.resolve("@hot-updater/supabase/scaffold"))), "supabase");
6644
6661
  const migrationPath = await path.join(tmpDir, "supabase", "migrations");
6645
6662
  const migrationFiles = await fs.readdir(migrationPath);
@@ -6658,8 +6675,9 @@ const runInit = async ({ build }) => {
6658
6675
  await deployEdgeFunction(tmpDir, project.id);
6659
6676
  await removeTmpDir();
6660
6677
  const configWriteResult = await writeHotUpdaterConfig(getConfigScaffold(build));
6678
+ await assertSkippedConfigDoesNotUseLegacySupabaseKey(configWriteResult);
6661
6679
  await makeEnv({
6662
- HOT_UPDATER_SUPABASE_ANON_KEY: serviceRoleKey.api_key,
6680
+ HOT_UPDATER_SUPABASE_SERVICE_ROLE_KEY: serviceRoleApiKey.api_key,
6663
6681
  HOT_UPDATER_SUPABASE_BUCKET_NAME: bucket.name,
6664
6682
  HOT_UPDATER_SUPABASE_URL: `https://${project.id}.supabase.co`
6665
6683
  });
@@ -6672,4 +6690,4 @@ const runInit = async ({ build }) => {
6672
6690
  p.log.success("Done! 🎉");
6673
6691
  };
6674
6692
  //#endregion
6675
- export { resolveEdgeFunctionDenoConfig, runInit, selectBucket, selectProject };
6693
+ export { getLegacySupabaseConfigReference, resolveEdgeFunctionDenoConfig, runInit, selectBucket, selectProject };
package/dist/index.cjs CHANGED
@@ -21,19 +21,44 @@ 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-BZC0Z0XP.cjs");
24
+ const require_supabaseEdgeFunctionStorage = require("./supabaseEdgeFunctionStorage-D933mGy9.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");
28
- fs_promises = __toESM(fs_promises, 1);
28
+ fs_promises = __toESM(fs_promises);
29
29
  let path = require("path");
30
- path = __toESM(path, 1);
30
+ path = __toESM(path);
31
31
  //#region src/supabaseStorage.ts
32
+ function getErrorMessage(error) {
33
+ if (error instanceof Error) return error.message;
34
+ if (error && typeof error === "object" && "message" in error && typeof error.message === "string") return error.message;
35
+ return String(error);
36
+ }
37
+ async function createSignedUrlOrThrow({ bucket, key, expiresIn }) {
38
+ let data = null;
39
+ let error = null;
40
+ try {
41
+ const response = await bucket.createSignedUrl(key, expiresIn);
42
+ data = response.data;
43
+ error = response.error;
44
+ } catch (thrownError) {
45
+ error = thrownError;
46
+ }
47
+ if (!error && data?.signedUrl) return data.signedUrl;
48
+ throw new Error(`Failed to generate download URL for "${key}": ${getErrorMessage(error ?? /* @__PURE__ */ new Error("missing signed URL"))}`);
49
+ }
50
+ async function verifyObjectCanBeSignedForRuntime({ bucket, key }) {
51
+ await createSignedUrlOrThrow({
52
+ bucket,
53
+ key,
54
+ expiresIn: 3600
55
+ });
56
+ }
32
57
  const supabaseStorage = (0, _hot_updater_plugin_core.createUniversalStoragePlugin)({
33
58
  name: "supabaseStorage",
34
59
  supportedProtocol: "supabase-storage",
35
60
  factory: (config) => {
36
- const bucket = (0, _supabase_supabase_js.createClient)(config.supabaseUrl, config.supabaseAnonKey).storage.from(config.bucketName);
61
+ const bucket = (0, _supabase_supabase_js.createClient)(config.supabaseUrl, require_supabaseEdgeFunctionStorage.resolveSupabaseServiceRoleKey(config)).storage.from(config.bucketName);
37
62
  const getStorageKey = (0, _hot_updater_plugin_core.createStorageKeyBuilder)(config.basePath);
38
63
  return {
39
64
  node: {
@@ -56,8 +81,24 @@ const supabaseStorage = (0, _hot_updater_plugin_core.createUniversalStoragePlugi
56
81
  headers: {}
57
82
  });
58
83
  if (upload.error) throw upload.error;
84
+ await verifyObjectCanBeSignedForRuntime({
85
+ bucket,
86
+ key: Key
87
+ });
59
88
  return { storageUri: `supabase-storage://${upload.data.fullPath}` };
60
89
  },
90
+ async exists(storageUri) {
91
+ const { key, bucket: bucketName } = (0, _hot_updater_plugin_core.parseStorageUri)(storageUri, "supabase-storage");
92
+ if (bucketName !== config.bucketName) throw new Error(`Bucket name mismatch: expected "${config.bucketName}", but found "${bucketName}".`);
93
+ const { data, error } = await bucket.exists(key);
94
+ if (data === false) return false;
95
+ if (error) throw error;
96
+ await verifyObjectCanBeSignedForRuntime({
97
+ bucket,
98
+ key
99
+ });
100
+ return data;
101
+ },
61
102
  async downloadFile(storageUri, filePath) {
62
103
  const { key, bucket: bucketName } = (0, _hot_updater_plugin_core.parseStorageUri)(storageUri, "supabase-storage");
63
104
  if (bucketName !== config.bucketName) throw new Error(`Bucket name mismatch: expected "${config.bucketName}", but found "${bucketName}".`);
@@ -86,10 +127,11 @@ const supabaseStorage = (0, _hot_updater_plugin_core.createUniversalStoragePlugi
86
127
  let key = `${u.host}${u.pathname}`.replace(/^\//, "");
87
128
  if (!key) throw new Error("Invalid Supabase storage URI: missing key");
88
129
  if (key.startsWith(`${config.bucketName}/`)) key = key.substring(`${config.bucketName}/`.length);
89
- const { data, error } = await bucket.createSignedUrl(key, 3600);
90
- if (error) throw new Error(`Failed to generate download URL: ${error.message}`);
91
- if (!data?.signedUrl) throw new Error("Failed to generate download URL");
92
- return { fileUrl: data.signedUrl };
130
+ return { fileUrl: await createSignedUrlOrThrow({
131
+ bucket,
132
+ key,
133
+ expiresIn: 3600
134
+ }) };
93
135
  }
94
136
  }
95
137
  };
package/dist/index.d.cts CHANGED
@@ -1,22 +1,29 @@
1
- import { i as supabaseEdgeFunctionDatabase, n as supabaseEdgeFunctionStorage, r as SupabaseEdgeFunctionDatabaseConfig, t as SupabaseEdgeFunctionStorageConfig } from "./supabaseEdgeFunctionStorage-hkokSgXP.cjs";
1
+ import { i as supabaseEdgeFunctionDatabase, n as supabaseEdgeFunctionStorage, r as SupabaseEdgeFunctionDatabaseConfig, t as SupabaseEdgeFunctionStorageConfig } from "./supabaseEdgeFunctionStorage-CU396KO3.cjs";
2
+ import * as _$_hot_updater_plugin_core0 from "@hot-updater/plugin-core";
2
3
 
3
- //#region src/supabaseDatabase.d.ts
4
- interface SupabaseDatabaseConfig {
4
+ //#region src/supabaseConfig.d.ts
5
+ type SupabaseServiceRoleConfig = {
6
+ supabaseUrl: string;
7
+ supabaseServiceRoleKey: string;
8
+ supabaseAnonKey?: string;
9
+ } | {
5
10
  supabaseUrl: string;
6
11
  supabaseAnonKey: string;
7
- }
8
- declare const supabaseDatabase: (config: SupabaseDatabaseConfig, hooks?: import("@hot-updater/plugin-core").DatabasePluginHooks) => () => import("@hot-updater/plugin-core").DatabasePlugin<unknown>;
12
+ supabaseServiceRoleKey?: string;
13
+ };
14
+ //#endregion
15
+ //#region src/supabaseDatabase.d.ts
16
+ type SupabaseDatabaseConfig = SupabaseServiceRoleConfig;
17
+ declare const supabaseDatabase: (config: SupabaseServiceRoleConfig, hooks?: _$_hot_updater_plugin_core0.DatabasePluginHooks) => () => _$_hot_updater_plugin_core0.DatabasePlugin<unknown>;
9
18
  //#endregion
10
19
  //#region src/supabaseStorage.d.ts
11
- interface SupabaseStorageConfig {
12
- supabaseUrl: string;
13
- supabaseAnonKey: string;
20
+ type SupabaseStorageConfig = SupabaseServiceRoleConfig & {
14
21
  bucketName: string;
15
22
  /**
16
23
  * Base path where bundles will be stored in the bucket
17
24
  */
18
25
  basePath?: string;
19
- }
20
- declare const supabaseStorage: (config: SupabaseStorageConfig, hooks?: import("@hot-updater/plugin-core").StoragePluginHooks) => () => import("@hot-updater/plugin-core").UniversalStoragePlugin<unknown>;
26
+ };
27
+ declare const supabaseStorage: (config: SupabaseStorageConfig, hooks?: _$_hot_updater_plugin_core0.StoragePluginHooks) => () => _$_hot_updater_plugin_core0.UniversalStoragePlugin<unknown>;
21
28
  //#endregion
22
29
  export { SupabaseDatabaseConfig, SupabaseEdgeFunctionDatabaseConfig, SupabaseEdgeFunctionStorageConfig, SupabaseStorageConfig, supabaseDatabase, supabaseEdgeFunctionDatabase, supabaseEdgeFunctionStorage, supabaseStorage };
package/dist/index.d.mts CHANGED
@@ -1,22 +1,29 @@
1
- import { i as supabaseEdgeFunctionDatabase, n as supabaseEdgeFunctionStorage, r as SupabaseEdgeFunctionDatabaseConfig, t as SupabaseEdgeFunctionStorageConfig } from "./supabaseEdgeFunctionStorage-CXmcRBZ2.mjs";
1
+ import { i as supabaseEdgeFunctionDatabase, n as supabaseEdgeFunctionStorage, r as SupabaseEdgeFunctionDatabaseConfig, t as SupabaseEdgeFunctionStorageConfig } from "./supabaseEdgeFunctionStorage-BRxGvt-r.mjs";
2
+ import * as _$_hot_updater_plugin_core0 from "@hot-updater/plugin-core";
2
3
 
3
- //#region src/supabaseDatabase.d.ts
4
- interface SupabaseDatabaseConfig {
4
+ //#region src/supabaseConfig.d.ts
5
+ type SupabaseServiceRoleConfig = {
6
+ supabaseUrl: string;
7
+ supabaseServiceRoleKey: string;
8
+ supabaseAnonKey?: string;
9
+ } | {
5
10
  supabaseUrl: string;
6
11
  supabaseAnonKey: string;
7
- }
8
- declare const supabaseDatabase: (config: SupabaseDatabaseConfig, hooks?: import("@hot-updater/plugin-core").DatabasePluginHooks) => () => import("@hot-updater/plugin-core").DatabasePlugin<unknown>;
12
+ supabaseServiceRoleKey?: string;
13
+ };
14
+ //#endregion
15
+ //#region src/supabaseDatabase.d.ts
16
+ type SupabaseDatabaseConfig = SupabaseServiceRoleConfig;
17
+ declare const supabaseDatabase: (config: SupabaseServiceRoleConfig, hooks?: _$_hot_updater_plugin_core0.DatabasePluginHooks) => () => _$_hot_updater_plugin_core0.DatabasePlugin<unknown>;
9
18
  //#endregion
10
19
  //#region src/supabaseStorage.d.ts
11
- interface SupabaseStorageConfig {
12
- supabaseUrl: string;
13
- supabaseAnonKey: string;
20
+ type SupabaseStorageConfig = SupabaseServiceRoleConfig & {
14
21
  bucketName: string;
15
22
  /**
16
23
  * Base path where bundles will be stored in the bucket
17
24
  */
18
25
  basePath?: string;
19
- }
20
- declare const supabaseStorage: (config: SupabaseStorageConfig, hooks?: import("@hot-updater/plugin-core").StoragePluginHooks) => () => import("@hot-updater/plugin-core").UniversalStoragePlugin<unknown>;
26
+ };
27
+ declare const supabaseStorage: (config: SupabaseStorageConfig, hooks?: _$_hot_updater_plugin_core0.StoragePluginHooks) => () => _$_hot_updater_plugin_core0.UniversalStoragePlugin<unknown>;
21
28
  //#endregion
22
29
  export { SupabaseDatabaseConfig, SupabaseEdgeFunctionDatabaseConfig, SupabaseEdgeFunctionStorageConfig, SupabaseStorageConfig, supabaseDatabase, supabaseEdgeFunctionDatabase, supabaseEdgeFunctionStorage, supabaseStorage };
package/dist/index.mjs CHANGED
@@ -1,14 +1,39 @@
1
- import { n as supabaseEdgeFunctionDatabase, r as supabaseDatabase, t as supabaseEdgeFunctionStorage } from "./supabaseEdgeFunctionStorage-BKU_mLzA.mjs";
1
+ import { i as resolveSupabaseServiceRoleKey, n as supabaseEdgeFunctionDatabase, r as supabaseDatabase, t as supabaseEdgeFunctionStorage } from "./supabaseEdgeFunctionStorage-B4KN0khj.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";
5
5
  import path from "path";
6
6
  //#region src/supabaseStorage.ts
7
+ function getErrorMessage(error) {
8
+ if (error instanceof Error) return error.message;
9
+ if (error && typeof error === "object" && "message" in error && typeof error.message === "string") return error.message;
10
+ return String(error);
11
+ }
12
+ async function createSignedUrlOrThrow({ bucket, key, expiresIn }) {
13
+ let data = null;
14
+ let error = null;
15
+ try {
16
+ const response = await bucket.createSignedUrl(key, expiresIn);
17
+ data = response.data;
18
+ error = response.error;
19
+ } catch (thrownError) {
20
+ error = thrownError;
21
+ }
22
+ if (!error && data?.signedUrl) return data.signedUrl;
23
+ throw new Error(`Failed to generate download URL for "${key}": ${getErrorMessage(error ?? /* @__PURE__ */ new Error("missing signed URL"))}`);
24
+ }
25
+ async function verifyObjectCanBeSignedForRuntime({ bucket, key }) {
26
+ await createSignedUrlOrThrow({
27
+ bucket,
28
+ key,
29
+ expiresIn: 3600
30
+ });
31
+ }
7
32
  const supabaseStorage = createUniversalStoragePlugin({
8
33
  name: "supabaseStorage",
9
34
  supportedProtocol: "supabase-storage",
10
35
  factory: (config) => {
11
- const bucket = createClient(config.supabaseUrl, config.supabaseAnonKey).storage.from(config.bucketName);
36
+ const bucket = createClient(config.supabaseUrl, resolveSupabaseServiceRoleKey(config)).storage.from(config.bucketName);
12
37
  const getStorageKey = createStorageKeyBuilder(config.basePath);
13
38
  return {
14
39
  node: {
@@ -31,8 +56,24 @@ const supabaseStorage = createUniversalStoragePlugin({
31
56
  headers: {}
32
57
  });
33
58
  if (upload.error) throw upload.error;
59
+ await verifyObjectCanBeSignedForRuntime({
60
+ bucket,
61
+ key: Key
62
+ });
34
63
  return { storageUri: `supabase-storage://${upload.data.fullPath}` };
35
64
  },
65
+ async exists(storageUri) {
66
+ const { key, bucket: bucketName } = parseStorageUri(storageUri, "supabase-storage");
67
+ if (bucketName !== config.bucketName) throw new Error(`Bucket name mismatch: expected "${config.bucketName}", but found "${bucketName}".`);
68
+ const { data, error } = await bucket.exists(key);
69
+ if (data === false) return false;
70
+ if (error) throw error;
71
+ await verifyObjectCanBeSignedForRuntime({
72
+ bucket,
73
+ key
74
+ });
75
+ return data;
76
+ },
36
77
  async downloadFile(storageUri, filePath) {
37
78
  const { key, bucket: bucketName } = parseStorageUri(storageUri, "supabase-storage");
38
79
  if (bucketName !== config.bucketName) throw new Error(`Bucket name mismatch: expected "${config.bucketName}", but found "${bucketName}".`);
@@ -61,10 +102,11 @@ const supabaseStorage = createUniversalStoragePlugin({
61
102
  let key = `${u.host}${u.pathname}`.replace(/^\//, "");
62
103
  if (!key) throw new Error("Invalid Supabase storage URI: missing key");
63
104
  if (key.startsWith(`${config.bucketName}/`)) key = key.substring(`${config.bucketName}/`.length);
64
- const { data, error } = await bucket.createSignedUrl(key, 3600);
65
- if (error) throw new Error(`Failed to generate download URL: ${error.message}`);
66
- if (!data?.signedUrl) throw new Error("Failed to generate download URL");
67
- return { fileUrl: data.signedUrl };
105
+ return { fileUrl: await createSignedUrlOrThrow({
106
+ bucket,
107
+ key,
108
+ expiresIn: 3600
109
+ }) };
68
110
  }
69
111
  }
70
112
  };
@@ -1,6 +1,13 @@
1
1
  import { DEFAULT_ROLLOUT_COHORT_COUNT, getAssetBaseStorageUri, getBundlePatches, getManifestFileHash, getManifestStorageUri, stripBundleArtifactMetadata } from "@hot-updater/core";
2
2
  import { calculatePagination, createDatabasePlugin, createDatabasePluginGetUpdateInfo, createRuntimeStoragePlugin } from "@hot-updater/plugin-core";
3
3
  import { createClient } from "@supabase/supabase-js";
4
+ //#region src/supabaseConfig.ts
5
+ const resolveSupabaseServiceRoleKey = (config) => {
6
+ const key = config.supabaseServiceRoleKey ?? config.supabaseAnonKey;
7
+ if (!key) throw new Error("Supabase service role key is required. Set supabaseServiceRoleKey.");
8
+ return key;
9
+ };
10
+ //#endregion
4
11
  //#region src/supabaseDatabase.ts
5
12
  const normalizeMetadata = (value) => {
6
13
  if (!value) return {};
@@ -99,7 +106,7 @@ const bundleToPatchRows = (bundle) => getBundlePatches(bundle).map((patch, index
99
106
  const supabaseDatabase = createDatabasePlugin({
100
107
  name: "supabaseDatabase",
101
108
  factory: (config) => {
102
- const supabase = createClient(config.supabaseUrl, config.supabaseAnonKey);
109
+ const supabase = createClient(config.supabaseUrl, resolveSupabaseServiceRoleKey(config));
103
110
  const fetchPatchMap = async (bundleIds) => {
104
111
  const patchMap = /* @__PURE__ */ new Map();
105
112
  if (bundleIds.length === 0) return patchMap;
@@ -228,11 +235,16 @@ const supabaseDatabase = createDatabasePlugin({
228
235
  const supabaseEdgeFunctionDatabase = (config, hooks) => {
229
236
  return supabaseDatabase({
230
237
  supabaseUrl: config.supabaseUrl,
231
- supabaseAnonKey: config.supabaseServiceRoleKey
238
+ supabaseServiceRoleKey: config.supabaseServiceRoleKey
232
239
  }, hooks);
233
240
  };
234
241
  //#endregion
235
242
  //#region src/supabaseEdgeFunctionStorage.ts
243
+ function getErrorMessage(error) {
244
+ if (error instanceof Error) return error.message;
245
+ if (error && typeof error === "object" && "message" in error && typeof error.message === "string") return error.message;
246
+ return String(error);
247
+ }
236
248
  const parseSupabaseStorageUri = (storageUri) => {
237
249
  const storageUrl = new URL(storageUri);
238
250
  if (storageUrl.protocol !== "supabase-storage:") throw new Error("Invalid Supabase storage URI protocol");
@@ -262,13 +274,22 @@ const supabaseEdgeFunctionStorage = createRuntimeStoragePlugin({
262
274
  },
263
275
  async getDownloadUrl(storageUri) {
264
276
  const { bucketName, key } = parseSupabaseStorageUri(storageUri);
265
- const { data, error } = await supabase.storage.from(bucketName).createSignedUrl(key, config.signedUrlExpiresIn ?? 3600);
266
- if (error) throw new Error(`Failed to generate download URL: ${error.message}`);
267
- if (!data?.signedUrl) throw new Error("Failed to generate download URL");
268
- return { fileUrl: data.signedUrl };
277
+ const bucket = supabase.storage.from(bucketName);
278
+ const expiresIn = config.signedUrlExpiresIn ?? 3600;
279
+ let data = null;
280
+ let error = null;
281
+ try {
282
+ const response = await bucket.createSignedUrl(key, expiresIn);
283
+ data = response.data;
284
+ error = response.error;
285
+ } catch (thrownError) {
286
+ error = thrownError;
287
+ }
288
+ if (!error && data?.signedUrl) return { fileUrl: data.signedUrl };
289
+ throw new Error(`Failed to generate download URL for "${bucketName}/${key}": ${getErrorMessage(error ?? /* @__PURE__ */ new Error("missing signed URL"))}`);
269
290
  }
270
291
  };
271
292
  }
272
293
  });
273
294
  //#endregion
274
- export { supabaseEdgeFunctionDatabase as n, supabaseDatabase as r, supabaseEdgeFunctionStorage as t };
295
+ export { resolveSupabaseServiceRoleKey as i, supabaseEdgeFunctionDatabase as n, supabaseDatabase as r, supabaseEdgeFunctionStorage as t };
@@ -1,3 +1,4 @@
1
+ import * as _$_hot_updater_plugin_core0 from "@hot-updater/plugin-core";
1
2
  import { DatabasePluginHooks } from "@hot-updater/plugin-core";
2
3
 
3
4
  //#region src/supabaseEdgeFunctionDatabase.d.ts
@@ -5,7 +6,7 @@ interface SupabaseEdgeFunctionDatabaseConfig {
5
6
  supabaseUrl: string;
6
7
  supabaseServiceRoleKey: string;
7
8
  }
8
- declare const supabaseEdgeFunctionDatabase: (config: SupabaseEdgeFunctionDatabaseConfig, hooks?: DatabasePluginHooks) => () => import("@hot-updater/plugin-core").DatabasePlugin<unknown>;
9
+ declare const supabaseEdgeFunctionDatabase: (config: SupabaseEdgeFunctionDatabaseConfig, hooks?: DatabasePluginHooks) => () => _$_hot_updater_plugin_core0.DatabasePlugin<unknown>;
9
10
  //#endregion
10
11
  //#region src/supabaseEdgeFunctionStorage.d.ts
11
12
  interface SupabaseEdgeFunctionStorageConfig {
@@ -13,6 +14,6 @@ interface SupabaseEdgeFunctionStorageConfig {
13
14
  supabaseServiceRoleKey: string;
14
15
  signedUrlExpiresIn?: number;
15
16
  }
16
- declare const supabaseEdgeFunctionStorage: (config: SupabaseEdgeFunctionStorageConfig, hooks?: import("@hot-updater/plugin-core").StoragePluginHooks) => () => import("@hot-updater/plugin-core").RuntimeStoragePlugin<unknown>;
17
+ declare const supabaseEdgeFunctionStorage: (config: SupabaseEdgeFunctionStorageConfig, hooks?: _$_hot_updater_plugin_core0.StoragePluginHooks) => () => _$_hot_updater_plugin_core0.RuntimeStoragePlugin<unknown>;
17
18
  //#endregion
18
19
  export { supabaseEdgeFunctionDatabase as i, supabaseEdgeFunctionStorage as n, SupabaseEdgeFunctionDatabaseConfig as r, SupabaseEdgeFunctionStorageConfig as t };
@@ -1,3 +1,4 @@
1
+ import * as _$_hot_updater_plugin_core0 from "@hot-updater/plugin-core";
1
2
  import { DatabasePluginHooks } from "@hot-updater/plugin-core";
2
3
 
3
4
  //#region src/supabaseEdgeFunctionDatabase.d.ts
@@ -5,7 +6,7 @@ interface SupabaseEdgeFunctionDatabaseConfig {
5
6
  supabaseUrl: string;
6
7
  supabaseServiceRoleKey: string;
7
8
  }
8
- declare const supabaseEdgeFunctionDatabase: (config: SupabaseEdgeFunctionDatabaseConfig, hooks?: DatabasePluginHooks) => () => import("@hot-updater/plugin-core").DatabasePlugin<unknown>;
9
+ declare const supabaseEdgeFunctionDatabase: (config: SupabaseEdgeFunctionDatabaseConfig, hooks?: DatabasePluginHooks) => () => _$_hot_updater_plugin_core0.DatabasePlugin<unknown>;
9
10
  //#endregion
10
11
  //#region src/supabaseEdgeFunctionStorage.d.ts
11
12
  interface SupabaseEdgeFunctionStorageConfig {
@@ -13,6 +14,6 @@ interface SupabaseEdgeFunctionStorageConfig {
13
14
  supabaseServiceRoleKey: string;
14
15
  signedUrlExpiresIn?: number;
15
16
  }
16
- declare const supabaseEdgeFunctionStorage: (config: SupabaseEdgeFunctionStorageConfig, hooks?: import("@hot-updater/plugin-core").StoragePluginHooks) => () => import("@hot-updater/plugin-core").RuntimeStoragePlugin<unknown>;
17
+ declare const supabaseEdgeFunctionStorage: (config: SupabaseEdgeFunctionStorageConfig, hooks?: _$_hot_updater_plugin_core0.StoragePluginHooks) => () => _$_hot_updater_plugin_core0.RuntimeStoragePlugin<unknown>;
17
18
  //#endregion
18
19
  export { supabaseEdgeFunctionDatabase as i, supabaseEdgeFunctionStorage as n, SupabaseEdgeFunctionDatabaseConfig as r, SupabaseEdgeFunctionStorageConfig as t };
@@ -2,6 +2,13 @@ require("./index.cjs");
2
2
  let _hot_updater_core = require("@hot-updater/core");
3
3
  let _hot_updater_plugin_core = require("@hot-updater/plugin-core");
4
4
  let _supabase_supabase_js = require("@supabase/supabase-js");
5
+ //#region src/supabaseConfig.ts
6
+ const resolveSupabaseServiceRoleKey = (config) => {
7
+ const key = config.supabaseServiceRoleKey ?? config.supabaseAnonKey;
8
+ if (!key) throw new Error("Supabase service role key is required. Set supabaseServiceRoleKey.");
9
+ return key;
10
+ };
11
+ //#endregion
5
12
  //#region src/supabaseDatabase.ts
6
13
  const normalizeMetadata = (value) => {
7
14
  if (!value) return {};
@@ -100,7 +107,7 @@ const bundleToPatchRows = (bundle) => (0, _hot_updater_core.getBundlePatches)(bu
100
107
  const supabaseDatabase = (0, _hot_updater_plugin_core.createDatabasePlugin)({
101
108
  name: "supabaseDatabase",
102
109
  factory: (config) => {
103
- const supabase = (0, _supabase_supabase_js.createClient)(config.supabaseUrl, config.supabaseAnonKey);
110
+ const supabase = (0, _supabase_supabase_js.createClient)(config.supabaseUrl, resolveSupabaseServiceRoleKey(config));
104
111
  const fetchPatchMap = async (bundleIds) => {
105
112
  const patchMap = /* @__PURE__ */ new Map();
106
113
  if (bundleIds.length === 0) return patchMap;
@@ -229,11 +236,16 @@ const supabaseDatabase = (0, _hot_updater_plugin_core.createDatabasePlugin)({
229
236
  const supabaseEdgeFunctionDatabase = (config, hooks) => {
230
237
  return supabaseDatabase({
231
238
  supabaseUrl: config.supabaseUrl,
232
- supabaseAnonKey: config.supabaseServiceRoleKey
239
+ supabaseServiceRoleKey: config.supabaseServiceRoleKey
233
240
  }, hooks);
234
241
  };
235
242
  //#endregion
236
243
  //#region src/supabaseEdgeFunctionStorage.ts
244
+ function getErrorMessage(error) {
245
+ if (error instanceof Error) return error.message;
246
+ if (error && typeof error === "object" && "message" in error && typeof error.message === "string") return error.message;
247
+ return String(error);
248
+ }
237
249
  const parseSupabaseStorageUri = (storageUri) => {
238
250
  const storageUrl = new URL(storageUri);
239
251
  if (storageUrl.protocol !== "supabase-storage:") throw new Error("Invalid Supabase storage URI protocol");
@@ -263,15 +275,30 @@ const supabaseEdgeFunctionStorage = (0, _hot_updater_plugin_core.createRuntimeSt
263
275
  },
264
276
  async getDownloadUrl(storageUri) {
265
277
  const { bucketName, key } = parseSupabaseStorageUri(storageUri);
266
- const { data, error } = await supabase.storage.from(bucketName).createSignedUrl(key, config.signedUrlExpiresIn ?? 3600);
267
- if (error) throw new Error(`Failed to generate download URL: ${error.message}`);
268
- if (!data?.signedUrl) throw new Error("Failed to generate download URL");
269
- return { fileUrl: data.signedUrl };
278
+ const bucket = supabase.storage.from(bucketName);
279
+ const expiresIn = config.signedUrlExpiresIn ?? 3600;
280
+ let data = null;
281
+ let error = null;
282
+ try {
283
+ const response = await bucket.createSignedUrl(key, expiresIn);
284
+ data = response.data;
285
+ error = response.error;
286
+ } catch (thrownError) {
287
+ error = thrownError;
288
+ }
289
+ if (!error && data?.signedUrl) return { fileUrl: data.signedUrl };
290
+ throw new Error(`Failed to generate download URL for "${bucketName}/${key}": ${getErrorMessage(error ?? /* @__PURE__ */ new Error("missing signed URL"))}`);
270
291
  }
271
292
  };
272
293
  }
273
294
  });
274
295
  //#endregion
296
+ Object.defineProperty(exports, "resolveSupabaseServiceRoleKey", {
297
+ enumerable: true,
298
+ get: function() {
299
+ return resolveSupabaseServiceRoleKey;
300
+ }
301
+ });
275
302
  Object.defineProperty(exports, "supabaseDatabase", {
276
303
  enumerable: true,
277
304
  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.31.4",
4
+ "version": "0.32.0",
5
5
  "description": "React Native OTA solution for self-hosted",
6
6
  "main": "dist/index.cjs",
7
7
  "module": "dist/index.mjs",
@@ -47,10 +47,10 @@
47
47
  "@supabase/supabase-js": "2.76.1",
48
48
  "hono": "4.12.9",
49
49
  "uuidv7": "^1.0.2",
50
- "@hot-updater/core": "0.31.4",
51
- "@hot-updater/plugin-core": "0.31.4",
52
- "@hot-updater/server": "0.31.4",
53
- "@hot-updater/cli-tools": "0.31.4"
50
+ "@hot-updater/core": "0.32.0",
51
+ "@hot-updater/plugin-core": "0.32.0",
52
+ "@hot-updater/cli-tools": "0.32.0",
53
+ "@hot-updater/server": "0.32.0"
54
54
  },
55
55
  "devDependencies": {
56
56
  "@electric-sql/pglite": "0.2.17",
@@ -60,10 +60,10 @@
60
60
  "execa": "9.5.2",
61
61
  "@types/node": "^20",
62
62
  "mime": "^4.0.4",
63
- "@hot-updater/js": "0.31.4",
64
- "@hot-updater/mock": "0.31.4",
65
- "@hot-updater/test-utils": "0.31.4",
66
- "@hot-updater/postgres": "0.31.4"
63
+ "@hot-updater/js": "0.32.0",
64
+ "@hot-updater/mock": "0.32.0",
65
+ "@hot-updater/test-utils": "0.32.0",
66
+ "@hot-updater/postgres": "0.32.0"
67
67
  },
68
68
  "scripts": {
69
69
  "build": "tsdown",
@@ -32,7 +32,6 @@ const hotUpdater = createHotUpdater({
32
32
  basePath: hotUpdaterBasePath,
33
33
  routes: {
34
34
  updateCheck: true,
35
- version: true,
36
35
  bundles: false,
37
36
  },
38
37
  });
@@ -0,0 +1,48 @@
1
+ -- HotUpdater.supabase_rls
2
+
3
+ ALTER TABLE public.bundles ENABLE ROW LEVEL SECURITY;
4
+ ALTER TABLE public.bundle_patches ENABLE ROW LEVEL SECURITY;
5
+
6
+ ALTER FUNCTION public.get_target_app_version_list(public.platforms, uuid)
7
+ SET search_path = public, pg_catalog;
8
+ ALTER FUNCTION public.get_channels()
9
+ SET search_path = public, pg_catalog;
10
+ ALTER FUNCTION public.positive_mod(integer, integer)
11
+ SET search_path = public, pg_catalog;
12
+ ALTER FUNCTION public.hash_rollout_value(text)
13
+ SET search_path = public, pg_catalog;
14
+ ALTER FUNCTION public.normalize_cohort_value(text)
15
+ SET search_path = public, pg_catalog;
16
+ ALTER FUNCTION public.gcd_int(integer, integer)
17
+ SET search_path = public, pg_catalog;
18
+ ALTER FUNCTION public.get_rollout_multiplier(uuid)
19
+ SET search_path = public, pg_catalog;
20
+ ALTER FUNCTION public.get_rollout_offset(uuid)
21
+ SET search_path = public, pg_catalog;
22
+ ALTER FUNCTION public.get_modular_inverse(integer, integer)
23
+ SET search_path = public, pg_catalog;
24
+ ALTER FUNCTION public.is_numeric_cohort(text)
25
+ SET search_path = public, pg_catalog;
26
+ ALTER FUNCTION public.get_numeric_cohort_rollout_position(uuid, text)
27
+ SET search_path = public, pg_catalog;
28
+ ALTER FUNCTION public.is_cohort_eligible(uuid, text, integer, text[])
29
+ SET search_path = public, pg_catalog;
30
+ ALTER FUNCTION public.get_update_info_by_fingerprint_hash(
31
+ public.platforms,
32
+ uuid,
33
+ uuid,
34
+ text,
35
+ text,
36
+ text
37
+ )
38
+ SET search_path = public, pg_catalog;
39
+ ALTER FUNCTION public.get_update_info_by_app_version(
40
+ public.platforms,
41
+ text,
42
+ uuid,
43
+ uuid,
44
+ text,
45
+ text[],
46
+ text
47
+ )
48
+ SET search_path = public, pg_catalog;
@@ -0,0 +1,67 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+
4
+ import { describe, expect, it } from "vitest";
5
+
6
+ const rlsMigrationPath = path.resolve(
7
+ "plugins/supabase/supabase/migrations/20260520014100_hot-updater_rls.sql",
8
+ );
9
+ const migrationsDir = path.dirname(rlsMigrationPath);
10
+
11
+ describe("Supabase RLS migration", () => {
12
+ it("runs after Supabase function redefinition migrations", async () => {
13
+ const migrations = (await fs.readdir(migrationsDir))
14
+ .filter((file) => file.endsWith(".sql"))
15
+ .sort();
16
+
17
+ expect(migrations.at(-1)).toBe(path.basename(rlsMigrationPath));
18
+ });
19
+
20
+ it("enables RLS on Hot Updater tables", async () => {
21
+ const sql = await fs.readFile(rlsMigrationPath, "utf8");
22
+
23
+ expect(sql).toContain(
24
+ "ALTER TABLE public.bundles ENABLE ROW LEVEL SECURITY;",
25
+ );
26
+ expect(sql).toContain(
27
+ "ALTER TABLE public.bundle_patches ENABLE ROW LEVEL SECURITY;",
28
+ );
29
+ expect(sql).not.toContain("REVOKE ALL ON TABLE");
30
+ expect(sql).not.toContain("GRANT SELECT, INSERT, UPDATE, DELETE");
31
+ });
32
+
33
+ it("pins search_path for public Hot Updater functions", async () => {
34
+ const sql = await fs.readFile(rlsMigrationPath, "utf8");
35
+ const hotUpdaterFunctions = [
36
+ "get_target_app_version_list",
37
+ "get_channels",
38
+ "positive_mod",
39
+ "hash_rollout_value",
40
+ "normalize_cohort_value",
41
+ "gcd_int",
42
+ "get_rollout_multiplier",
43
+ "get_rollout_offset",
44
+ "get_modular_inverse",
45
+ "is_numeric_cohort",
46
+ "get_numeric_cohort_rollout_position",
47
+ "is_cohort_eligible",
48
+ "get_update_info_by_fingerprint_hash",
49
+ "get_update_info_by_app_version",
50
+ ];
51
+
52
+ for (const functionName of hotUpdaterFunctions) {
53
+ expect(sql).toContain(`ALTER FUNCTION public.${functionName}`);
54
+ }
55
+
56
+ expect(sql.match(/SET search_path = public, pg_catalog;/g)).toHaveLength(
57
+ hotUpdaterFunctions.length,
58
+ );
59
+ });
60
+
61
+ it("does not change function execution grants", async () => {
62
+ const sql = await fs.readFile(rlsMigrationPath, "utf8");
63
+
64
+ expect(sql).not.toContain("REVOKE EXECUTE");
65
+ expect(sql).not.toContain("GRANT EXECUTE");
66
+ });
67
+ });