@hot-updater/supabase 1.2.0-rc.8 → 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-DFBKC3sm.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-DFBKC3sm.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-DFBKC3sm.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-DFBKC3sm.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. */
@@ -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
  };
@@ -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 };
@@ -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.8",
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.8",
55
- "@hot-updater/plugin-core": "1.2.0-rc.8",
56
- "@hot-updater/server": "1.2.0-rc.8",
57
- "@hot-updater/cli-tools": "1.2.0-rc.8"
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.8",
68
- "@hot-updater/test-utils": "1.2.0-rc.8",
69
- "@hot-updater/postgres": "1.2.0-rc.8"
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",