@hot-updater/supabase 0.36.7 → 1.0.0-rc.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.
Files changed (37) hide show
  1. package/dist/edge.cjs +6 -3
  2. package/dist/edge.d.cts +14 -2
  3. package/dist/edge.d.mts +14 -2
  4. package/dist/edge.mjs +5 -2
  5. package/dist/iac/index.cjs +607 -50
  6. package/dist/iac/index.d.cts +26 -8
  7. package/dist/iac/index.d.mts +26 -8
  8. package/dist/iac/index.mjs +604 -52
  9. package/dist/index.cjs +3 -145
  10. package/dist/index.d.cts +7 -24
  11. package/dist/index.d.mts +7 -24
  12. package/dist/index.mjs +2 -117
  13. package/dist/init/index.cjs +5 -2
  14. package/dist/init/index.d.cts +2 -2
  15. package/dist/init/index.d.mts +2 -2
  16. package/dist/init/index.mjs +5 -2
  17. package/dist/supabaseDatabase-BDIBsXpz.d.cts +13 -0
  18. package/dist/supabaseDatabase-CP9WalBi.d.mts +13 -0
  19. package/dist/supabaseStorage-Cw294tR4.mjs +557 -0
  20. package/dist/supabaseStorage-yP7u4m7E.cjs +568 -0
  21. package/package.json +9 -9
  22. package/supabase/edge-functions/index.ts +9 -13
  23. package/supabase/edge-functions/runtime.docker.integration.spec.ts +417 -202
  24. package/supabase/migrations/20260818000000_hot-updater_1.0.0.sql +583 -0
  25. package/supabase/migrations.spec.ts +127 -46
  26. package/dist/supabaseEdgeFunctionStorage-BRxGvt-r.d.mts +0 -19
  27. package/dist/supabaseEdgeFunctionStorage-BSLlUeqi.cjs +0 -386
  28. package/dist/supabaseEdgeFunctionStorage-CU396KO3.d.cts +0 -19
  29. package/dist/supabaseEdgeFunctionStorage-pV0s_mTF.mjs +0 -356
  30. package/supabase/migrations/20250103114225_init.sql +0 -228
  31. package/supabase/migrations/20250314000000_hot-updater_0.13.0.sql +0 -134
  32. package/supabase/migrations/20250516000000_hot-updater_0.18.0.sql +0 -194
  33. package/supabase/migrations/20251014000000_hot-updater_0.21.0.sql +0 -184
  34. package/supabase/migrations/20260401000000_hot-updater_0.29.0.sql +0 -503
  35. package/supabase/migrations/20260414000000_hot-updater_0.30.0.sql +0 -50
  36. package/supabase/migrations/20260422000000_hot-updater_0.31.0.sql +0 -20
  37. package/supabase/migrations/20260520014100_hot-updater_rls.sql +0 -48
@@ -0,0 +1,568 @@
1
+ let _hot_updater_plugin_core = require("@hot-updater/plugin-core");
2
+ let _hot_updater_plugin_core_internal = require("@hot-updater/plugin-core/internal");
3
+ let _supabase_supabase_js = require("@supabase/supabase-js");
4
+ //#region src/supabaseConfig.ts
5
+ const resolveSupabaseServiceRoleKey = (config) => {
6
+ if (!config.supabaseServiceRoleKey) throw new Error("Supabase service role key is required. Set supabaseServiceRoleKey.");
7
+ return config.supabaseServiceRoleKey;
8
+ };
9
+ //#endregion
10
+ //#region src/supabaseFilter.ts
11
+ var InvalidSupabasePredicateError = class extends Error {
12
+ name = "InvalidSupabasePredicateError";
13
+ };
14
+ const encodeValue = (value) => {
15
+ if (typeof value !== "string") return String(value);
16
+ return `"${value.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"")}"`;
17
+ };
18
+ const encodePattern = (value) => encodeValue(value.replaceAll("*", "\\*").replaceAll("%", "\\%").replaceAll("_", "\\_"));
19
+ const predicate = (condition) => {
20
+ const operator = condition.operator ?? "eq";
21
+ const field = condition.field;
22
+ switch (operator) {
23
+ case "eq":
24
+ case "ne":
25
+ if (condition.value === null) return `${field}.${operator === "eq" ? "is" : "not.is"}.null`;
26
+ if (typeof condition.value !== "boolean" && typeof condition.value !== "number" && typeof condition.value !== "string") throw new InvalidSupabasePredicateError();
27
+ if ("mode" in condition && condition.mode === "insensitive") {
28
+ const comparison = encodePattern(condition.value);
29
+ return `${field}.${operator === "eq" ? "ilike" : "not.ilike"}.${comparison}`;
30
+ }
31
+ return `${field}.${operator === "eq" ? "eq" : "neq"}.${encodeValue(condition.value)}`;
32
+ case "gt":
33
+ case "gte":
34
+ case "lt":
35
+ case "lte":
36
+ if (typeof condition.value !== "number" && typeof condition.value !== "string") throw new InvalidSupabasePredicateError();
37
+ return `${field}.${operator}.${encodeValue(condition.value)}`;
38
+ case "in":
39
+ case "not_in": {
40
+ if (!Array.isArray(condition.value)) throw new InvalidSupabasePredicateError();
41
+ if (condition.value.length === 0) return operator === "in" ? `and(${field}.is.null,${field}.not.is.null)` : `or(${field}.is.null,${field}.not.is.null)`;
42
+ const values = condition.value.map(encodeValue).join(",");
43
+ return `${field}.${operator === "in" ? "in" : "not.in"}.(${values})`;
44
+ }
45
+ case "contains":
46
+ case "starts_with":
47
+ case "ends_with": {
48
+ if (typeof condition.value !== "string") throw new InvalidSupabasePredicateError();
49
+ const value = encodePattern(condition.value).slice(1, -1);
50
+ const pattern = operator === "contains" ? `"*${value}*"` : operator === "starts_with" ? `"${value}*"` : `"*${value}"`;
51
+ return `${field}.${"mode" in condition && condition.mode === "insensitive" ? "ilike" : "like"}.${pattern}`;
52
+ }
53
+ }
54
+ };
55
+ const buildSupabaseFilter = (where) => {
56
+ const [first, ...rest] = where ?? [];
57
+ if (first === void 0) return;
58
+ let expression = predicate(first);
59
+ for (const condition of rest) {
60
+ const next = predicate(condition);
61
+ expression = condition.connector === "OR" ? `or(${expression},${next})` : `and(${expression},${next})`;
62
+ }
63
+ return expression;
64
+ };
65
+ //#endregion
66
+ //#region src/supabaseInfrastructureNames.ts
67
+ const SUPABASE_V1_TABLE_NAMES = {
68
+ apiKeys: "hot_updater_v1_api_keys",
69
+ bundleEvents: "hot_updater_v1_bundle_events",
70
+ bundlePatches: "hot_updater_v1_bundle_patches",
71
+ bundles: "hot_updater_v1_bundles",
72
+ channels: "hot_updater_v1_channels",
73
+ releaseCatalogs: "hot_updater_v1_release_catalogs",
74
+ releases: "hot_updater_v1_releases",
75
+ settings: "hot_updater_v1_private_settings"
76
+ };
77
+ const SUPABASE_V1_FUNCTION_NAMES = {
78
+ commit: "hot_updater_v1_commit",
79
+ deleteChannel: "hot_updater_v1_delete_channel"
80
+ };
81
+ //#endregion
82
+ //#region src/supabaseResult.ts
83
+ var SupabaseDatabaseError = class extends Error {
84
+ name = "SupabaseDatabaseError";
85
+ constructor(operation, cause) {
86
+ super(`Supabase database operation failed: ${operation}`, { cause });
87
+ this.operation = operation;
88
+ }
89
+ };
90
+ var SupabaseMissingDataError = class extends Error {
91
+ name = "SupabaseMissingDataError";
92
+ constructor(operation) {
93
+ super(`Supabase database operation returned no row: ${operation}`);
94
+ this.operation = operation;
95
+ }
96
+ };
97
+ const throwSupabaseError = (operation, error) => {
98
+ if (error !== null) throw new SupabaseDatabaseError(operation, error);
99
+ };
100
+ //#endregion
101
+ //#region src/supabaseDatabase.ts
102
+ const isForeignKeyViolation = (error) => typeof error === "object" && error !== null && Reflect.get(error, "code") === "23503";
103
+ const createSupabaseImplementation = (supabase) => {
104
+ const implementation = {
105
+ async create(input) {
106
+ switch (input.model) {
107
+ case "bundles": {
108
+ const { data, error } = await supabase.from(SUPABASE_V1_TABLE_NAMES.bundles).insert(input.data).select("*").single();
109
+ throwSupabaseError("create bundles", error);
110
+ if (data === null) throw new SupabaseMissingDataError("create bundles");
111
+ return data;
112
+ }
113
+ case "bundle_patches": {
114
+ const { data, error } = await supabase.from(SUPABASE_V1_TABLE_NAMES.bundlePatches).insert(input.data).select("*").single();
115
+ throwSupabaseError("create bundle_patches", error);
116
+ if (data === null) throw new SupabaseMissingDataError("create bundle_patches");
117
+ return data;
118
+ }
119
+ case "channels": {
120
+ if (input.onConflict === "ignore") {
121
+ const { data, error } = await supabase.from(SUPABASE_V1_TABLE_NAMES.channels).upsert(input.data, {
122
+ onConflict: "name",
123
+ ignoreDuplicates: true
124
+ }).select("*").maybeSingle();
125
+ throwSupabaseError("create channels", error);
126
+ return data ?? input.data;
127
+ }
128
+ const { data, error } = await supabase.from(SUPABASE_V1_TABLE_NAMES.channels).insert(input.data).select("*").single();
129
+ throwSupabaseError("create channels", error);
130
+ if (data === null) throw new SupabaseMissingDataError("create channels");
131
+ return data;
132
+ }
133
+ case "bundle_events": {
134
+ const { data, error } = await supabase.from(SUPABASE_V1_TABLE_NAMES.bundleEvents).insert(input.data).select("*").single();
135
+ throwSupabaseError("create bundle_events", error);
136
+ if (data === null) throw new SupabaseMissingDataError("create bundle_events");
137
+ return data;
138
+ }
139
+ case "releases": {
140
+ const { data, error } = await supabase.from(SUPABASE_V1_TABLE_NAMES.releases).insert(input.data).select("*").single();
141
+ throwSupabaseError("create releases", error);
142
+ if (data === null) throw new SupabaseMissingDataError("create releases");
143
+ return data;
144
+ }
145
+ case "release_catalogs": {
146
+ const { data, error } = await supabase.from(SUPABASE_V1_TABLE_NAMES.releaseCatalogs).insert(input.data).select("*").single();
147
+ throwSupabaseError("create release_catalogs", error);
148
+ if (data === null) throw new SupabaseMissingDataError("create release_catalogs");
149
+ return data;
150
+ }
151
+ case "api_keys": {
152
+ const { data, error } = await (input.onConflict === "ignore" ? supabase.from(SUPABASE_V1_TABLE_NAMES.apiKeys).upsert(input.data, {
153
+ onConflict: "hash",
154
+ ignoreDuplicates: true
155
+ }) : supabase.from(SUPABASE_V1_TABLE_NAMES.apiKeys).insert(input.data)).select("*").maybeSingle();
156
+ throwSupabaseError("create api_keys", error);
157
+ if (data === null && input.onConflict !== "ignore") throw new SupabaseMissingDataError("create api_keys");
158
+ return data ?? input.data;
159
+ }
160
+ }
161
+ },
162
+ async update(input) {
163
+ const filter = buildSupabaseFilter(input.where);
164
+ if (input.model === "api_keys") {
165
+ let query = supabase.from(SUPABASE_V1_TABLE_NAMES.apiKeys).update(input.update);
166
+ if (filter !== void 0) query = query.or(filter);
167
+ const { data, error } = await query.select("*").maybeSingle();
168
+ throwSupabaseError("update api_keys", error);
169
+ return data;
170
+ }
171
+ if (input.model === "releases") {
172
+ let query = supabase.from(SUPABASE_V1_TABLE_NAMES.releases).update(input.update);
173
+ if (filter !== void 0) query = query.or(filter);
174
+ const { data, error } = await query.select("*").maybeSingle();
175
+ throwSupabaseError("update releases", error);
176
+ return data;
177
+ }
178
+ if (input.model === "release_catalogs") {
179
+ let query = supabase.from(SUPABASE_V1_TABLE_NAMES.releaseCatalogs).update(input.update);
180
+ if (filter !== void 0) query = query.or(filter);
181
+ const { data, error } = await query.select("*").maybeSingle();
182
+ throwSupabaseError("update release_catalogs", error);
183
+ return data;
184
+ }
185
+ let query = supabase.from(SUPABASE_V1_TABLE_NAMES.bundles).update(input.update);
186
+ if (filter !== void 0) query = query.or(filter);
187
+ const { data, error } = await query.select("*").maybeSingle();
188
+ throwSupabaseError("update bundles", error);
189
+ return data;
190
+ },
191
+ async delete(input) {
192
+ const filter = buildSupabaseFilter(input.where);
193
+ switch (input.model) {
194
+ case "bundles": {
195
+ let query = supabase.from(SUPABASE_V1_TABLE_NAMES.bundles).delete();
196
+ if (filter !== void 0) query = query.or(filter);
197
+ const { error } = await query;
198
+ throwSupabaseError("delete bundles", error);
199
+ return;
200
+ }
201
+ case "bundle_patches": {
202
+ let query = supabase.from(SUPABASE_V1_TABLE_NAMES.bundlePatches).delete();
203
+ if (filter !== void 0) query = query.or(filter);
204
+ const { error } = await query;
205
+ throwSupabaseError("delete bundle_patches", error);
206
+ return;
207
+ }
208
+ case "channels": {
209
+ let query = supabase.from(SUPABASE_V1_TABLE_NAMES.channels).delete();
210
+ if (filter !== void 0) query = query.or(filter);
211
+ const { error } = await query;
212
+ if (isForeignKeyViolation(error)) throw new _hot_updater_plugin_core_internal.DatabaseRowReferencedError();
213
+ throwSupabaseError("delete channels", error);
214
+ }
215
+ case "releases": {
216
+ let query = supabase.from(SUPABASE_V1_TABLE_NAMES.releases).delete();
217
+ if (filter !== void 0) query = query.or(filter);
218
+ const { error } = await query;
219
+ throwSupabaseError("delete releases", error);
220
+ }
221
+ }
222
+ },
223
+ async count(input) {
224
+ if (input.distinct !== void 0) throw new _hot_updater_plugin_core.DatabasePluginInputError("invalid-operation");
225
+ const filter = buildSupabaseFilter(input.where);
226
+ switch (input.model) {
227
+ case "bundles": {
228
+ let query = supabase.from(SUPABASE_V1_TABLE_NAMES.bundles).select("*", {
229
+ count: "exact",
230
+ head: true
231
+ });
232
+ if (filter !== void 0) query = query.or(filter);
233
+ const { count, error } = await query;
234
+ throwSupabaseError("count bundles", error);
235
+ return count ?? 0;
236
+ }
237
+ case "bundle_patches": {
238
+ let query = supabase.from(SUPABASE_V1_TABLE_NAMES.bundlePatches).select("*", {
239
+ count: "exact",
240
+ head: true
241
+ });
242
+ if (filter !== void 0) query = query.or(filter);
243
+ const { count, error } = await query;
244
+ throwSupabaseError("count bundle_patches", error);
245
+ return count ?? 0;
246
+ }
247
+ case "releases": {
248
+ let query = supabase.from(SUPABASE_V1_TABLE_NAMES.releases).select("*", {
249
+ count: "exact",
250
+ head: true
251
+ });
252
+ if (filter !== void 0) query = query.or(filter);
253
+ const { count, error } = await query;
254
+ throwSupabaseError("count releases", error);
255
+ return count ?? 0;
256
+ }
257
+ }
258
+ },
259
+ async findOne(input) {
260
+ const filter = buildSupabaseFilter(input.where);
261
+ switch (input.model) {
262
+ case "bundles": {
263
+ let query = supabase.from(SUPABASE_V1_TABLE_NAMES.bundles).select("*");
264
+ if (filter !== void 0) query = query.or(filter);
265
+ const { data, error } = await query.limit(1).maybeSingle();
266
+ throwSupabaseError("findOne bundles", error);
267
+ return data;
268
+ }
269
+ case "api_keys": {
270
+ let query = supabase.from(SUPABASE_V1_TABLE_NAMES.apiKeys).select("*");
271
+ if (filter !== void 0) query = query.or(filter);
272
+ const { data, error } = await query.limit(1).maybeSingle();
273
+ throwSupabaseError("findOne api_keys", error);
274
+ return data;
275
+ }
276
+ case "channels": {
277
+ let query = supabase.from(SUPABASE_V1_TABLE_NAMES.channels).select("*");
278
+ if (filter !== void 0) query = query.or(filter);
279
+ const { data, error } = await query.limit(1).maybeSingle();
280
+ throwSupabaseError("findOne channels", error);
281
+ return data;
282
+ }
283
+ case "bundle_patches": {
284
+ let query = supabase.from(SUPABASE_V1_TABLE_NAMES.bundlePatches).select("*");
285
+ if (filter !== void 0) query = query.or(filter);
286
+ const { data, error } = await query.limit(1).maybeSingle();
287
+ throwSupabaseError("findOne bundle_patches", error);
288
+ return data;
289
+ }
290
+ case "releases": {
291
+ let query = supabase.from(SUPABASE_V1_TABLE_NAMES.releases).select("*");
292
+ if (filter !== void 0) query = query.or(filter);
293
+ const { data, error } = await query.limit(1).maybeSingle();
294
+ throwSupabaseError("findOne releases", error);
295
+ return data;
296
+ }
297
+ case "release_catalogs": {
298
+ let query = supabase.from(SUPABASE_V1_TABLE_NAMES.releaseCatalogs).select("*");
299
+ if (filter !== void 0) query = query.or(filter);
300
+ const { data, error } = await query.limit(1).maybeSingle();
301
+ throwSupabaseError("findOne release_catalogs", error);
302
+ return data;
303
+ }
304
+ }
305
+ },
306
+ async findMany(input) {
307
+ if (input.distinctOn !== void 0) throw new _hot_updater_plugin_core.DatabasePluginInputError("invalid-operation");
308
+ if (input.limit === 0) return [];
309
+ const filter = buildSupabaseFilter(input.where);
310
+ const rangeEnd = input.offset + input.limit - 1;
311
+ const orderBy = input.orderBy ?? [];
312
+ switch (input.model) {
313
+ case "bundles": {
314
+ let query = supabase.from(SUPABASE_V1_TABLE_NAMES.bundles).select("*");
315
+ if (filter !== void 0) query = query.or(filter);
316
+ for (const clause of orderBy) query = query.order(clause.field, {
317
+ ascending: clause.direction === "asc",
318
+ ...clause.nulls ? { nullsFirst: clause.nulls === "first" } : {}
319
+ });
320
+ const { data, error } = await query.range(input.offset, rangeEnd);
321
+ throwSupabaseError("findMany bundles", error);
322
+ return data ?? [];
323
+ }
324
+ case "bundle_events": {
325
+ let query = supabase.from(SUPABASE_V1_TABLE_NAMES.bundleEvents).select("*");
326
+ if (filter !== void 0) query = query.or(filter);
327
+ for (const clause of orderBy) query = query.order(clause.field, {
328
+ ascending: clause.direction === "asc",
329
+ ...clause.nulls ? { nullsFirst: clause.nulls === "first" } : {}
330
+ });
331
+ const { data, error } = await query.range(input.offset, rangeEnd);
332
+ throwSupabaseError("findMany bundle_events", error);
333
+ return data ?? [];
334
+ }
335
+ case "api_keys": {
336
+ let query = supabase.from(SUPABASE_V1_TABLE_NAMES.apiKeys).select("*");
337
+ if (filter !== void 0) query = query.or(filter);
338
+ for (const clause of orderBy) query = query.order(clause.field, {
339
+ ascending: clause.direction === "asc",
340
+ ...clause.nulls ? { nullsFirst: clause.nulls === "first" } : {}
341
+ });
342
+ const { data, error } = await query.range(input.offset, rangeEnd);
343
+ throwSupabaseError("findMany api_keys", error);
344
+ return data ?? [];
345
+ }
346
+ case "bundle_patches": {
347
+ let query = supabase.from(SUPABASE_V1_TABLE_NAMES.bundlePatches).select("*");
348
+ if (filter !== void 0) query = query.or(filter);
349
+ for (const clause of orderBy) query = query.order(clause.field, {
350
+ ascending: clause.direction === "asc",
351
+ ...clause.nulls ? { nullsFirst: clause.nulls === "first" } : {}
352
+ });
353
+ const { data, error } = await query.range(input.offset, rangeEnd);
354
+ throwSupabaseError("findMany bundle_patches", error);
355
+ return data ?? [];
356
+ }
357
+ case "channels": {
358
+ let query = supabase.from(SUPABASE_V1_TABLE_NAMES.channels).select("*");
359
+ if (filter !== void 0) query = query.or(filter);
360
+ for (const clause of orderBy) query = query.order(clause.field, {
361
+ ascending: clause.direction === "asc",
362
+ ...clause.nulls ? { nullsFirst: clause.nulls === "first" } : {}
363
+ });
364
+ const { data, error } = await query.range(input.offset, rangeEnd);
365
+ throwSupabaseError("findMany channels", error);
366
+ return data ?? [];
367
+ }
368
+ case "releases": {
369
+ let query = supabase.from(SUPABASE_V1_TABLE_NAMES.releases).select("*");
370
+ if (filter !== void 0) query = query.or(filter);
371
+ for (const clause of orderBy) query = query.order(clause.field, {
372
+ ascending: clause.direction === "asc",
373
+ ...clause.nulls ? { nullsFirst: clause.nulls === "first" } : {}
374
+ });
375
+ const { data, error } = await query.range(input.offset, rangeEnd);
376
+ throwSupabaseError("findMany releases", error);
377
+ return data ?? [];
378
+ }
379
+ case "release_catalogs": {
380
+ let query = supabase.from(SUPABASE_V1_TABLE_NAMES.releaseCatalogs).select("*");
381
+ if (filter !== void 0) query = query.or(filter);
382
+ for (const clause of orderBy) query = query.order(clause.field, {
383
+ ascending: clause.direction === "asc",
384
+ ...clause.nulls ? { nullsFirst: clause.nulls === "first" } : {}
385
+ });
386
+ const { data, error } = await query.range(input.offset, rangeEnd);
387
+ throwSupabaseError("findMany release_catalogs", error);
388
+ return data ?? [];
389
+ }
390
+ }
391
+ },
392
+ async insertChannel(input) {
393
+ const { data: inserted, error: insertError } = await supabase.from(SUPABASE_V1_TABLE_NAMES.channels).upsert(input.row, {
394
+ onConflict: "name",
395
+ ignoreDuplicates: true
396
+ }).select("*").maybeSingle();
397
+ throwSupabaseError("insert channel", insertError);
398
+ if (inserted !== null) return {
399
+ row: inserted,
400
+ inserted: true
401
+ };
402
+ const { data: existing, error: findError } = await supabase.from(SUPABASE_V1_TABLE_NAMES.channels).select("*").eq("name", input.row.name).single();
403
+ throwSupabaseError("find canonical channel", findError);
404
+ if (existing === null) throw new SupabaseMissingDataError("find canonical channel");
405
+ return {
406
+ row: existing,
407
+ inserted: false
408
+ };
409
+ },
410
+ async deleteChannel(input) {
411
+ const { data, error } = await supabase.rpc(SUPABASE_V1_FUNCTION_NAMES.deleteChannel, { p_id: input.id });
412
+ throwSupabaseError("delete channel", error);
413
+ if (data === null || typeof data !== "object" || !("deleted" in data) || typeof data.deleted !== "boolean") throw new SupabaseMissingDataError("delete channel");
414
+ return data;
415
+ }
416
+ };
417
+ implementation.commit = async (input) => {
418
+ const { data, error } = await supabase.rpc(SUPABASE_V1_FUNCTION_NAMES.commit, { p_commit: input });
419
+ throwSupabaseError("commit database changes", error);
420
+ if (data === null || typeof data !== "object" || !("committed" in data) || typeof data.committed !== "boolean") throw new SupabaseMissingDataError("commit database changes");
421
+ return data;
422
+ };
423
+ return implementation;
424
+ };
425
+ const supabaseDatabase = (config) => {
426
+ const adapter = (0, _hot_updater_plugin_core_internal.createDatabasePluginAdapter)("supabaseDatabase", createSupabaseImplementation((0, _supabase_supabase_js.createClient)(config.supabaseUrl, resolveSupabaseServiceRoleKey(config))));
427
+ return (0, _hot_updater_plugin_core.createDatabasePlugin)({
428
+ name: "supabaseDatabase",
429
+ models: adapter.models,
430
+ commit: adapter.commit
431
+ });
432
+ };
433
+ //#endregion
434
+ //#region src/supabaseSignedUrlBatcher.ts
435
+ function getErrorMessage(error) {
436
+ if (error instanceof Error) return error.message;
437
+ if (error && typeof error === "object" && "message" in error && typeof error.message === "string") return error.message;
438
+ return String(error);
439
+ }
440
+ const createSupabaseSignedUrlBatcher = ({ createSignedUrls, expiresIn, formatObjectPath }) => {
441
+ let pendingByBucket = /* @__PURE__ */ new Map();
442
+ let flushScheduled = false;
443
+ const createSignedUrlError = (bucketName, key, error) => /* @__PURE__ */ new Error(`Failed to generate download URL for "${formatObjectPath(bucketName, key)}": ${getErrorMessage(error)}`);
444
+ const flush = async () => {
445
+ const batches = pendingByBucket;
446
+ pendingByBucket = /* @__PURE__ */ new Map();
447
+ flushScheduled = false;
448
+ await Promise.all([...batches.entries()].map(async ([bucketName, pending]) => {
449
+ try {
450
+ const { data, error } = await createSignedUrls(bucketName, pending.map(({ key }) => key), expiresIn);
451
+ if (error || !data) {
452
+ const batchError = error ?? /* @__PURE__ */ new Error("missing signed URL response");
453
+ for (const request of pending) request.reject(createSignedUrlError(bucketName, request.key, batchError));
454
+ return;
455
+ }
456
+ pending.forEach((request, index) => {
457
+ const result = data[index];
458
+ if (!result?.error && result?.signedUrl) {
459
+ request.resolve(result.signedUrl);
460
+ return;
461
+ }
462
+ request.reject(createSignedUrlError(bucketName, request.key, result?.error ?? /* @__PURE__ */ new Error("missing signed URL")));
463
+ });
464
+ } catch (error) {
465
+ for (const request of pending) request.reject(createSignedUrlError(bucketName, request.key, error));
466
+ }
467
+ }));
468
+ };
469
+ return (bucketName, key) => new Promise((resolve, reject) => {
470
+ const pending = pendingByBucket.get(bucketName) ?? [];
471
+ pending.push({
472
+ key,
473
+ reject,
474
+ resolve
475
+ });
476
+ pendingByBucket.set(bucketName, pending);
477
+ if (!flushScheduled) {
478
+ flushScheduled = true;
479
+ queueMicrotask(() => {
480
+ flush();
481
+ });
482
+ }
483
+ });
484
+ };
485
+ //#endregion
486
+ //#region src/supabaseStorage.ts
487
+ const isNotFoundError = (error) => error?.message?.toLowerCase().includes("not found") === true;
488
+ const isMissingExistsError = (error) => {
489
+ if (typeof error !== "object" || error === null || Reflect.get(error, "name") !== "StorageUnknownError") return false;
490
+ const originalError = Reflect.get(error, "originalError");
491
+ if (typeof originalError !== "object" || originalError === null) return false;
492
+ const status = Reflect.get(originalError, "status");
493
+ return status === 400 || status === 404;
494
+ };
495
+ const supabaseStorage = (config) => {
496
+ const bucket = (0, _supabase_supabase_js.createClient)(config.supabaseUrl, resolveSupabaseServiceRoleKey(config)).storage.from(config.bucketName);
497
+ const getStorageKey = (0, _hot_updater_plugin_core.createStorageKeyBuilder)(config.basePath);
498
+ const resolveSignedUrl = createSupabaseSignedUrlBatcher({
499
+ createSignedUrls: (_bucketName, keys, expiresIn) => bucket.createSignedUrls(keys, expiresIn),
500
+ expiresIn: config.signedUrlExpiresIn ?? 3600,
501
+ formatObjectPath: (_bucketName, key) => key
502
+ });
503
+ const parseAndValidate = (storageUri) => {
504
+ const parsed = (0, _hot_updater_plugin_core.parseStorageUri)(storageUri, "supabase-storage");
505
+ if (parsed.bucket !== config.bucketName) throw new Error(`Bucket name mismatch: expected "${config.bucketName}", but found "${parsed.bucket}".`);
506
+ return parsed;
507
+ };
508
+ return (0, _hot_updater_plugin_core.createStoragePlugin)({
509
+ name: "supabaseStorage",
510
+ protocol: "supabase-storage",
511
+ async put({ key, body, contentLength, contentType }) {
512
+ const storageKey = getStorageKey(key);
513
+ const { error } = await bucket.upload(storageKey, body, {
514
+ contentType,
515
+ cacheControl: "max-age=31536000",
516
+ duplex: "half",
517
+ ...contentLength === void 0 ? {} : { headers: { "content-length": String(contentLength) } }
518
+ });
519
+ if (error) throw error;
520
+ return { storageUri: (0, _hot_updater_plugin_core.createStorageUri)({
521
+ protocol: "supabase-storage",
522
+ bucket: config.bucketName,
523
+ key: storageKey
524
+ }) };
525
+ },
526
+ async get({ storageUri }) {
527
+ const { key } = parseAndValidate(storageUri);
528
+ const { data, error } = await bucket.download(key);
529
+ if (error) {
530
+ if (isNotFoundError(error)) return { response: null };
531
+ throw new Error(`Failed to download storage object: ${error.message}`);
532
+ }
533
+ return { response: data ? new Response(data) : null };
534
+ },
535
+ async getDownloadUrl({ storageUri }) {
536
+ const { key } = parseAndValidate(storageUri);
537
+ return { url: await resolveSignedUrl(config.bucketName, key) };
538
+ },
539
+ async exists({ storageUri }) {
540
+ const { key } = parseAndValidate(storageUri);
541
+ const { data, error } = await bucket.exists(key);
542
+ if (error) {
543
+ if (isMissingExistsError(error)) return { exists: false };
544
+ throw error;
545
+ }
546
+ return { exists: data };
547
+ },
548
+ async delete({ storageUri }) {
549
+ const { key } = parseAndValidate(storageUri);
550
+ const { error } = await bucket.remove([key]);
551
+ if (error && !isNotFoundError(error)) throw new Error(`Failed to delete storage object: ${error.message}`);
552
+ return { deleted: true };
553
+ }
554
+ });
555
+ };
556
+ //#endregion
557
+ Object.defineProperty(exports, "supabaseDatabase", {
558
+ enumerable: true,
559
+ get: function() {
560
+ return supabaseDatabase;
561
+ }
562
+ });
563
+ Object.defineProperty(exports, "supabaseStorage", {
564
+ enumerable: true,
565
+ get: function() {
566
+ return supabaseStorage;
567
+ }
568
+ });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hot-updater/supabase",
3
3
  "type": "module",
4
- "version": "0.36.7",
4
+ "version": "1.0.0-rc.0",
5
5
  "description": "React Native OTA solution for self-hosted",
6
6
  "main": "dist/index.cjs",
7
7
  "module": "dist/index.mjs",
@@ -17,6 +17,7 @@
17
17
  "require": "./dist/init/index.cjs"
18
18
  },
19
19
  "./edge": {
20
+ "types": "./dist/edge.d.cts",
20
21
  "import": "./dist/edge.mjs",
21
22
  "require": "./dist/edge.cjs"
22
23
  },
@@ -55,10 +56,10 @@
55
56
  "@supabase/supabase-js": "2.76.1",
56
57
  "hono": "4.12.34",
57
58
  "uuidv7": "^1.0.2",
58
- "@hot-updater/core": "0.36.7",
59
- "@hot-updater/cli-tools": "0.36.7",
60
- "@hot-updater/plugin-core": "0.36.7",
61
- "@hot-updater/server": "0.36.7"
59
+ "@hot-updater/core": "1.0.0-rc.0",
60
+ "@hot-updater/server": "1.0.0-rc.0",
61
+ "@hot-updater/cli-tools": "1.0.0-rc.0",
62
+ "@hot-updater/plugin-core": "1.0.0-rc.0"
62
63
  },
63
64
  "devDependencies": {
64
65
  "@electric-sql/pglite": "0.4.1",
@@ -68,10 +69,9 @@
68
69
  "execa": "9.5.2",
69
70
  "@types/node": "^20",
70
71
  "mime": "^4.0.4",
71
- "@hot-updater/js": "0.36.7",
72
- "@hot-updater/mock": "0.36.7",
73
- "@hot-updater/test-utils": "0.36.7",
74
- "@hot-updater/postgres": "0.36.7"
72
+ "@hot-updater/mock": "1.0.0-rc.0",
73
+ "@hot-updater/postgres": "1.0.0-rc.0",
74
+ "@hot-updater/test-utils": "1.0.0-rc.0"
75
75
  },
76
76
  "scripts": {
77
77
  "build": "tsdown",
@@ -1,44 +1,40 @@
1
1
  import "jsr:@supabase/functions-js/edge-runtime.d.ts";
2
2
  import { createHotUpdater } from "@hot-updater/server";
3
- import {
4
- supabaseEdgeFunctionDatabase,
5
- supabaseEdgeFunctionStorage,
6
- } from "@hot-updater/supabase";
3
+ import { supabaseDatabase, supabaseStorage } from "@hot-updater/supabase/edge";
7
4
  import { Hono } from "hono";
8
5
 
9
6
  declare global {
10
7
  var HotUpdater: {
8
+ BUCKET_NAME: string;
11
9
  FUNCTION_NAME: string;
12
10
  };
13
11
  }
14
12
 
15
13
  const functionName = HotUpdater.FUNCTION_NAME;
14
+ const bucketName = HotUpdater.BUCKET_NAME;
16
15
  const supabaseUrl = Deno.env.get("SUPABASE_URL") ?? "";
17
16
  const supabaseServiceRoleKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") ?? "";
18
17
  const functionBasePath = `/${functionName}`;
19
18
  const hotUpdaterBasePath = "/";
20
19
 
21
20
  const hotUpdater = createHotUpdater({
22
- database: supabaseEdgeFunctionDatabase({
21
+ database: supabaseDatabase({
23
22
  supabaseUrl,
24
23
  supabaseServiceRoleKey,
25
24
  }),
26
- storages: [
27
- supabaseEdgeFunctionStorage({
25
+ clientAccess: { type: "api-key" },
26
+ storage: [
27
+ supabaseStorage({
28
28
  supabaseUrl,
29
29
  supabaseServiceRoleKey,
30
+ bucketName,
30
31
  }),
31
32
  ],
32
- basePath: hotUpdaterBasePath,
33
- routes: {
34
- updateCheck: true,
35
- bundles: false,
36
- },
37
33
  });
38
34
 
39
35
  const app = new Hono().basePath(functionBasePath);
40
36
 
41
37
  app.get("/ping", (c) => c.text("pong"));
42
- app.mount(hotUpdaterBasePath, hotUpdater.handler);
38
+ app.mount(hotUpdaterBasePath, hotUpdater.handlers.client);
43
39
 
44
40
  Deno.serve(app.fetch);