@hot-updater/supabase 1.0.0-rc.21 → 1.0.0-rc.22

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,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_plugins = require("./plugins-DJUmZYvT.cjs");
2
+ const require_plugins = require("./plugins-jpIkI_T2.cjs");
3
3
  //#region src/supabaseEdgeFunctionStorage.ts
4
4
  const supabaseEdgeFunctionStorage = (config) => require_plugins.supabaseStorage(config);
5
5
  //#endregion
package/dist/edge.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { n as supabaseStorage, r as supabaseDatabase, t as plugins } from "./plugins-B8grmU79.mjs";
1
+ import { n as supabaseStorage, r as supabaseDatabase, t as plugins } from "./plugins-Dj-4Pk3j.mjs";
2
2
  //#region src/supabaseEdgeFunctionStorage.ts
3
3
  const supabaseEdgeFunctionStorage = (config) => supabaseStorage(config);
4
4
  //#endregion
package/dist/index.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_plugins = require("./plugins-DJUmZYvT.cjs");
2
+ const require_plugins = require("./plugins-jpIkI_T2.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.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { a as SUPABASE_TABLE_PREFIX, i as SUPABASE_APPLY_FUNCTION, n as supabaseStorage, r as supabaseDatabase$1, t as plugins } from "./plugins-B8grmU79.mjs";
1
+ import { a as SUPABASE_TABLE_PREFIX, i as SUPABASE_APPLY_FUNCTION, n as supabaseStorage, r as supabaseDatabase$1, t as plugins } from "./plugins-Dj-4Pk3j.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. */
@@ -129,13 +129,44 @@ const createSupabaseSignedUrlBatcher = ({ createSignedUrls, expiresIn, formatObj
129
129
  //#endregion
130
130
  //#region src/supabaseStorage.ts
131
131
  const isNotFoundError = (error) => error?.message?.toLowerCase().includes("not found") === true;
132
+ /**
133
+ * Storage's unread response to a download or HEAD request it failed, which
134
+ * supabase-js wraps in a StorageUnknownError.
135
+ */
136
+ const failedResponseOf = (error) => {
137
+ if (typeof error !== "object" || error === null || Reflect.get(error, "name") !== "StorageUnknownError") return;
138
+ const response = Reflect.get(error, "originalError");
139
+ if (typeof response !== "object" || response === null || typeof Reflect.get(response, "status") !== "number") return;
140
+ return response;
141
+ };
142
+ /** Storage answers a HEAD request for a missing object with 400 or 404. */
132
143
  const isMissingExistsError = (error) => {
133
- if (typeof error !== "object" || error === null || Reflect.get(error, "name") !== "StorageUnknownError") return false;
134
- const originalError = Reflect.get(error, "originalError");
135
- if (typeof originalError !== "object" || originalError === null) return false;
136
- const status = Reflect.get(originalError, "status");
144
+ const status = failedResponseOf(error)?.status;
137
145
  return status === 400 || status === 404;
138
146
  };
147
+ /**
148
+ * Storage answers a download of a missing object with 400 or 404 and
149
+ * `error: "not_found"`, and a missing bucket with another error.
150
+ */
151
+ const isMissingObjectError = async (error) => {
152
+ const response = failedResponseOf(error);
153
+ if (response?.status !== 400 && response?.status !== 404) return false;
154
+ const body = await response.json().catch(() => void 0);
155
+ return typeof body === "object" && body !== null && Reflect.get(body, "error") === "not_found";
156
+ };
157
+ /** A character Storage rejects in an object name, `?`, or `!`. */
158
+ const ESCAPED_CHARACTER = /[^A-Za-z0-9_/.*'() &$=@;:+,-]/gu;
159
+ const utf8 = new TextEncoder();
160
+ /**
161
+ * The object name Storage keeps a key under. Storage accepts names of ASCII
162
+ * letters, digits and `_/!.*'() &$=@;:+,?-` only, and supabase-js puts a name
163
+ * in the request URL unencoded, where `?` ends the path. So every other
164
+ * character of the key, `?`, and the escape character `!` are stored as `!`
165
+ * and the two hex digits of each of their UTF-8 bytes, such as `#` as `!23`.
166
+ * Distinct keys keep distinct names, and the names need no URL encoding.
167
+ * Deploy's keys, such as `<bundleId>/manifest.json`, are stored as they are.
168
+ */
169
+ const toObjectName = (key) => key.replace(ESCAPED_CHARACTER, (character) => Array.from(utf8.encode(character), (byte) => `!${byte.toString(16).toUpperCase().padStart(2, "0")}`).join(""));
139
170
  const supabaseStorage = (config) => {
140
171
  const bucket = createClient(config.supabaseUrl, resolveSupabaseServiceRoleKey(config)).storage.from(config.bucketName);
141
172
  const getStorageKey = createStorageKeyBuilder(config.basePath);
@@ -154,7 +185,7 @@ const supabaseStorage = (config) => {
154
185
  protocol: "supabase-storage",
155
186
  async put({ key, body, contentLength, contentType }) {
156
187
  const storageKey = getStorageKey(key);
157
- const { error } = await bucket.upload(storageKey, body, {
188
+ const { error } = await bucket.upload(toObjectName(storageKey), body, {
158
189
  contentType,
159
190
  cacheControl: "max-age=31536000",
160
191
  upsert: true,
@@ -170,20 +201,20 @@ const supabaseStorage = (config) => {
170
201
  },
171
202
  async get({ storageUri }) {
172
203
  const { key } = parseAndValidate(storageUri);
173
- const { data, error } = await bucket.download(key);
204
+ const { data, error } = await bucket.download(toObjectName(key));
174
205
  if (error) {
175
- if (isNotFoundError(error)) return { response: null };
206
+ if (await isMissingObjectError(error)) return { response: null };
176
207
  throw new Error(`Failed to download storage object: ${error.message}`);
177
208
  }
178
209
  return { response: data ? new Response(data) : null };
179
210
  },
180
211
  async getDownloadUrl({ storageUri }) {
181
212
  const { key } = parseAndValidate(storageUri);
182
- return { url: await resolveSignedUrl(config.bucketName, key) };
213
+ return { url: await resolveSignedUrl(config.bucketName, toObjectName(key)) };
183
214
  },
184
215
  async exists({ storageUri }) {
185
216
  const { key } = parseAndValidate(storageUri);
186
- const { data, error } = await bucket.exists(key);
217
+ const { data, error } = await bucket.exists(toObjectName(key));
187
218
  if (error) {
188
219
  if (isMissingExistsError(error)) return { exists: false };
189
220
  throw error;
@@ -192,7 +223,7 @@ const supabaseStorage = (config) => {
192
223
  },
193
224
  async delete({ storageUri }) {
194
225
  const { key } = parseAndValidate(storageUri);
195
- const { error } = await bucket.remove([key]);
226
+ const { error } = await bucket.remove([toObjectName(key)]);
196
227
  if (error && !isNotFoundError(error)) throw new Error(`Failed to delete storage object: ${error.message}`);
197
228
  return { deleted: true };
198
229
  }
@@ -129,13 +129,44 @@ const createSupabaseSignedUrlBatcher = ({ createSignedUrls, expiresIn, formatObj
129
129
  //#endregion
130
130
  //#region src/supabaseStorage.ts
131
131
  const isNotFoundError = (error) => error?.message?.toLowerCase().includes("not found") === true;
132
+ /**
133
+ * Storage's unread response to a download or HEAD request it failed, which
134
+ * supabase-js wraps in a StorageUnknownError.
135
+ */
136
+ const failedResponseOf = (error) => {
137
+ if (typeof error !== "object" || error === null || Reflect.get(error, "name") !== "StorageUnknownError") return;
138
+ const response = Reflect.get(error, "originalError");
139
+ if (typeof response !== "object" || response === null || typeof Reflect.get(response, "status") !== "number") return;
140
+ return response;
141
+ };
142
+ /** Storage answers a HEAD request for a missing object with 400 or 404. */
132
143
  const isMissingExistsError = (error) => {
133
- if (typeof error !== "object" || error === null || Reflect.get(error, "name") !== "StorageUnknownError") return false;
134
- const originalError = Reflect.get(error, "originalError");
135
- if (typeof originalError !== "object" || originalError === null) return false;
136
- const status = Reflect.get(originalError, "status");
144
+ const status = failedResponseOf(error)?.status;
137
145
  return status === 400 || status === 404;
138
146
  };
147
+ /**
148
+ * Storage answers a download of a missing object with 400 or 404 and
149
+ * `error: "not_found"`, and a missing bucket with another error.
150
+ */
151
+ const isMissingObjectError = async (error) => {
152
+ const response = failedResponseOf(error);
153
+ if (response?.status !== 400 && response?.status !== 404) return false;
154
+ const body = await response.json().catch(() => void 0);
155
+ return typeof body === "object" && body !== null && Reflect.get(body, "error") === "not_found";
156
+ };
157
+ /** A character Storage rejects in an object name, `?`, or `!`. */
158
+ const ESCAPED_CHARACTER = /[^A-Za-z0-9_/.*'() &$=@;:+,-]/gu;
159
+ const utf8 = new TextEncoder();
160
+ /**
161
+ * The object name Storage keeps a key under. Storage accepts names of ASCII
162
+ * letters, digits and `_/!.*'() &$=@;:+,?-` only, and supabase-js puts a name
163
+ * in the request URL unencoded, where `?` ends the path. So every other
164
+ * character of the key, `?`, and the escape character `!` are stored as `!`
165
+ * and the two hex digits of each of their UTF-8 bytes, such as `#` as `!23`.
166
+ * Distinct keys keep distinct names, and the names need no URL encoding.
167
+ * Deploy's keys, such as `<bundleId>/manifest.json`, are stored as they are.
168
+ */
169
+ const toObjectName = (key) => key.replace(ESCAPED_CHARACTER, (character) => Array.from(utf8.encode(character), (byte) => `!${byte.toString(16).toUpperCase().padStart(2, "0")}`).join(""));
139
170
  const supabaseStorage = (config) => {
140
171
  const bucket = (0, _supabase_supabase_js.createClient)(config.supabaseUrl, resolveSupabaseServiceRoleKey(config)).storage.from(config.bucketName);
141
172
  const getStorageKey = (0, _hot_updater_plugin_core.createStorageKeyBuilder)(config.basePath);
@@ -154,7 +185,7 @@ const supabaseStorage = (config) => {
154
185
  protocol: "supabase-storage",
155
186
  async put({ key, body, contentLength, contentType }) {
156
187
  const storageKey = getStorageKey(key);
157
- const { error } = await bucket.upload(storageKey, body, {
188
+ const { error } = await bucket.upload(toObjectName(storageKey), body, {
158
189
  contentType,
159
190
  cacheControl: "max-age=31536000",
160
191
  upsert: true,
@@ -170,20 +201,20 @@ const supabaseStorage = (config) => {
170
201
  },
171
202
  async get({ storageUri }) {
172
203
  const { key } = parseAndValidate(storageUri);
173
- const { data, error } = await bucket.download(key);
204
+ const { data, error } = await bucket.download(toObjectName(key));
174
205
  if (error) {
175
- if (isNotFoundError(error)) return { response: null };
206
+ if (await isMissingObjectError(error)) return { response: null };
176
207
  throw new Error(`Failed to download storage object: ${error.message}`);
177
208
  }
178
209
  return { response: data ? new Response(data) : null };
179
210
  },
180
211
  async getDownloadUrl({ storageUri }) {
181
212
  const { key } = parseAndValidate(storageUri);
182
- return { url: await resolveSignedUrl(config.bucketName, key) };
213
+ return { url: await resolveSignedUrl(config.bucketName, toObjectName(key)) };
183
214
  },
184
215
  async exists({ storageUri }) {
185
216
  const { key } = parseAndValidate(storageUri);
186
- const { data, error } = await bucket.exists(key);
217
+ const { data, error } = await bucket.exists(toObjectName(key));
187
218
  if (error) {
188
219
  if (isMissingExistsError(error)) return { exists: false };
189
220
  throw error;
@@ -192,7 +223,7 @@ const supabaseStorage = (config) => {
192
223
  },
193
224
  async delete({ storageUri }) {
194
225
  const { key } = parseAndValidate(storageUri);
195
- const { error } = await bucket.remove([key]);
226
+ const { error } = await bucket.remove([toObjectName(key)]);
196
227
  if (error && !isNotFoundError(error)) throw new Error(`Failed to delete storage object: ${error.message}`);
197
228
  return { deleted: true };
198
229
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hot-updater/supabase",
3
3
  "type": "module",
4
- "version": "1.0.0-rc.21",
4
+ "version": "1.0.0-rc.22",
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.0.0-rc.21",
55
- "@hot-updater/plugin-core": "1.0.0-rc.21",
56
- "@hot-updater/cli-tools": "1.0.0-rc.21",
57
- "@hot-updater/server": "1.0.0-rc.21"
54
+ "@hot-updater/protocol": "1.0.0-rc.22",
55
+ "@hot-updater/plugin-core": "1.0.0-rc.22",
56
+ "@hot-updater/cli-tools": "1.0.0-rc.22",
57
+ "@hot-updater/server": "1.0.0-rc.22"
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.0.0-rc.21",
68
- "@hot-updater/test-utils": "1.0.0-rc.21",
69
- "@hot-updater/postgres": "1.0.0-rc.21"
67
+ "@hot-updater/mock": "1.0.0-rc.22",
68
+ "@hot-updater/test-utils": "1.0.0-rc.22",
69
+ "@hot-updater/postgres": "1.0.0-rc.22"
70
70
  },
71
71
  "scripts": {
72
72
  "build": "tsdown",