@hot-updater/supabase 1.1.4 → 1.1.6

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-DAl3pyb7.cjs");
2
+ const require_supabaseStorage = require("./supabaseStorage-BJTIWSwx.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-BryXQ72m.cjs";
1
+ import { i as supabaseDatabase, n as supabaseStorage, r as SupabaseDatabaseConfig, t as SupabaseStorageConfig } from "./supabaseStorage-CkNoVO3F.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-BryXQ72m.mjs";
1
+ import { i as supabaseDatabase, n as supabaseStorage, r as SupabaseDatabaseConfig, t as SupabaseStorageConfig } from "./supabaseStorage-CkNoVO3F.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-C8xQ3nyM.mjs";
1
+ import { n as supabaseDatabase, t as supabaseStorage } from "./supabaseStorage-CKYtf3yW.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-DAl3pyb7.cjs");
2
+ const require_supabaseStorage = require("./supabaseStorage-BJTIWSwx.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-BryXQ72m.cjs";
1
+ import { n as supabaseStorage, r as SupabaseDatabaseConfig, t as SupabaseStorageConfig } from "./supabaseStorage-CkNoVO3F.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-BryXQ72m.mjs";
1
+ import { n as supabaseStorage, r as SupabaseDatabaseConfig, t as SupabaseStorageConfig } from "./supabaseStorage-CkNoVO3F.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-C8xQ3nyM.mjs";
1
+ import { i as SUPABASE_TABLE_PREFIX, n as supabaseDatabase$1, r as SUPABASE_APPLY_FUNCTION, t as supabaseStorage } from "./supabaseStorage-CKYtf3yW.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. */
@@ -154,7 +154,15 @@ const isMissingObjectError = async (error) => {
154
154
  };
155
155
  /** A character Storage rejects in an object name, `?`, or `!`. */
156
156
  const ESCAPED_CHARACTER = /[^A-Za-z0-9_/.*'() &$=@;:+,-]/gu;
157
+ const ESCAPE_SEQUENCE = /!([0-9A-F]{2})/gu;
157
158
  const utf8 = new TextEncoder();
159
+ const utf8Decoder = new TextDecoder("utf-8", { fatal: true });
160
+ /** Objects Storage lists a page at a time. */
161
+ const LIST_PAGE_SIZE = 1e3;
162
+ /** Names Storage removes in one request. */
163
+ const DELETE_BATCH_SIZE = 1e3;
164
+ /** The object the Supabase dashboard keeps in a folder it creates. */
165
+ const EMPTY_FOLDER_PLACEHOLDER = ".emptyFolderPlaceholder";
158
166
  /**
159
167
  * The object name Storage keeps a key under. Storage accepts names of ASCII
160
168
  * letters, digits and `_/!.*'() &$=@;:+,?-` only, and supabase-js puts a name
@@ -165,9 +173,38 @@ const utf8 = new TextEncoder();
165
173
  * Deploy's keys, such as `<bundleId>/manifest.json`, are stored as they are.
166
174
  */
167
175
  const toObjectName = (key) => key.replace(ESCAPED_CHARACTER, (character) => Array.from(utf8.encode(character), (byte) => `!${byte.toString(16).toUpperCase().padStart(2, "0")}`).join(""));
176
+ /** The key `toObjectName` stored under `name`, or null for a name it never makes. */
177
+ const fromObjectName = (name) => {
178
+ const bytes = [];
179
+ const parts = [];
180
+ const flush = () => {
181
+ if (bytes.length === 0) return true;
182
+ try {
183
+ parts.push(utf8Decoder.decode(new Uint8Array(bytes)));
184
+ } catch {
185
+ return false;
186
+ }
187
+ bytes.length = 0;
188
+ return true;
189
+ };
190
+ let index = 0;
191
+ for (const match of name.matchAll(ESCAPE_SEQUENCE)) {
192
+ if (match.index !== index) {
193
+ if (!flush()) return null;
194
+ parts.push(name.slice(index, match.index));
195
+ }
196
+ bytes.push(Number.parseInt(match[1], 16));
197
+ index = match.index + match[0].length;
198
+ }
199
+ if (!flush()) return null;
200
+ parts.push(name.slice(index));
201
+ const key = parts.join("");
202
+ return toObjectName(key) === name ? key : null;
203
+ };
168
204
  const supabaseStorage = (config) => {
169
205
  const bucket = (0, _supabase_supabase_js.createClient)(config.supabaseUrl, resolveSupabaseServiceRoleKey(config)).storage.from(config.bucketName);
170
206
  const getStorageKey = (0, _hot_updater_plugin_core.createStorageKeyBuilder)(config.basePath);
207
+ const basePrefix = getStorageKey() ? `${getStorageKey()}/` : "";
171
208
  const resolveSignedUrl = createSupabaseSignedUrlBatcher({
172
209
  createSignedUrls: (_bucketName, keys, expiresIn) => bucket.createSignedUrls(keys, expiresIn),
173
210
  expiresIn: config.signedUrlExpiresIn ?? 3600,
@@ -224,6 +261,51 @@ const supabaseStorage = (config) => {
224
261
  const { error } = await bucket.remove([toObjectName(key)]);
225
262
  if (error && !isNotFoundError(error)) throw new Error(`Failed to delete storage object: ${error.message}`);
226
263
  return { deleted: true };
264
+ },
265
+ async listObjects(prefix) {
266
+ const objects = [];
267
+ const folder = toObjectName(getStorageKey(prefix ?? ""));
268
+ let cursor;
269
+ do {
270
+ const { data, error } = await bucket.listV2({
271
+ prefix: folder ? `${folder}/` : "",
272
+ limit: LIST_PAGE_SIZE,
273
+ ...cursor === void 0 ? {} : { cursor }
274
+ });
275
+ if (error) throw new Error(`Failed to list storage objects: ${error.message}`);
276
+ for (const entry of data.objects) {
277
+ const name = entry.key ?? entry.name;
278
+ if (name.split("/").at(-1) === EMPTY_FOLDER_PLACEHOLDER) continue;
279
+ const key = fromObjectName(name);
280
+ if (key === null || !key.startsWith(basePrefix)) continue;
281
+ let storageUri;
282
+ try {
283
+ storageUri = (0, _hot_updater_plugin_core.createStorageUri)({
284
+ protocol: "supabase-storage",
285
+ bucket: config.bucketName,
286
+ key
287
+ });
288
+ } catch {
289
+ continue;
290
+ }
291
+ const updatedAt = entry.updated_at ?? entry.created_at;
292
+ objects.push({
293
+ key: key.slice(basePrefix.length),
294
+ ...updatedAt ? { lastModifiedAt: new Date(updatedAt) } : {},
295
+ size: Number(entry.metadata?.size ?? 0),
296
+ storageUri
297
+ });
298
+ }
299
+ cursor = data.hasNext ? data.nextCursor : void 0;
300
+ } while (cursor !== void 0);
301
+ return objects;
302
+ },
303
+ async deleteObjects(relativeKeys) {
304
+ const names = relativeKeys.map((key) => toObjectName(getStorageKey(key)));
305
+ for (let offset = 0; offset < names.length; offset += DELETE_BATCH_SIZE) {
306
+ const { error } = await bucket.remove(names.slice(offset, offset + DELETE_BATCH_SIZE));
307
+ if (error && !isNotFoundError(error)) throw new Error(`Failed to delete storage objects: ${error.message}`);
308
+ }
227
309
  }
228
310
  });
229
311
  };
@@ -154,7 +154,15 @@ const isMissingObjectError = async (error) => {
154
154
  };
155
155
  /** A character Storage rejects in an object name, `?`, or `!`. */
156
156
  const ESCAPED_CHARACTER = /[^A-Za-z0-9_/.*'() &$=@;:+,-]/gu;
157
+ const ESCAPE_SEQUENCE = /!([0-9A-F]{2})/gu;
157
158
  const utf8 = new TextEncoder();
159
+ const utf8Decoder = new TextDecoder("utf-8", { fatal: true });
160
+ /** Objects Storage lists a page at a time. */
161
+ const LIST_PAGE_SIZE = 1e3;
162
+ /** Names Storage removes in one request. */
163
+ const DELETE_BATCH_SIZE = 1e3;
164
+ /** The object the Supabase dashboard keeps in a folder it creates. */
165
+ const EMPTY_FOLDER_PLACEHOLDER = ".emptyFolderPlaceholder";
158
166
  /**
159
167
  * The object name Storage keeps a key under. Storage accepts names of ASCII
160
168
  * letters, digits and `_/!.*'() &$=@;:+,?-` only, and supabase-js puts a name
@@ -165,9 +173,38 @@ const utf8 = new TextEncoder();
165
173
  * Deploy's keys, such as `<bundleId>/manifest.json`, are stored as they are.
166
174
  */
167
175
  const toObjectName = (key) => key.replace(ESCAPED_CHARACTER, (character) => Array.from(utf8.encode(character), (byte) => `!${byte.toString(16).toUpperCase().padStart(2, "0")}`).join(""));
176
+ /** The key `toObjectName` stored under `name`, or null for a name it never makes. */
177
+ const fromObjectName = (name) => {
178
+ const bytes = [];
179
+ const parts = [];
180
+ const flush = () => {
181
+ if (bytes.length === 0) return true;
182
+ try {
183
+ parts.push(utf8Decoder.decode(new Uint8Array(bytes)));
184
+ } catch {
185
+ return false;
186
+ }
187
+ bytes.length = 0;
188
+ return true;
189
+ };
190
+ let index = 0;
191
+ for (const match of name.matchAll(ESCAPE_SEQUENCE)) {
192
+ if (match.index !== index) {
193
+ if (!flush()) return null;
194
+ parts.push(name.slice(index, match.index));
195
+ }
196
+ bytes.push(Number.parseInt(match[1], 16));
197
+ index = match.index + match[0].length;
198
+ }
199
+ if (!flush()) return null;
200
+ parts.push(name.slice(index));
201
+ const key = parts.join("");
202
+ return toObjectName(key) === name ? key : null;
203
+ };
168
204
  const supabaseStorage = (config) => {
169
205
  const bucket = createClient(config.supabaseUrl, resolveSupabaseServiceRoleKey(config)).storage.from(config.bucketName);
170
206
  const getStorageKey = createStorageKeyBuilder(config.basePath);
207
+ const basePrefix = getStorageKey() ? `${getStorageKey()}/` : "";
171
208
  const resolveSignedUrl = createSupabaseSignedUrlBatcher({
172
209
  createSignedUrls: (_bucketName, keys, expiresIn) => bucket.createSignedUrls(keys, expiresIn),
173
210
  expiresIn: config.signedUrlExpiresIn ?? 3600,
@@ -224,6 +261,51 @@ const supabaseStorage = (config) => {
224
261
  const { error } = await bucket.remove([toObjectName(key)]);
225
262
  if (error && !isNotFoundError(error)) throw new Error(`Failed to delete storage object: ${error.message}`);
226
263
  return { deleted: true };
264
+ },
265
+ async listObjects(prefix) {
266
+ const objects = [];
267
+ const folder = toObjectName(getStorageKey(prefix ?? ""));
268
+ let cursor;
269
+ do {
270
+ const { data, error } = await bucket.listV2({
271
+ prefix: folder ? `${folder}/` : "",
272
+ limit: LIST_PAGE_SIZE,
273
+ ...cursor === void 0 ? {} : { cursor }
274
+ });
275
+ if (error) throw new Error(`Failed to list storage objects: ${error.message}`);
276
+ for (const entry of data.objects) {
277
+ const name = entry.key ?? entry.name;
278
+ if (name.split("/").at(-1) === EMPTY_FOLDER_PLACEHOLDER) continue;
279
+ const key = fromObjectName(name);
280
+ if (key === null || !key.startsWith(basePrefix)) continue;
281
+ let storageUri;
282
+ try {
283
+ storageUri = createStorageUri({
284
+ protocol: "supabase-storage",
285
+ bucket: config.bucketName,
286
+ key
287
+ });
288
+ } catch {
289
+ continue;
290
+ }
291
+ const updatedAt = entry.updated_at ?? entry.created_at;
292
+ objects.push({
293
+ key: key.slice(basePrefix.length),
294
+ ...updatedAt ? { lastModifiedAt: new Date(updatedAt) } : {},
295
+ size: Number(entry.metadata?.size ?? 0),
296
+ storageUri
297
+ });
298
+ }
299
+ cursor = data.hasNext ? data.nextCursor : void 0;
300
+ } while (cursor !== void 0);
301
+ return objects;
302
+ },
303
+ async deleteObjects(relativeKeys) {
304
+ const names = relativeKeys.map((key) => toObjectName(getStorageKey(key)));
305
+ for (let offset = 0; offset < names.length; offset += DELETE_BATCH_SIZE) {
306
+ const { error } = await bucket.remove(names.slice(offset, offset + DELETE_BATCH_SIZE));
307
+ if (error && !isNotFoundError(error)) throw new Error(`Failed to delete storage objects: ${error.message}`);
308
+ }
227
309
  }
228
310
  });
229
311
  };
@@ -23,6 +23,6 @@ type SupabaseStorageConfig = SupabaseServiceRoleConfig & {
23
23
  /** Signed download URL lifetime in seconds. @default 3600 */
24
24
  signedUrlExpiresIn?: number;
25
25
  };
26
- declare const supabaseStorage: (config: SupabaseStorageConfig) => StorageAdapterWith<"put" | "get" | "getDownloadUrl" | "exists" | "delete">;
26
+ declare const supabaseStorage: (config: SupabaseStorageConfig) => StorageAdapterWith<"put" | "get" | "getDownloadUrl" | "exists" | "delete" | "listObjects" | "deleteObjects">;
27
27
  //#endregion
28
28
  export { supabaseDatabase as i, supabaseStorage as n, SupabaseDatabaseConfig as r, SupabaseStorageConfig as t };
@@ -23,6 +23,6 @@ type SupabaseStorageConfig = SupabaseServiceRoleConfig & {
23
23
  /** Signed download URL lifetime in seconds. @default 3600 */
24
24
  signedUrlExpiresIn?: number;
25
25
  };
26
- declare const supabaseStorage: (config: SupabaseStorageConfig) => StorageAdapterWith<"put" | "get" | "getDownloadUrl" | "exists" | "delete">;
26
+ declare const supabaseStorage: (config: SupabaseStorageConfig) => StorageAdapterWith<"put" | "get" | "getDownloadUrl" | "exists" | "delete" | "listObjects" | "deleteObjects">;
27
27
  //#endregion
28
28
  export { supabaseDatabase as i, supabaseStorage as n, SupabaseDatabaseConfig as r, SupabaseStorageConfig as t };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hot-updater/supabase",
3
3
  "type": "module",
4
- "version": "1.1.4",
4
+ "version": "1.1.6",
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.1.4",
55
- "@hot-updater/cli-tools": "1.1.4",
56
- "@hot-updater/plugin-core": "1.1.4",
57
- "@hot-updater/server": "1.1.4"
54
+ "@hot-updater/protocol": "1.1.6",
55
+ "@hot-updater/plugin-core": "1.1.6",
56
+ "@hot-updater/server": "1.1.6",
57
+ "@hot-updater/cli-tools": "1.1.6"
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/test-utils": "1.1.4",
68
- "@hot-updater/mock": "1.1.4",
69
- "@hot-updater/postgres": "1.1.4"
67
+ "@hot-updater/test-utils": "1.1.6",
68
+ "@hot-updater/postgres": "1.1.6",
69
+ "@hot-updater/mock": "1.1.6"
70
70
  },
71
71
  "scripts": {
72
72
  "build": "tsdown",