@hot-updater/supabase 0.28.0 → 0.29.1

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/package.json CHANGED
@@ -1,23 +1,27 @@
1
1
  {
2
2
  "name": "@hot-updater/supabase",
3
3
  "type": "module",
4
- "version": "0.28.0",
4
+ "version": "0.29.1",
5
5
  "description": "React Native OTA solution for self-hosted",
6
6
  "main": "dist/index.cjs",
7
- "module": "dist/index.js",
8
- "types": "dist/index.d.ts",
7
+ "module": "dist/index.mjs",
8
+ "types": "dist/index.d.cts",
9
9
  "exports": {
10
10
  ".": {
11
- "import": "./dist/index.js",
11
+ "import": "./dist/index.mjs",
12
12
  "require": "./dist/index.cjs"
13
13
  },
14
+ "./edge": {
15
+ "import": "./dist/edge.mjs",
16
+ "require": "./dist/edge.cjs"
17
+ },
14
18
  "./scaffold": {
15
19
  "import": "./supabase/index.ts",
16
20
  "require": "./supabase/index.ts"
17
21
  },
18
22
  "./iac": {
19
- "types": "./dist/iac/index.d.ts",
20
- "import": "./dist/iac/index.js",
23
+ "types": "./dist/iac/index.d.cts",
24
+ "import": "./dist/iac/index.mjs",
21
25
  "require": "./dist/iac/index.cjs"
22
26
  },
23
27
  "./package.json": "./package.json"
@@ -41,17 +45,25 @@
41
45
  ],
42
46
  "dependencies": {
43
47
  "@supabase/supabase-js": "^2.76.1",
44
- "@hot-updater/core": "0.28.0",
45
- "@hot-updater/plugin-core": "0.28.0",
46
- "@hot-updater/cli-tools": "0.28.0"
48
+ "hono": "4.12.9",
49
+ "uuidv7": "^1.0.2",
50
+ "@hot-updater/core": "0.29.1",
51
+ "@hot-updater/plugin-core": "0.29.1",
52
+ "@hot-updater/server": "0.29.1",
53
+ "@hot-updater/cli-tools": "0.29.1"
47
54
  },
48
55
  "devDependencies": {
56
+ "@electric-sql/pglite": "^0.2.15",
57
+ "camelcase-keys": "^9.1.3",
49
58
  "dayjs": "^1.11.13",
50
59
  "es-toolkit": "^1.32.0",
51
60
  "execa": "9.5.2",
52
61
  "@types/node": "^20",
53
62
  "mime": "^4.0.4",
54
- "@hot-updater/postgres": "0.28.0"
63
+ "@hot-updater/js": "0.29.1",
64
+ "@hot-updater/mock": "0.29.1",
65
+ "@hot-updater/test-utils": "0.29.1",
66
+ "@hot-updater/postgres": "0.29.1"
55
67
  },
56
68
  "scripts": {
57
69
  "build": "tsdown",
@@ -1,165 +1,10 @@
1
1
  import "jsr:@supabase/functions-js/edge-runtime.d.ts";
2
- import { Hono } from "jsr:@hono/hono";
2
+ import { Hono } from "npm:hono";
3
+ import { createHotUpdater } from "@hot-updater/server/runtime";
3
4
  import {
4
- createClient,
5
- type SupabaseClient,
6
- } from "jsr:@supabase/supabase-js@2.49.4";
7
- import semver from "npm:semver@7.7.1";
8
- import type { GetBundlesArgs, UpdateInfo } from "@hot-updater/core";
9
-
10
- const NIL_UUID = "00000000-0000-0000-0000-000000000000";
11
-
12
- const semverSatisfies = (targetAppVersion: string, currentVersion: string) => {
13
- const currentCoerce = semver.coerce(currentVersion);
14
- if (!currentCoerce) {
15
- return false;
16
- }
17
-
18
- return semver.satisfies(currentCoerce.version, targetAppVersion);
19
- };
20
-
21
- /**
22
- * Filters target app versions that are compatible with the current app version.
23
- * Returns only versions that are compatible with the current version according to semver rules.
24
- *
25
- * @param targetAppVersionList - List of target app versions to filter
26
- * @param currentVersion - Current app version
27
- * @returns Array of target app versions compatible with the current version
28
- */
29
- export const filterCompatibleAppVersions = (
30
- targetAppVersionList: string[],
31
- currentVersion: string,
32
- ) => {
33
- const compatibleAppVersionList = targetAppVersionList.filter((version) =>
34
- semverSatisfies(version, currentVersion),
35
- );
36
-
37
- return compatibleAppVersionList.sort((a, b) => b.localeCompare(a));
38
- };
39
-
40
- const appVersionStrategy = async (
41
- supabase: SupabaseClient<any, "public", any>,
42
- {
43
- appPlatform,
44
- minBundleId,
45
- bundleId,
46
- appVersion,
47
- channel,
48
- }: {
49
- appPlatform: string;
50
- minBundleId: string;
51
- bundleId: string;
52
- appVersion: string;
53
- channel: string;
54
- },
55
- ) => {
56
- const { data: appVersionList } = await supabase.rpc(
57
- "get_target_app_version_list",
58
- {
59
- app_platform: appPlatform,
60
- min_bundle_id: minBundleId || NIL_UUID,
61
- },
62
- );
63
- const compatibleAppVersionList = filterCompatibleAppVersions(
64
- appVersionList?.map((group) => group.target_app_version) ?? [],
65
- appVersion,
66
- );
67
-
68
- return supabase.rpc("get_update_info_by_app_version", {
69
- app_platform: appPlatform,
70
- app_version: appVersion,
71
- bundle_id: bundleId,
72
- min_bundle_id: minBundleId || NIL_UUID,
73
- target_channel: channel || "production",
74
- target_app_version_list: compatibleAppVersionList,
75
- });
76
- };
77
-
78
- const fingerprintHashStrategy = async (
79
- supabase: SupabaseClient<any, "public", any>,
80
- {
81
- appPlatform,
82
- minBundleId,
83
- bundleId,
84
- channel,
85
- fingerprintHash,
86
- }: {
87
- appPlatform: string;
88
- bundleId: string;
89
- minBundleId: string | null;
90
- channel: string | null;
91
- fingerprintHash: string;
92
- },
93
- ) => {
94
- return supabase.rpc("get_update_info_by_fingerprint_hash", {
95
- app_platform: appPlatform,
96
- bundle_id: bundleId,
97
- min_bundle_id: minBundleId || NIL_UUID,
98
- target_channel: channel || "production",
99
- target_fingerprint_hash: fingerprintHash,
100
- });
101
- };
102
-
103
- const handleUpdateRequest = async (
104
- supabase: SupabaseClient<any, "public", any>,
105
- updateConfig: GetBundlesArgs,
106
- ) => {
107
- const { data, error } =
108
- updateConfig._updateStrategy === "fingerprint"
109
- ? await fingerprintHashStrategy(supabase, {
110
- appPlatform: updateConfig.platform,
111
- minBundleId: updateConfig.minBundleId!,
112
- bundleId: updateConfig.bundleId,
113
- channel: updateConfig.channel!,
114
- fingerprintHash: updateConfig.fingerprintHash!,
115
- })
116
- : await appVersionStrategy(supabase, {
117
- appPlatform: updateConfig.platform,
118
- minBundleId: updateConfig.minBundleId!,
119
- bundleId: updateConfig.bundleId,
120
- appVersion: updateConfig.appVersion!,
121
- channel: updateConfig.channel!,
122
- });
123
-
124
- if (error) {
125
- throw error;
126
- }
127
-
128
- const storageUri = data[0]?.storage_uri;
129
- const response = data[0]
130
- ? ({
131
- id: data[0].id,
132
- shouldForceUpdate: data[0].should_force_update,
133
- message: data[0].message,
134
- status: data[0].status,
135
- fileHash: data[0].file_hash,
136
- } as UpdateInfo)
137
- : null;
138
-
139
- if (!response) {
140
- return null;
141
- }
142
-
143
- if (response.id === NIL_UUID) {
144
- return {
145
- ...response,
146
- fileUrl: null,
147
- };
148
- }
149
-
150
- const storageURL = new URL(storageUri);
151
- const storageBucket = storageURL.host;
152
- const storagePath = storageURL.pathname;
153
-
154
- const { data: signedUrlData } = await supabase.storage
155
- .from(storageBucket)
156
- .createSignedUrl(storagePath, 60);
157
-
158
- return {
159
- ...response,
160
- fileUrl: signedUrlData?.signedUrl ?? null,
161
- };
162
- };
5
+ supabaseEdgeFunctionDatabase,
6
+ supabaseEdgeFunctionStorage,
7
+ } from "@hot-updater/supabase";
163
8
 
164
9
  declare global {
165
10
  var HotUpdater: {
@@ -168,165 +13,32 @@ declare global {
168
13
  }
169
14
 
170
15
  const functionName = HotUpdater.FUNCTION_NAME;
171
- const app = new Hono().basePath(`/${functionName}`);
172
-
173
- app.get("/ping", (c) => c.text("pong"));
174
-
175
- app.get("/", async (c) => {
176
- try {
177
- const bundleId = c.req.header("x-bundle-id");
178
- const appPlatform = c.req.header("x-app-platform") as "ios" | "android";
179
- const appVersion = c.req.header("x-app-version");
180
- const fingerprintHash = c.req.header("x-fingerprint-hash");
181
- const minBundleId = c.req.header("x-min-bundle-id");
182
- const channel = c.req.header("x-channel");
183
-
184
- if (!appVersion && !fingerprintHash) {
185
- return c.json(
186
- {
187
- error:
188
- "Missing required headers (x-app-version or x-fingerprint-hash).",
189
- },
190
- 400,
191
- );
192
- }
193
-
194
- if (!bundleId || !appPlatform) {
195
- return c.json(
196
- { error: "Missing required headers (x-app-platform, x-bundle-id)." },
197
- 400,
198
- );
199
- }
200
-
201
- const supabase = createClient(
202
- Deno.env.get("SUPABASE_URL") ?? "",
203
- Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") ?? "",
204
- {
205
- auth: { autoRefreshToken: false, persistSession: false },
206
- },
207
- );
208
-
209
- const updateConfig = fingerprintHash
210
- ? ({
211
- platform: appPlatform,
212
- fingerprintHash,
213
- bundleId,
214
- minBundleId: minBundleId || NIL_UUID,
215
- channel: channel || "production",
216
- _updateStrategy: "fingerprint" as const,
217
- } satisfies GetBundlesArgs)
218
- : ({
219
- platform: appPlatform,
220
- appVersion: appVersion!,
221
- bundleId,
222
- minBundleId: minBundleId || NIL_UUID,
223
- channel: channel || "production",
224
- _updateStrategy: "appVersion" as const,
225
- } satisfies GetBundlesArgs);
226
-
227
- const result = await handleUpdateRequest(supabase, updateConfig);
228
- return c.json(result);
229
- } catch (err: unknown) {
230
- return c.json(
231
- {
232
- error: err instanceof Error ? err.message : "Unknown error",
233
- },
234
- 500,
235
- );
236
- }
237
- });
238
-
239
- app.get(
240
- "/app-version/:platform/:app-version/:channel/:minBundleId/:bundleId",
241
- async (c) => {
242
- try {
243
- const {
244
- platform,
245
- "app-version": appVersion,
246
- channel,
247
- minBundleId,
248
- bundleId,
249
- } = c.req.param();
250
-
251
- if (!bundleId || !platform) {
252
- return c.json(
253
- { error: "Missing required parameters (platform, bundleId)." },
254
- 400,
255
- );
256
- }
257
-
258
- const supabase = createClient(
259
- Deno.env.get("SUPABASE_URL") ?? "",
260
- Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") ?? "",
261
- {
262
- auth: { autoRefreshToken: false, persistSession: false },
263
- },
264
- );
265
-
266
- const updateConfig = {
267
- platform: platform as "ios" | "android",
268
- appVersion,
269
- bundleId,
270
- minBundleId: minBundleId || NIL_UUID,
271
- channel: channel || "production",
272
- _updateStrategy: "appVersion" as const,
273
- } satisfies GetBundlesArgs;
274
-
275
- const result = await handleUpdateRequest(supabase, updateConfig);
276
- return c.json(result);
277
- } catch (err: unknown) {
278
- return c.json(
279
- {
280
- error: err instanceof Error ? err.message : "Unknown error",
281
- },
282
- 500,
283
- );
284
- }
16
+ const supabaseUrl = Deno.env.get("SUPABASE_URL") ?? "";
17
+ const supabaseServiceRoleKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") ?? "";
18
+ const functionBasePath = `/${functionName}`;
19
+ const hotUpdaterBasePath = "/";
20
+
21
+ const hotUpdater = createHotUpdater({
22
+ database: supabaseEdgeFunctionDatabase({
23
+ supabaseUrl,
24
+ supabaseServiceRoleKey,
25
+ }),
26
+ storages: [
27
+ supabaseEdgeFunctionStorage({
28
+ supabaseUrl,
29
+ supabaseServiceRoleKey,
30
+ }),
31
+ ],
32
+ basePath: hotUpdaterBasePath,
33
+ routes: {
34
+ updateCheck: true,
35
+ bundles: false,
285
36
  },
286
- );
287
-
288
- app.get(
289
- "/fingerprint/:platform/:fingerprintHash/:channel/:minBundleId/:bundleId",
290
- async (c) => {
291
- try {
292
- const { platform, fingerprintHash, channel, minBundleId, bundleId } =
293
- c.req.param();
294
-
295
- if (!bundleId || !platform) {
296
- return c.json(
297
- { error: "Missing required parameters (platform, bundleId)." },
298
- 400,
299
- );
300
- }
301
-
302
- const supabase = createClient(
303
- Deno.env.get("SUPABASE_URL") ?? "",
304
- Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") ?? "",
305
- {
306
- auth: { autoRefreshToken: false, persistSession: false },
307
- },
308
- );
37
+ });
309
38
 
310
- const updateConfig = {
311
- platform: platform as "ios" | "android",
312
- fingerprintHash,
313
- bundleId,
314
- minBundleId: minBundleId || NIL_UUID,
315
- channel: channel || "production",
316
- _updateStrategy: "fingerprint" as const,
317
- } satisfies GetBundlesArgs;
39
+ const app = new Hono().basePath(functionBasePath);
318
40
 
319
- const result = await handleUpdateRequest(supabase, updateConfig);
320
- return c.json(result);
321
- } catch (err: unknown) {
322
- return c.json(
323
- {
324
- error: err instanceof Error ? err.message : "Unknown error",
325
- },
326
- 500,
327
- );
328
- }
329
- },
330
- );
41
+ app.get("/ping", (c) => c.text("pong"));
42
+ app.mount(hotUpdaterBasePath, hotUpdater.handler);
331
43
 
332
44
  Deno.serve(app.fetch);