@hot-updater/supabase 1.2.0-rc.7 → 1.2.0-rc.9

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_supabaseStorage = require("./supabaseStorage-BV6GuUz_.cjs");
2
+ const require_supabaseStorage = require("./supabaseStorage-DUa49vGh.cjs");
3
3
  exports.supabaseDatabase = require_supabaseStorage.supabaseDatabase;
4
4
  exports.supabaseStorage = require_supabaseStorage.supabaseStorage;
package/dist/edge.d.cts CHANGED
@@ -1,2 +1,2 @@
1
- import { i as supabaseDatabase, n as supabaseStorage, r as SupabaseDatabaseConfig, t as SupabaseStorageConfig } from "./supabaseStorage-B131_auF.cjs";
1
+ import { i as supabaseDatabase, n as supabaseStorage, r as SupabaseDatabaseConfig, t as SupabaseStorageConfig } from "./supabaseStorage-C9W-69OP.cjs";
2
2
  export { type SupabaseDatabaseConfig, type SupabaseStorageConfig, supabaseDatabase, supabaseStorage };
package/dist/edge.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { i as supabaseDatabase, n as supabaseStorage, r as SupabaseDatabaseConfig, t as SupabaseStorageConfig } from "./supabaseStorage-B131_auF.mjs";
1
+ import { i as supabaseDatabase, n as supabaseStorage, r as SupabaseDatabaseConfig, t as SupabaseStorageConfig } from "./supabaseStorage-C9W-69OP.mjs";
2
2
  export { type SupabaseDatabaseConfig, type SupabaseStorageConfig, supabaseDatabase, supabaseStorage };
package/dist/edge.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { n as supabaseDatabase, t as supabaseStorage } from "./supabaseStorage-8VAuNYHy.mjs";
1
+ import { n as supabaseDatabase, t as supabaseStorage } from "./supabaseStorage-B6IM8UX2.mjs";
2
2
  export { supabaseDatabase, supabaseStorage };
package/dist/index.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_supabaseStorage = require("./supabaseStorage-BV6GuUz_.cjs");
2
+ const require_supabaseStorage = require("./supabaseStorage-DUa49vGh.cjs");
3
3
  let _hot_updater_plugin_core = require("@hot-updater/plugin-core");
4
4
  //#region src/supabaseSchema.ts
5
5
  /** Every table the apply RPC may name: models, index tables, settings, and the write guard. */
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { n as supabaseStorage, r as SupabaseDatabaseConfig, t as SupabaseStorageConfig } from "./supabaseStorage-B131_auF.cjs";
1
+ import { n as supabaseStorage, r as SupabaseDatabaseConfig, t as SupabaseStorageConfig } from "./supabaseStorage-C9W-69OP.cjs";
2
2
  import { ToolingDatabase } from "@hot-updater/plugin-core";
3
3
  //#region src/supabaseMigration.d.ts
4
4
  /**
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { n as supabaseStorage, r as SupabaseDatabaseConfig, t as SupabaseStorageConfig } from "./supabaseStorage-B131_auF.mjs";
1
+ import { n as supabaseStorage, r as SupabaseDatabaseConfig, t as SupabaseStorageConfig } from "./supabaseStorage-C9W-69OP.mjs";
2
2
  import { ToolingDatabase } from "@hot-updater/plugin-core";
3
3
  //#region src/supabaseMigration.d.ts
4
4
  /**
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { i as SUPABASE_TABLE_PREFIX, n as supabaseDatabase$1, r as SUPABASE_APPLY_FUNCTION, t as supabaseStorage } from "./supabaseStorage-8VAuNYHy.mjs";
1
+ import { i as SUPABASE_TABLE_PREFIX, n as supabaseDatabase$1, r as SUPABASE_APPLY_FUNCTION, t as supabaseStorage } from "./supabaseStorage-B6IM8UX2.mjs";
2
2
  import { SETTINGS_TABLE, WRITE_GUARD_TABLE, coreSettings, coreTarget, createTableStatements, generateEngineSql, isMultiIndex } from "@hot-updater/plugin-core";
3
3
  //#region src/supabaseSchema.ts
4
4
  /** Every table the apply RPC may name: models, index tables, settings, and the write guard. */
@@ -6421,14 +6421,15 @@ const supabaseDatabase = (config) => {
6421
6421
  //#region iac/configTemplate.ts
6422
6422
  /**
6423
6423
  * The config init writes. With the `resources` init set up, it names them,
6424
- * and reads the service role key with `secretEnv`, from the environment and
6424
+ * and reads the service role key with `secret()`, from the environment and
6425
6425
  * `.env.hotupdater`, then the config's `secrets` adapter. Without them, as
6426
- * the agent scaffold writes it, it reads every value from the environment.
6426
+ * the agent scaffold writes it before the agent creates the project, it
6427
+ * reads the URL and bucket from the environment, where the agent records
6428
+ * them, and the key with `secret()` alike.
6427
6429
  */
6428
6430
  const getConfigScaffold = (build, resources) => {
6429
6431
  const supabaseUrl = resources === void 0 ? "process.env.HOT_UPDATER_SUPABASE_URL!" : JSON.stringify(resources.supabaseUrl);
6430
- const serviceRoleKey = resources === void 0 ? "process.env.HOT_UPDATER_SUPABASE_SERVICE_ROLE_KEY!" : `async () =>
6431
- (await deploySecrets()).HOT_UPDATER_SUPABASE_SERVICE_ROLE_KEY`;
6432
+ const serviceRoleKey = "secret(\"HOT_UPDATER_SUPABASE_SERVICE_ROLE_KEY\")";
6432
6433
  const storageConfig = {
6433
6434
  imports: [{
6434
6435
  pkg: "@hot-updater/supabase",
@@ -6450,7 +6451,6 @@ const getConfigScaffold = (build, resources) => {
6450
6451
  supabaseServiceRoleKey: ${serviceRoleKey},
6451
6452
  })`
6452
6453
  };
6453
- const helperStatements = resources === void 0 ? [] : [(0, _hot_updater_cli_tools.secretEnvHelper)("deploySecrets", ["HOT_UPDATER_SUPABASE_SERVICE_ROLE_KEY"])];
6454
6454
  const builder = new _hot_updater_cli_tools.ConfigBuilder().setBuildType(build).setStorage(storageConfig).setDatabase(databaseConfig).setPlugins({
6455
6455
  imports: [{
6456
6456
  pkg: "hot-updater/plugins",
@@ -6462,11 +6462,11 @@ const getConfigScaffold = (build, resources) => {
6462
6462
  }],
6463
6463
  configString: "[apiKeys(), insights(), remoteConfig()]"
6464
6464
  });
6465
- if (resources !== void 0) builder.addImport({
6465
+ builder.addImport({
6466
6466
  pkg: "hot-updater",
6467
- named: ["secretEnv"]
6468
- }).setIntermediateCode(helperStatements.map(({ code }) => code).join("\n\n"));
6469
- return (0, _hot_updater_cli_tools.createHotUpdaterConfigScaffoldFromBuilder)(builder, { helperStatements });
6467
+ named: ["secret"]
6468
+ });
6469
+ return (0, _hot_updater_cli_tools.createHotUpdaterConfigScaffoldFromBuilder)(builder);
6470
6470
  };
6471
6471
  //#endregion
6472
6472
  //#region iac/init/index.ts
@@ -1,7 +1,7 @@
1
1
  import { createRequire } from "node:module";
2
2
  import fs from "fs/promises";
3
3
  import path from "path";
4
- import { ConfigBuilder, InitError, MissingInitInputsError, assembleServer, assertInfrastructureGenerationAtUrl, assertInitInputs, assertInitProviderInputs, confirmInitInputPersistence, copyDirToTmp, createHotUpdaterConfigScaffoldFromBuilder, getHotUpdaterEnvValue, getHotUpdaterInitInputEnv, getInitProviderEnvVars, getInitProviderTextPromptValues, link, makeEnv, moduleSpecifiersOf, p, printAppSetup, provisionClientCredential, readHotUpdaterInitEnv, resolveInitProviderInput, resolvePackageVersion, secretEnvHelper, transformEnv, transformTemplate, writeHotUpdaterFiles } from "@hot-updater/cli-tools";
4
+ import { ConfigBuilder, InitError, MissingInitInputsError, assembleServer, assertInfrastructureGenerationAtUrl, assertInitInputs, assertInitProviderInputs, confirmInitInputPersistence, copyDirToTmp, createHotUpdaterConfigScaffoldFromBuilder, getHotUpdaterEnvValue, getHotUpdaterInitInputEnv, getInitProviderEnvVars, getInitProviderTextPromptValues, link, makeEnv, moduleSpecifiersOf, p, printAppSetup, provisionClientCredential, readHotUpdaterInitEnv, resolveInitProviderInput, resolvePackageVersion, transformEnv, transformTemplate, writeHotUpdaterFiles } from "@hot-updater/cli-tools";
5
5
  import { apiKeys, insights, remoteConfig } from "@hot-updater/server/plugins";
6
6
  import { fileURLToPath } from "node:url";
7
7
  import { ChildProcess, execFile, spawn, spawnSync } from "node:child_process";
@@ -6416,14 +6416,15 @@ const supabaseDatabase = (config) => {
6416
6416
  //#region iac/configTemplate.ts
6417
6417
  /**
6418
6418
  * The config init writes. With the `resources` init set up, it names them,
6419
- * and reads the service role key with `secretEnv`, from the environment and
6419
+ * and reads the service role key with `secret()`, from the environment and
6420
6420
  * `.env.hotupdater`, then the config's `secrets` adapter. Without them, as
6421
- * the agent scaffold writes it, it reads every value from the environment.
6421
+ * the agent scaffold writes it before the agent creates the project, it
6422
+ * reads the URL and bucket from the environment, where the agent records
6423
+ * them, and the key with `secret()` alike.
6422
6424
  */
6423
6425
  const getConfigScaffold = (build, resources) => {
6424
6426
  const supabaseUrl = resources === void 0 ? "process.env.HOT_UPDATER_SUPABASE_URL!" : JSON.stringify(resources.supabaseUrl);
6425
- const serviceRoleKey = resources === void 0 ? "process.env.HOT_UPDATER_SUPABASE_SERVICE_ROLE_KEY!" : `async () =>
6426
- (await deploySecrets()).HOT_UPDATER_SUPABASE_SERVICE_ROLE_KEY`;
6427
+ const serviceRoleKey = "secret(\"HOT_UPDATER_SUPABASE_SERVICE_ROLE_KEY\")";
6427
6428
  const storageConfig = {
6428
6429
  imports: [{
6429
6430
  pkg: "@hot-updater/supabase",
@@ -6445,7 +6446,6 @@ const getConfigScaffold = (build, resources) => {
6445
6446
  supabaseServiceRoleKey: ${serviceRoleKey},
6446
6447
  })`
6447
6448
  };
6448
- const helperStatements = resources === void 0 ? [] : [secretEnvHelper("deploySecrets", ["HOT_UPDATER_SUPABASE_SERVICE_ROLE_KEY"])];
6449
6449
  const builder = new ConfigBuilder().setBuildType(build).setStorage(storageConfig).setDatabase(databaseConfig).setPlugins({
6450
6450
  imports: [{
6451
6451
  pkg: "hot-updater/plugins",
@@ -6457,11 +6457,11 @@ const getConfigScaffold = (build, resources) => {
6457
6457
  }],
6458
6458
  configString: "[apiKeys(), insights(), remoteConfig()]"
6459
6459
  });
6460
- if (resources !== void 0) builder.addImport({
6460
+ builder.addImport({
6461
6461
  pkg: "hot-updater",
6462
- named: ["secretEnv"]
6463
- }).setIntermediateCode(helperStatements.map(({ code }) => code).join("\n\n"));
6464
- return createHotUpdaterConfigScaffoldFromBuilder(builder, { helperStatements });
6462
+ named: ["secret"]
6463
+ });
6464
+ return createHotUpdaterConfigScaffoldFromBuilder(builder);
6465
6465
  };
6466
6466
  //#endregion
6467
6467
  //#region iac/init/index.ts
@@ -166,7 +166,15 @@ const isMissingObjectError = async (error) => {
166
166
  };
167
167
  /** A character Storage rejects in an object name, `?`, or `!`. */
168
168
  const ESCAPED_CHARACTER = /[^A-Za-z0-9_/.*'() &$=@;:+,-]/gu;
169
+ const ESCAPE_SEQUENCE = /!([0-9A-F]{2})/gu;
169
170
  const utf8 = new TextEncoder();
171
+ const utf8Decoder = new TextDecoder("utf-8", { fatal: true });
172
+ /** Objects Storage lists a page at a time. */
173
+ const LIST_PAGE_SIZE = 1e3;
174
+ /** Names Storage removes in one request. */
175
+ const DELETE_BATCH_SIZE = 1e3;
176
+ /** The object the Supabase dashboard keeps in a folder it creates. */
177
+ const EMPTY_FOLDER_PLACEHOLDER = ".emptyFolderPlaceholder";
170
178
  /**
171
179
  * The object name Storage keeps a key under. Storage accepts names of ASCII
172
180
  * letters, digits and `_/!.*'() &$=@;:+,?-` only, and supabase-js puts a name
@@ -177,6 +185,34 @@ const utf8 = new TextEncoder();
177
185
  * Deploy's keys, such as `<bundleId>/manifest.json`, are stored as they are.
178
186
  */
179
187
  const toObjectName = (key) => key.replace(ESCAPED_CHARACTER, (character) => Array.from(utf8.encode(character), (byte) => `!${byte.toString(16).toUpperCase().padStart(2, "0")}`).join(""));
188
+ /** The key `toObjectName` stored under `name`, or null for a name it never makes. */
189
+ const fromObjectName = (name) => {
190
+ const bytes = [];
191
+ const parts = [];
192
+ const flush = () => {
193
+ if (bytes.length === 0) return true;
194
+ try {
195
+ parts.push(utf8Decoder.decode(new Uint8Array(bytes)));
196
+ } catch {
197
+ return false;
198
+ }
199
+ bytes.length = 0;
200
+ return true;
201
+ };
202
+ let index = 0;
203
+ for (const match of name.matchAll(ESCAPE_SEQUENCE)) {
204
+ if (match.index !== index) {
205
+ if (!flush()) return null;
206
+ parts.push(name.slice(index, match.index));
207
+ }
208
+ bytes.push(Number.parseInt(match[1], 16));
209
+ index = match.index + match[0].length;
210
+ }
211
+ if (!flush()) return null;
212
+ parts.push(name.slice(index));
213
+ const key = parts.join("");
214
+ return toObjectName(key) === name ? key : null;
215
+ };
180
216
  const supabaseStorage = (config) => {
181
217
  let client;
182
218
  const bucket = async () => (await (client ??= resolveSupabaseServiceRoleKey(config).then((key) => createClient(config.supabaseUrl, key), (error) => {
@@ -184,6 +220,7 @@ const supabaseStorage = (config) => {
184
220
  throw error;
185
221
  }))).storage.from(config.bucketName);
186
222
  const getStorageKey = createStorageKeyBuilder(config.basePath);
223
+ const basePrefix = getStorageKey() ? `${getStorageKey()}/` : "";
187
224
  const resolveSignedUrl = createSupabaseSignedUrlBatcher({
188
225
  createSignedUrls: async (_bucketName, keys, expiresIn) => (await bucket()).createSignedUrls(keys, expiresIn),
189
226
  expiresIn: config.signedUrlExpiresIn ?? 3600,
@@ -240,6 +277,51 @@ const supabaseStorage = (config) => {
240
277
  const { error } = await (await bucket()).remove([toObjectName(key)]);
241
278
  if (error && !isNotFoundError(error)) throw new Error(`Failed to delete storage object: ${error.message}`);
242
279
  return { deleted: true };
280
+ },
281
+ async listObjects(prefix) {
282
+ const objects = [];
283
+ const folder = toObjectName(getStorageKey(prefix ?? ""));
284
+ let cursor;
285
+ do {
286
+ const { data, error } = await (await bucket()).listV2({
287
+ prefix: folder ? `${folder}/` : "",
288
+ limit: LIST_PAGE_SIZE,
289
+ ...cursor === void 0 ? {} : { cursor }
290
+ });
291
+ if (error) throw new Error(`Failed to list storage objects: ${error.message}`);
292
+ for (const entry of data.objects) {
293
+ const name = entry.key ?? entry.name;
294
+ if (name.split("/").at(-1) === EMPTY_FOLDER_PLACEHOLDER) continue;
295
+ const key = fromObjectName(name);
296
+ if (key === null || !key.startsWith(basePrefix)) continue;
297
+ let storageUri;
298
+ try {
299
+ storageUri = createStorageUri({
300
+ protocol: "supabase-storage",
301
+ bucket: config.bucketName,
302
+ key
303
+ });
304
+ } catch {
305
+ continue;
306
+ }
307
+ const updatedAt = entry.updated_at ?? entry.created_at;
308
+ objects.push({
309
+ key: key.slice(basePrefix.length),
310
+ ...updatedAt ? { lastModifiedAt: new Date(updatedAt) } : {},
311
+ size: Number(entry.metadata?.size ?? 0),
312
+ storageUri
313
+ });
314
+ }
315
+ cursor = data.hasNext ? data.nextCursor : void 0;
316
+ } while (cursor !== void 0);
317
+ return objects;
318
+ },
319
+ async deleteObjects(relativeKeys) {
320
+ const names = relativeKeys.map((key) => toObjectName(getStorageKey(key)));
321
+ for (let offset = 0; offset < names.length; offset += DELETE_BATCH_SIZE) {
322
+ const { error } = await (await bucket()).remove(names.slice(offset, offset + DELETE_BATCH_SIZE));
323
+ if (error && !isNotFoundError(error)) throw new Error(`Failed to delete storage objects: ${error.message}`);
324
+ }
243
325
  }
244
326
  });
245
327
  };
@@ -1,13 +1,13 @@
1
- import { EngineDatabase, StorageAdapterWith } from "@hot-updater/plugin-core";
1
+ import { EngineDatabase, Lazy, StorageAdapterWith } from "@hot-updater/plugin-core";
2
2
  //#region src/supabaseConfig.d.ts
3
3
  type SupabaseServiceRoleConfig = {
4
4
  supabaseUrl: string;
5
5
  /**
6
6
  * The project's service role key, or a function that returns it, such as
7
- * one that reads it with `secretEnv`: called on the first request, so a
8
- * config loads without the key.
7
+ * `secret("<name>")` in `hot-updater.config.ts`: called on the first
8
+ * request, so a config loads without the key.
9
9
  */
10
- supabaseServiceRoleKey: string | (() => string | Promise<string>);
10
+ supabaseServiceRoleKey: Lazy<string>;
11
11
  };
12
12
  //#endregion
13
13
  //#region src/supabaseDatabase.d.ts
@@ -28,6 +28,6 @@ type SupabaseStorageConfig = SupabaseServiceRoleConfig & {
28
28
  /** Signed download URL lifetime in seconds. @default 3600 */
29
29
  signedUrlExpiresIn?: number;
30
30
  };
31
- declare const supabaseStorage: (config: SupabaseStorageConfig) => StorageAdapterWith<"put" | "get" | "getDownloadUrl" | "exists" | "delete">;
31
+ declare const supabaseStorage: (config: SupabaseStorageConfig) => StorageAdapterWith<"put" | "get" | "getDownloadUrl" | "exists" | "delete" | "listObjects" | "deleteObjects">;
32
32
  //#endregion
33
33
  export { supabaseDatabase as i, supabaseStorage as n, SupabaseDatabaseConfig as r, SupabaseStorageConfig as t };
@@ -1,13 +1,13 @@
1
- import { EngineDatabase, StorageAdapterWith } from "@hot-updater/plugin-core";
1
+ import { EngineDatabase, Lazy, StorageAdapterWith } from "@hot-updater/plugin-core";
2
2
  //#region src/supabaseConfig.d.ts
3
3
  type SupabaseServiceRoleConfig = {
4
4
  supabaseUrl: string;
5
5
  /**
6
6
  * The project's service role key, or a function that returns it, such as
7
- * one that reads it with `secretEnv`: called on the first request, so a
8
- * config loads without the key.
7
+ * `secret("<name>")` in `hot-updater.config.ts`: called on the first
8
+ * request, so a config loads without the key.
9
9
  */
10
- supabaseServiceRoleKey: string | (() => string | Promise<string>);
10
+ supabaseServiceRoleKey: Lazy<string>;
11
11
  };
12
12
  //#endregion
13
13
  //#region src/supabaseDatabase.d.ts
@@ -28,6 +28,6 @@ type SupabaseStorageConfig = SupabaseServiceRoleConfig & {
28
28
  /** Signed download URL lifetime in seconds. @default 3600 */
29
29
  signedUrlExpiresIn?: number;
30
30
  };
31
- declare const supabaseStorage: (config: SupabaseStorageConfig) => StorageAdapterWith<"put" | "get" | "getDownloadUrl" | "exists" | "delete">;
31
+ declare const supabaseStorage: (config: SupabaseStorageConfig) => StorageAdapterWith<"put" | "get" | "getDownloadUrl" | "exists" | "delete" | "listObjects" | "deleteObjects">;
32
32
  //#endregion
33
33
  export { supabaseDatabase as i, supabaseStorage as n, SupabaseDatabaseConfig as r, SupabaseStorageConfig as t };
@@ -166,7 +166,15 @@ const isMissingObjectError = async (error) => {
166
166
  };
167
167
  /** A character Storage rejects in an object name, `?`, or `!`. */
168
168
  const ESCAPED_CHARACTER = /[^A-Za-z0-9_/.*'() &$=@;:+,-]/gu;
169
+ const ESCAPE_SEQUENCE = /!([0-9A-F]{2})/gu;
169
170
  const utf8 = new TextEncoder();
171
+ const utf8Decoder = new TextDecoder("utf-8", { fatal: true });
172
+ /** Objects Storage lists a page at a time. */
173
+ const LIST_PAGE_SIZE = 1e3;
174
+ /** Names Storage removes in one request. */
175
+ const DELETE_BATCH_SIZE = 1e3;
176
+ /** The object the Supabase dashboard keeps in a folder it creates. */
177
+ const EMPTY_FOLDER_PLACEHOLDER = ".emptyFolderPlaceholder";
170
178
  /**
171
179
  * The object name Storage keeps a key under. Storage accepts names of ASCII
172
180
  * letters, digits and `_/!.*'() &$=@;:+,?-` only, and supabase-js puts a name
@@ -177,6 +185,34 @@ const utf8 = new TextEncoder();
177
185
  * Deploy's keys, such as `<bundleId>/manifest.json`, are stored as they are.
178
186
  */
179
187
  const toObjectName = (key) => key.replace(ESCAPED_CHARACTER, (character) => Array.from(utf8.encode(character), (byte) => `!${byte.toString(16).toUpperCase().padStart(2, "0")}`).join(""));
188
+ /** The key `toObjectName` stored under `name`, or null for a name it never makes. */
189
+ const fromObjectName = (name) => {
190
+ const bytes = [];
191
+ const parts = [];
192
+ const flush = () => {
193
+ if (bytes.length === 0) return true;
194
+ try {
195
+ parts.push(utf8Decoder.decode(new Uint8Array(bytes)));
196
+ } catch {
197
+ return false;
198
+ }
199
+ bytes.length = 0;
200
+ return true;
201
+ };
202
+ let index = 0;
203
+ for (const match of name.matchAll(ESCAPE_SEQUENCE)) {
204
+ if (match.index !== index) {
205
+ if (!flush()) return null;
206
+ parts.push(name.slice(index, match.index));
207
+ }
208
+ bytes.push(Number.parseInt(match[1], 16));
209
+ index = match.index + match[0].length;
210
+ }
211
+ if (!flush()) return null;
212
+ parts.push(name.slice(index));
213
+ const key = parts.join("");
214
+ return toObjectName(key) === name ? key : null;
215
+ };
180
216
  const supabaseStorage = (config) => {
181
217
  let client;
182
218
  const bucket = async () => (await (client ??= resolveSupabaseServiceRoleKey(config).then((key) => (0, _supabase_supabase_js.createClient)(config.supabaseUrl, key), (error) => {
@@ -184,6 +220,7 @@ const supabaseStorage = (config) => {
184
220
  throw error;
185
221
  }))).storage.from(config.bucketName);
186
222
  const getStorageKey = (0, _hot_updater_plugin_core.createStorageKeyBuilder)(config.basePath);
223
+ const basePrefix = getStorageKey() ? `${getStorageKey()}/` : "";
187
224
  const resolveSignedUrl = createSupabaseSignedUrlBatcher({
188
225
  createSignedUrls: async (_bucketName, keys, expiresIn) => (await bucket()).createSignedUrls(keys, expiresIn),
189
226
  expiresIn: config.signedUrlExpiresIn ?? 3600,
@@ -240,6 +277,51 @@ const supabaseStorage = (config) => {
240
277
  const { error } = await (await bucket()).remove([toObjectName(key)]);
241
278
  if (error && !isNotFoundError(error)) throw new Error(`Failed to delete storage object: ${error.message}`);
242
279
  return { deleted: true };
280
+ },
281
+ async listObjects(prefix) {
282
+ const objects = [];
283
+ const folder = toObjectName(getStorageKey(prefix ?? ""));
284
+ let cursor;
285
+ do {
286
+ const { data, error } = await (await bucket()).listV2({
287
+ prefix: folder ? `${folder}/` : "",
288
+ limit: LIST_PAGE_SIZE,
289
+ ...cursor === void 0 ? {} : { cursor }
290
+ });
291
+ if (error) throw new Error(`Failed to list storage objects: ${error.message}`);
292
+ for (const entry of data.objects) {
293
+ const name = entry.key ?? entry.name;
294
+ if (name.split("/").at(-1) === EMPTY_FOLDER_PLACEHOLDER) continue;
295
+ const key = fromObjectName(name);
296
+ if (key === null || !key.startsWith(basePrefix)) continue;
297
+ let storageUri;
298
+ try {
299
+ storageUri = (0, _hot_updater_plugin_core.createStorageUri)({
300
+ protocol: "supabase-storage",
301
+ bucket: config.bucketName,
302
+ key
303
+ });
304
+ } catch {
305
+ continue;
306
+ }
307
+ const updatedAt = entry.updated_at ?? entry.created_at;
308
+ objects.push({
309
+ key: key.slice(basePrefix.length),
310
+ ...updatedAt ? { lastModifiedAt: new Date(updatedAt) } : {},
311
+ size: Number(entry.metadata?.size ?? 0),
312
+ storageUri
313
+ });
314
+ }
315
+ cursor = data.hasNext ? data.nextCursor : void 0;
316
+ } while (cursor !== void 0);
317
+ return objects;
318
+ },
319
+ async deleteObjects(relativeKeys) {
320
+ const names = relativeKeys.map((key) => toObjectName(getStorageKey(key)));
321
+ for (let offset = 0; offset < names.length; offset += DELETE_BATCH_SIZE) {
322
+ const { error } = await (await bucket()).remove(names.slice(offset, offset + DELETE_BATCH_SIZE));
323
+ if (error && !isNotFoundError(error)) throw new Error(`Failed to delete storage objects: ${error.message}`);
324
+ }
243
325
  }
244
326
  });
245
327
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hot-updater/supabase",
3
3
  "type": "module",
4
- "version": "1.2.0-rc.7",
4
+ "version": "1.2.0-rc.9",
5
5
  "description": "React Native OTA solution for self-hosted",
6
6
  "main": "dist/index.cjs",
7
7
  "module": "dist/index.mjs",
@@ -51,10 +51,10 @@
51
51
  "@supabase/supabase-js": "2.76.1",
52
52
  "hono": "4.12.34",
53
53
  "uuidv7": "^1.0.2",
54
- "@hot-updater/protocol": "1.2.0-rc.7",
55
- "@hot-updater/plugin-core": "1.2.0-rc.7",
56
- "@hot-updater/cli-tools": "1.2.0-rc.7",
57
- "@hot-updater/server": "1.2.0-rc.7"
54
+ "@hot-updater/protocol": "1.2.0-rc.9",
55
+ "@hot-updater/plugin-core": "1.2.0-rc.9",
56
+ "@hot-updater/server": "1.2.0-rc.9",
57
+ "@hot-updater/cli-tools": "1.2.0-rc.9"
58
58
  },
59
59
  "devDependencies": {
60
60
  "@electric-sql/pglite": "0.4.1",
@@ -64,9 +64,9 @@
64
64
  "execa": "9.5.2",
65
65
  "@types/node": "^20",
66
66
  "mime": "^4.0.4",
67
- "@hot-updater/mock": "1.2.0-rc.7",
68
- "@hot-updater/test-utils": "1.2.0-rc.7",
69
- "@hot-updater/postgres": "1.2.0-rc.7"
67
+ "@hot-updater/mock": "1.2.0-rc.9",
68
+ "@hot-updater/test-utils": "1.2.0-rc.9",
69
+ "@hot-updater/postgres": "1.2.0-rc.9"
70
70
  },
71
71
  "scripts": {
72
72
  "build": "tsdown",