@hot-updater/supabase 0.28.0 → 0.29.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.
- package/dist/edge.cjs +4 -0
- package/dist/edge.d.cts +2 -0
- package/dist/edge.d.mts +2 -0
- package/dist/edge.mjs +2 -0
- package/dist/iac/index.cjs +457 -499
- package/dist/iac/index.d.cts +4 -1
- package/dist/iac/{index.d.ts → index.d.mts} +4 -1
- package/dist/iac/{index.js → index.mjs} +420 -451
- package/dist/index.cjs +15 -109
- package/dist/index.d.cts +5 -4
- package/dist/index.d.mts +23 -0
- package/dist/index.mjs +50 -0
- package/dist/supabaseEdgeFunctionStorage-B-gM2rZx.cjs +192 -0
- package/dist/supabaseEdgeFunctionStorage-ByPGforO.d.mts +19 -0
- package/dist/supabaseEdgeFunctionStorage-CSPi2UB8.d.cts +19 -0
- package/dist/supabaseEdgeFunctionStorage-CVEY5QJO.mjs +174 -0
- package/package.json +22 -10
- package/supabase/edge-functions/index.ts +29 -317
- package/supabase/edge-functions/runtime.docker.integration.spec.ts +779 -0
- package/supabase/migrations/20260401000000_hot-updater_0.29.0.sql +503 -0
- package/dist/index.d.ts +0 -22
- package/dist/index.js +0 -145
package/package.json
CHANGED
|
@@ -1,23 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hot-updater/supabase",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.29.0",
|
|
5
5
|
"description": "React Native OTA solution for self-hosted",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
7
|
-
"module": "dist/index.
|
|
8
|
-
"types": "dist/index.d.
|
|
7
|
+
"module": "dist/index.mjs",
|
|
8
|
+
"types": "dist/index.d.cts",
|
|
9
9
|
"exports": {
|
|
10
10
|
".": {
|
|
11
|
-
"import": "./dist/index.
|
|
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.
|
|
20
|
-
"import": "./dist/iac/index.
|
|
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
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"@hot-updater/
|
|
48
|
+
"hono": "4.12.9",
|
|
49
|
+
"uuidv7": "^1.0.2",
|
|
50
|
+
"@hot-updater/core": "0.29.0",
|
|
51
|
+
"@hot-updater/plugin-core": "0.29.0",
|
|
52
|
+
"@hot-updater/cli-tools": "0.29.0",
|
|
53
|
+
"@hot-updater/server": "0.29.0"
|
|
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/
|
|
63
|
+
"@hot-updater/js": "0.29.0",
|
|
64
|
+
"@hot-updater/mock": "0.29.0",
|
|
65
|
+
"@hot-updater/test-utils": "0.29.0",
|
|
66
|
+
"@hot-updater/postgres": "0.29.0"
|
|
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 "
|
|
2
|
+
import { Hono } from "npm:hono";
|
|
3
|
+
import { createHotUpdater } from "@hot-updater/server/runtime";
|
|
3
4
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
} from "
|
|
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
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
|
|
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
|
-
|
|
320
|
-
|
|
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);
|