@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.
- package/dist/edge.cjs +6 -3
- package/dist/edge.d.cts +14 -2
- package/dist/edge.d.mts +14 -2
- package/dist/edge.mjs +5 -2
- package/dist/iac/index.cjs +607 -50
- package/dist/iac/index.d.cts +26 -8
- package/dist/iac/index.d.mts +26 -8
- package/dist/iac/index.mjs +604 -52
- package/dist/index.cjs +3 -145
- package/dist/index.d.cts +7 -24
- package/dist/index.d.mts +7 -24
- package/dist/index.mjs +2 -117
- package/dist/init/index.cjs +5 -2
- package/dist/init/index.d.cts +2 -2
- package/dist/init/index.d.mts +2 -2
- package/dist/init/index.mjs +5 -2
- package/dist/supabaseDatabase-BDIBsXpz.d.cts +13 -0
- package/dist/supabaseDatabase-CP9WalBi.d.mts +13 -0
- package/dist/supabaseStorage-Cw294tR4.mjs +557 -0
- package/dist/supabaseStorage-yP7u4m7E.cjs +568 -0
- package/package.json +9 -9
- package/supabase/edge-functions/index.ts +9 -13
- package/supabase/edge-functions/runtime.docker.integration.spec.ts +417 -202
- package/supabase/migrations/20260818000000_hot-updater_1.0.0.sql +583 -0
- package/supabase/migrations.spec.ts +127 -46
- package/dist/supabaseEdgeFunctionStorage-BRxGvt-r.d.mts +0 -19
- package/dist/supabaseEdgeFunctionStorage-BSLlUeqi.cjs +0 -386
- package/dist/supabaseEdgeFunctionStorage-CU396KO3.d.cts +0 -19
- package/dist/supabaseEdgeFunctionStorage-pV0s_mTF.mjs +0 -356
- package/supabase/migrations/20250103114225_init.sql +0 -228
- package/supabase/migrations/20250314000000_hot-updater_0.13.0.sql +0 -134
- package/supabase/migrations/20250516000000_hot-updater_0.18.0.sql +0 -194
- package/supabase/migrations/20251014000000_hot-updater_0.21.0.sql +0 -184
- package/supabase/migrations/20260401000000_hot-updater_0.29.0.sql +0 -503
- package/supabase/migrations/20260414000000_hot-updater_0.30.0.sql +0 -50
- package/supabase/migrations/20260422000000_hot-updater_0.31.0.sql +0 -20
- package/supabase/migrations/20260520014100_hot-updater_rls.sql +0 -48
|
@@ -1,67 +1,148 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
|
|
4
|
+
import { PGlite } from "@electric-sql/pglite";
|
|
4
5
|
import { describe, expect, it } from "vitest";
|
|
5
6
|
|
|
6
|
-
const
|
|
7
|
-
"plugins/supabase/supabase/migrations/
|
|
7
|
+
const migrationPath = path.resolve(
|
|
8
|
+
"plugins/supabase/supabase/migrations/20260818000000_hot-updater_1.0.0.sql",
|
|
8
9
|
);
|
|
9
|
-
const migrationsDir = path.dirname(rlsMigrationPath);
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
const readMigrations = async () => {
|
|
12
|
+
const migrationDirectory = path.resolve(
|
|
13
|
+
"plugins/supabase/supabase/migrations",
|
|
14
|
+
);
|
|
15
|
+
const migrationFiles = (await fs.readdir(migrationDirectory))
|
|
16
|
+
.filter((file) => file.endsWith(".sql"))
|
|
17
|
+
.sort();
|
|
18
|
+
return Promise.all(
|
|
19
|
+
migrationFiles.map(async (file) => ({
|
|
20
|
+
file,
|
|
21
|
+
sql: await fs.readFile(path.join(migrationDirectory, file), "utf8"),
|
|
22
|
+
})),
|
|
23
|
+
);
|
|
24
|
+
};
|
|
16
25
|
|
|
17
|
-
|
|
26
|
+
describe("Supabase v1 schema", () => {
|
|
27
|
+
it("ships a single 1.0.0 CREATE migration", async () => {
|
|
28
|
+
const migrations = await readMigrations();
|
|
29
|
+
expect(migrations.map(({ file }) => file)).toEqual([
|
|
30
|
+
"20260818000000_hot-updater_1.0.0.sql",
|
|
31
|
+
]);
|
|
18
32
|
});
|
|
19
33
|
|
|
20
|
-
it("
|
|
21
|
-
const sql = await fs.readFile(
|
|
34
|
+
it("creates namespaced tables, RLS, and functions", async () => {
|
|
35
|
+
const sql = await fs.readFile(migrationPath, "utf8");
|
|
22
36
|
|
|
37
|
+
expect(sql).toContain("CREATE TABLE public.hot_updater_v1_channels");
|
|
38
|
+
expect(sql).toContain("CREATE TABLE public.hot_updater_v1_bundles");
|
|
39
|
+
expect(sql).toContain("CREATE TABLE public.hot_updater_v1_releases");
|
|
23
40
|
expect(sql).toContain(
|
|
24
|
-
"
|
|
41
|
+
"CREATE TABLE public.hot_updater_v1_release_catalogs",
|
|
25
42
|
);
|
|
43
|
+
expect(sql).toContain("CREATE TABLE public.hot_updater_v1_bundle_events");
|
|
44
|
+
expect(sql).toContain("CREATE TABLE public.hot_updater_v1_api_keys");
|
|
26
45
|
expect(sql).toContain(
|
|
27
|
-
"ALTER TABLE public.
|
|
46
|
+
"ALTER TABLE public.hot_updater_v1_bundles ENABLE ROW LEVEL SECURITY",
|
|
28
47
|
);
|
|
29
|
-
expect(sql).
|
|
30
|
-
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it("pins search_path for public Hot Updater functions", async () => {
|
|
34
|
-
const sql = await fs.readFile(rlsMigrationPath, "utf8");
|
|
35
|
-
const hotUpdaterFunctions = [
|
|
36
|
-
"get_target_app_version_list",
|
|
37
|
-
"get_channels",
|
|
38
|
-
"positive_mod",
|
|
39
|
-
"hash_rollout_value",
|
|
40
|
-
"normalize_cohort_value",
|
|
41
|
-
"gcd_int",
|
|
42
|
-
"get_rollout_multiplier",
|
|
43
|
-
"get_rollout_offset",
|
|
44
|
-
"get_modular_inverse",
|
|
45
|
-
"is_numeric_cohort",
|
|
46
|
-
"get_numeric_cohort_rollout_position",
|
|
47
|
-
"is_cohort_eligible",
|
|
48
|
-
"get_update_info_by_fingerprint_hash",
|
|
49
|
-
"get_update_info_by_app_version",
|
|
50
|
-
];
|
|
51
|
-
|
|
52
|
-
for (const functionName of hotUpdaterFunctions) {
|
|
53
|
-
expect(sql).toContain(`ALTER FUNCTION public.${functionName}`);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
expect(sql.match(/SET search_path = public, pg_catalog;/g)).toHaveLength(
|
|
57
|
-
hotUpdaterFunctions.length,
|
|
48
|
+
expect(sql).toContain(
|
|
49
|
+
"CREATE FUNCTION public.hot_updater_v1_commit(p_commit jsonb)",
|
|
58
50
|
);
|
|
51
|
+
expect(sql).toContain(
|
|
52
|
+
"CREATE FUNCTION public.hot_updater_v1_delete_channel(p_id text)",
|
|
53
|
+
);
|
|
54
|
+
expect(sql).toContain("REVOKE EXECUTE ON FUNCTION");
|
|
55
|
+
expect(sql).toContain("TO service_role;");
|
|
56
|
+
expect(sql).toContain("archive_byte_size double precision NOT NULL CHECK");
|
|
57
|
+
expect(sql).toContain("byte_size double precision NOT NULL CHECK");
|
|
58
|
+
expect(sql).toContain("archive_byte_size = v_bundle.archive_byte_size");
|
|
59
|
+
expect(sql).toContain(
|
|
60
|
+
"patch_file_hash, patch_storage_uri, byte_size, order_index",
|
|
61
|
+
);
|
|
62
|
+
expect(sql).toContain("NOTIFY pgrst, 'reload schema'");
|
|
63
|
+
expect(sql).not.toContain("get_update_info");
|
|
64
|
+
expect(sql).not.toContain("ALTER TABLE public.bundles ADD COLUMN");
|
|
59
65
|
});
|
|
60
66
|
|
|
61
|
-
it("
|
|
62
|
-
const
|
|
67
|
+
it("applies beside a v0 schema without modifying v0 data", async () => {
|
|
68
|
+
const database = new PGlite();
|
|
69
|
+
try {
|
|
70
|
+
await database.exec(
|
|
71
|
+
"CREATE ROLE anon; CREATE ROLE authenticated; CREATE ROLE service_role;",
|
|
72
|
+
);
|
|
73
|
+
await database.exec(`
|
|
74
|
+
CREATE TABLE public.channels (id text PRIMARY KEY, name text NOT NULL);
|
|
75
|
+
CREATE TABLE public.bundles (
|
|
76
|
+
id uuid PRIMARY KEY,
|
|
77
|
+
target_app_version text NOT NULL
|
|
78
|
+
);
|
|
79
|
+
INSERT INTO public.channels (id, name)
|
|
80
|
+
VALUES ('legacy-channel', 'legacy');
|
|
81
|
+
INSERT INTO public.bundles (id, target_app_version)
|
|
82
|
+
VALUES ('00000000-0000-0000-0000-000000000099', '0.85.0');
|
|
83
|
+
`);
|
|
84
|
+
for (const migration of await readMigrations()) {
|
|
85
|
+
await database.exec(migration.sql);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const tables = await database.query<{ tablename: string }>(`
|
|
89
|
+
select tablename from pg_tables
|
|
90
|
+
where schemaname = 'public'
|
|
91
|
+
order by tablename
|
|
92
|
+
`);
|
|
93
|
+
expect(tables.rows.map(({ tablename }) => tablename)).toEqual(
|
|
94
|
+
expect.arrayContaining([
|
|
95
|
+
"hot_updater_v1_bundles",
|
|
96
|
+
"hot_updater_v1_bundle_events",
|
|
97
|
+
"hot_updater_v1_bundle_patches",
|
|
98
|
+
"hot_updater_v1_channels",
|
|
99
|
+
"hot_updater_v1_api_keys",
|
|
100
|
+
"hot_updater_v1_private_settings",
|
|
101
|
+
"hot_updater_v1_release_catalogs",
|
|
102
|
+
"hot_updater_v1_releases",
|
|
103
|
+
]),
|
|
104
|
+
);
|
|
63
105
|
|
|
64
|
-
|
|
65
|
-
|
|
106
|
+
await database.exec(`
|
|
107
|
+
INSERT INTO public.hot_updater_v1_channels (id, name)
|
|
108
|
+
VALUES ('channel-1', 'production');
|
|
109
|
+
INSERT INTO public.hot_updater_v1_bundles (
|
|
110
|
+
id, platform, file_hash, storage_uri, archive_byte_size, metadata
|
|
111
|
+
) VALUES (
|
|
112
|
+
'00000000-0000-0000-0000-000000000001', 'ios', 'hash',
|
|
113
|
+
'storage://bundle', 3000000001, '{}'::jsonb
|
|
114
|
+
);
|
|
115
|
+
INSERT INTO public.hot_updater_v1_bundle_patches (
|
|
116
|
+
id, bundle_id, base_bundle_id, base_file_hash, patch_file_hash,
|
|
117
|
+
patch_storage_uri, byte_size
|
|
118
|
+
) VALUES (
|
|
119
|
+
'patch-1', '00000000-0000-0000-0000-000000000001',
|
|
120
|
+
'00000000-0000-0000-0000-000000000001', 'base-hash', 'patch-hash',
|
|
121
|
+
'storage://patch', 3000000002
|
|
122
|
+
);
|
|
123
|
+
`);
|
|
124
|
+
const channels = await database.query<{ name: string }>(
|
|
125
|
+
"SELECT name FROM public.hot_updater_v1_channels",
|
|
126
|
+
);
|
|
127
|
+
expect(channels.rows).toEqual([{ name: "production" }]);
|
|
128
|
+
const legacyChannels = await database.query<{ name: string }>(
|
|
129
|
+
"SELECT name FROM public.channels",
|
|
130
|
+
);
|
|
131
|
+
expect(legacyChannels.rows).toEqual([{ name: "legacy" }]);
|
|
132
|
+
const sizes = await database.query<{
|
|
133
|
+
archive_byte_size: number;
|
|
134
|
+
byte_size: number;
|
|
135
|
+
}>(`
|
|
136
|
+
SELECT bundle.archive_byte_size, patch.byte_size
|
|
137
|
+
FROM public.hot_updater_v1_bundles AS bundle
|
|
138
|
+
JOIN public.hot_updater_v1_bundle_patches AS patch
|
|
139
|
+
ON patch.bundle_id = bundle.id
|
|
140
|
+
`);
|
|
141
|
+
expect(sizes.rows).toEqual([
|
|
142
|
+
{ archive_byte_size: 3_000_000_001, byte_size: 3_000_000_002 },
|
|
143
|
+
]);
|
|
144
|
+
} finally {
|
|
145
|
+
await database.close();
|
|
146
|
+
}
|
|
66
147
|
});
|
|
67
148
|
});
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import * as _$_hot_updater_plugin_core0 from "@hot-updater/plugin-core";
|
|
2
|
-
import { DatabasePluginHooks } from "@hot-updater/plugin-core";
|
|
3
|
-
|
|
4
|
-
//#region src/supabaseEdgeFunctionDatabase.d.ts
|
|
5
|
-
interface SupabaseEdgeFunctionDatabaseConfig {
|
|
6
|
-
supabaseUrl: string;
|
|
7
|
-
supabaseServiceRoleKey: string;
|
|
8
|
-
}
|
|
9
|
-
declare const supabaseEdgeFunctionDatabase: (config: SupabaseEdgeFunctionDatabaseConfig, hooks?: DatabasePluginHooks) => () => _$_hot_updater_plugin_core0.DatabasePlugin<unknown>;
|
|
10
|
-
//#endregion
|
|
11
|
-
//#region src/supabaseEdgeFunctionStorage.d.ts
|
|
12
|
-
interface SupabaseEdgeFunctionStorageConfig {
|
|
13
|
-
supabaseUrl: string;
|
|
14
|
-
supabaseServiceRoleKey: string;
|
|
15
|
-
signedUrlExpiresIn?: number;
|
|
16
|
-
}
|
|
17
|
-
declare const supabaseEdgeFunctionStorage: (config: SupabaseEdgeFunctionStorageConfig, hooks?: _$_hot_updater_plugin_core0.StoragePluginHooks) => () => _$_hot_updater_plugin_core0.RuntimeStoragePlugin<unknown>;
|
|
18
|
-
//#endregion
|
|
19
|
-
export { supabaseEdgeFunctionDatabase as i, supabaseEdgeFunctionStorage as n, SupabaseEdgeFunctionDatabaseConfig as r, SupabaseEdgeFunctionStorageConfig as t };
|
|
@@ -1,386 +0,0 @@
|
|
|
1
|
-
require("./index.cjs");
|
|
2
|
-
let _hot_updater_core = require("@hot-updater/core");
|
|
3
|
-
let _hot_updater_plugin_core = require("@hot-updater/plugin-core");
|
|
4
|
-
let _supabase_supabase_js = require("@supabase/supabase-js");
|
|
5
|
-
//#region src/supabaseBundleMapper.ts
|
|
6
|
-
const BUNDLE_SELECT_COLUMNS = "id, channel, enabled, platform, should_force_update, file_hash, git_commit_hash, message, fingerprint_hash, target_app_version, storage_uri, metadata, manifest_storage_uri, manifest_file_hash, asset_base_storage_uri, rollout_cohort_count, target_cohorts";
|
|
7
|
-
const normalizeMetadata = (value) => {
|
|
8
|
-
if (!value) return {};
|
|
9
|
-
if (typeof value === "string") try {
|
|
10
|
-
return normalizeMetadata(JSON.parse(value));
|
|
11
|
-
} catch {
|
|
12
|
-
return {};
|
|
13
|
-
}
|
|
14
|
-
if (typeof value === "object" && !Array.isArray(value)) return value;
|
|
15
|
-
return {};
|
|
16
|
-
};
|
|
17
|
-
const buildBundlePatchId = (bundleId, baseBundleId) => `${bundleId}:${baseBundleId}`;
|
|
18
|
-
const mapRowToBundle = (row, patchRows = []) => {
|
|
19
|
-
const rawMetadata = normalizeMetadata(row.metadata);
|
|
20
|
-
const patches = patchRows.slice().sort((left, right) => left.order_index - right.order_index || left.base_bundle_id.localeCompare(right.base_bundle_id)).map((patch) => ({
|
|
21
|
-
baseBundleId: patch.base_bundle_id,
|
|
22
|
-
baseFileHash: patch.base_file_hash,
|
|
23
|
-
patchFileHash: patch.patch_file_hash,
|
|
24
|
-
patchStorageUri: patch.patch_storage_uri
|
|
25
|
-
}));
|
|
26
|
-
const primaryPatch = patches[0] ?? null;
|
|
27
|
-
return {
|
|
28
|
-
channel: row.channel,
|
|
29
|
-
enabled: Boolean(row.enabled),
|
|
30
|
-
shouldForceUpdate: Boolean(row.should_force_update),
|
|
31
|
-
fileHash: row.file_hash,
|
|
32
|
-
gitCommitHash: row.git_commit_hash,
|
|
33
|
-
id: row.id,
|
|
34
|
-
message: row.message,
|
|
35
|
-
platform: row.platform,
|
|
36
|
-
targetAppVersion: row.target_app_version,
|
|
37
|
-
fingerprintHash: row.fingerprint_hash,
|
|
38
|
-
storageUri: row.storage_uri,
|
|
39
|
-
metadata: (0, _hot_updater_core.stripBundleArtifactMetadata)(rawMetadata),
|
|
40
|
-
manifestStorageUri: row.manifest_storage_uri ?? null,
|
|
41
|
-
manifestFileHash: row.manifest_file_hash ?? null,
|
|
42
|
-
assetBaseStorageUri: row.asset_base_storage_uri ?? null,
|
|
43
|
-
patches,
|
|
44
|
-
patchBaseBundleId: primaryPatch?.baseBundleId ?? null,
|
|
45
|
-
patchBaseFileHash: primaryPatch?.baseFileHash ?? null,
|
|
46
|
-
patchFileHash: primaryPatch?.patchFileHash ?? null,
|
|
47
|
-
patchStorageUri: primaryPatch?.patchStorageUri ?? null,
|
|
48
|
-
rolloutCohortCount: row.rollout_cohort_count ?? _hot_updater_core.DEFAULT_ROLLOUT_COHORT_COUNT,
|
|
49
|
-
targetCohorts: row.target_cohorts ?? null
|
|
50
|
-
};
|
|
51
|
-
};
|
|
52
|
-
const bundleToRow = (bundle) => ({
|
|
53
|
-
id: bundle.id,
|
|
54
|
-
channel: bundle.channel,
|
|
55
|
-
enabled: bundle.enabled,
|
|
56
|
-
should_force_update: bundle.shouldForceUpdate,
|
|
57
|
-
file_hash: bundle.fileHash,
|
|
58
|
-
git_commit_hash: bundle.gitCommitHash,
|
|
59
|
-
message: bundle.message,
|
|
60
|
-
platform: bundle.platform,
|
|
61
|
-
target_app_version: bundle.targetAppVersion,
|
|
62
|
-
fingerprint_hash: bundle.fingerprintHash,
|
|
63
|
-
storage_uri: bundle.storageUri,
|
|
64
|
-
metadata: (0, _hot_updater_core.stripBundleArtifactMetadata)(bundle.metadata) ?? {},
|
|
65
|
-
manifest_storage_uri: (0, _hot_updater_core.getManifestStorageUri)(bundle),
|
|
66
|
-
manifest_file_hash: (0, _hot_updater_core.getManifestFileHash)(bundle),
|
|
67
|
-
asset_base_storage_uri: (0, _hot_updater_core.getAssetBaseStorageUri)(bundle),
|
|
68
|
-
rollout_cohort_count: bundle.rolloutCohortCount ?? _hot_updater_core.DEFAULT_ROLLOUT_COHORT_COUNT,
|
|
69
|
-
target_cohorts: bundle.targetCohorts ?? null
|
|
70
|
-
});
|
|
71
|
-
const bundleToPatchRows = (bundle) => (0, _hot_updater_core.getBundlePatches)(bundle).map((patch, index) => ({
|
|
72
|
-
id: buildBundlePatchId(bundle.id, patch.baseBundleId),
|
|
73
|
-
bundle_id: bundle.id,
|
|
74
|
-
base_bundle_id: patch.baseBundleId,
|
|
75
|
-
base_file_hash: patch.baseFileHash,
|
|
76
|
-
patch_file_hash: patch.patchFileHash,
|
|
77
|
-
patch_storage_uri: patch.patchStorageUri,
|
|
78
|
-
order_index: index
|
|
79
|
-
}));
|
|
80
|
-
//#endregion
|
|
81
|
-
//#region src/supabaseConfig.ts
|
|
82
|
-
const resolveSupabaseServiceRoleKey = (config) => {
|
|
83
|
-
const key = config.supabaseServiceRoleKey ?? config.supabaseAnonKey;
|
|
84
|
-
if (!key) throw new Error("Supabase service role key is required. Set supabaseServiceRoleKey.");
|
|
85
|
-
return key;
|
|
86
|
-
};
|
|
87
|
-
//#endregion
|
|
88
|
-
//#region src/supabaseDatabase.ts
|
|
89
|
-
const createSupabaseError = (error) => {
|
|
90
|
-
if (error instanceof Error) return error;
|
|
91
|
-
if (error && typeof error === "object") {
|
|
92
|
-
const properties = {};
|
|
93
|
-
let target = error;
|
|
94
|
-
while (target && target !== Object.prototype) {
|
|
95
|
-
for (const key of Object.getOwnPropertyNames(target)) properties[key] = error[key];
|
|
96
|
-
target = Object.getPrototypeOf(target);
|
|
97
|
-
}
|
|
98
|
-
return new Error(JSON.stringify({
|
|
99
|
-
name: error.constructor.name,
|
|
100
|
-
...properties
|
|
101
|
-
}));
|
|
102
|
-
}
|
|
103
|
-
try {
|
|
104
|
-
return new Error(JSON.stringify(error));
|
|
105
|
-
} catch {
|
|
106
|
-
return new Error(String(error));
|
|
107
|
-
}
|
|
108
|
-
};
|
|
109
|
-
const mapUpdateInfoRow = (row) => ({
|
|
110
|
-
id: row.id,
|
|
111
|
-
shouldForceUpdate: row.should_force_update,
|
|
112
|
-
message: row.message,
|
|
113
|
-
status: row.status,
|
|
114
|
-
storageUri: row.storage_uri,
|
|
115
|
-
fileHash: row.file_hash
|
|
116
|
-
});
|
|
117
|
-
const supabaseDatabase = (0, _hot_updater_plugin_core.createDatabasePlugin)({
|
|
118
|
-
name: "supabaseDatabase",
|
|
119
|
-
factory: (config) => {
|
|
120
|
-
const supabase = (0, _supabase_supabase_js.createClient)(config.supabaseUrl, resolveSupabaseServiceRoleKey(config));
|
|
121
|
-
const fetchPatchMap = async (bundleIds) => {
|
|
122
|
-
const patchMap = /* @__PURE__ */ new Map();
|
|
123
|
-
if (bundleIds.length === 0) return patchMap;
|
|
124
|
-
const { data, error } = await supabase.from("bundle_patches").select("*").in("bundle_id", bundleIds).order("order_index", { ascending: true });
|
|
125
|
-
if (error) throw createSupabaseError(error);
|
|
126
|
-
for (const row of data ?? []) {
|
|
127
|
-
const current = patchMap.get(row.bundle_id) ?? [];
|
|
128
|
-
current.push(row);
|
|
129
|
-
patchMap.set(row.bundle_id, current);
|
|
130
|
-
}
|
|
131
|
-
return patchMap;
|
|
132
|
-
};
|
|
133
|
-
return {
|
|
134
|
-
async getUpdateInfo(args) {
|
|
135
|
-
const channel = args.channel ?? "production";
|
|
136
|
-
const minBundleId = args.minBundleId ?? _hot_updater_core.NIL_UUID;
|
|
137
|
-
if (args._updateStrategy === "appVersion") {
|
|
138
|
-
const { data: targetAppVersionRows, error: targetAppVersionError } = await supabase.rpc("get_target_app_version_list", {
|
|
139
|
-
app_platform: args.platform,
|
|
140
|
-
min_bundle_id: minBundleId
|
|
141
|
-
});
|
|
142
|
-
if (targetAppVersionError) throw createSupabaseError(targetAppVersionError);
|
|
143
|
-
const targetAppVersionList = (0, _hot_updater_plugin_core.filterCompatibleAppVersions)((targetAppVersionRows ?? []).map((row) => row.target_app_version).filter((version) => Boolean(version)), args.appVersion);
|
|
144
|
-
const { data, error } = await supabase.rpc("get_update_info_by_app_version", {
|
|
145
|
-
app_platform: args.platform,
|
|
146
|
-
app_version: args.appVersion,
|
|
147
|
-
bundle_id: args.bundleId,
|
|
148
|
-
min_bundle_id: minBundleId,
|
|
149
|
-
target_channel: channel,
|
|
150
|
-
target_app_version_list: targetAppVersionList,
|
|
151
|
-
cohort: args.cohort ?? null
|
|
152
|
-
});
|
|
153
|
-
if (error) throw createSupabaseError(error);
|
|
154
|
-
const updateInfo = data?.[0] ?? null;
|
|
155
|
-
return updateInfo ? mapUpdateInfoRow(updateInfo) : null;
|
|
156
|
-
}
|
|
157
|
-
const { data, error } = await supabase.rpc("get_update_info_by_fingerprint_hash", {
|
|
158
|
-
app_platform: args.platform,
|
|
159
|
-
bundle_id: args.bundleId,
|
|
160
|
-
min_bundle_id: minBundleId,
|
|
161
|
-
target_channel: channel,
|
|
162
|
-
target_fingerprint_hash: args.fingerprintHash,
|
|
163
|
-
cohort: args.cohort ?? null
|
|
164
|
-
});
|
|
165
|
-
if (error) throw createSupabaseError(error);
|
|
166
|
-
const updateInfo = data?.[0] ?? null;
|
|
167
|
-
return updateInfo ? mapUpdateInfoRow(updateInfo) : null;
|
|
168
|
-
},
|
|
169
|
-
async getBundleById(bundleId) {
|
|
170
|
-
const [{ data, error }, patchMap] = await Promise.all([supabase.from("bundles").select(BUNDLE_SELECT_COLUMNS).eq("id", bundleId).single(), fetchPatchMap([bundleId])]);
|
|
171
|
-
if (!data || error) return null;
|
|
172
|
-
return mapRowToBundle(data, patchMap.get(bundleId) ?? []);
|
|
173
|
-
},
|
|
174
|
-
async getBundles(options) {
|
|
175
|
-
const { where, limit, orderBy } = options ?? {};
|
|
176
|
-
const offset = (options && "offset" in options ? options.offset : void 0) ?? 0;
|
|
177
|
-
if (where?.targetAppVersionIn && where.targetAppVersionIn.length === 0 || where?.id?.in && where.id.in.length === 0) return {
|
|
178
|
-
data: [],
|
|
179
|
-
pagination: (0, _hot_updater_plugin_core.calculatePagination)(0, {
|
|
180
|
-
limit,
|
|
181
|
-
offset
|
|
182
|
-
})
|
|
183
|
-
};
|
|
184
|
-
let countQuery = supabase.from("bundles").select("*", {
|
|
185
|
-
count: "exact",
|
|
186
|
-
head: true
|
|
187
|
-
});
|
|
188
|
-
if (where?.channel) countQuery = countQuery.eq("channel", where.channel);
|
|
189
|
-
if (where?.platform) countQuery = countQuery.eq("platform", where.platform);
|
|
190
|
-
if (where?.enabled !== void 0) countQuery = countQuery.eq("enabled", where.enabled);
|
|
191
|
-
if (where?.fingerprintHash !== void 0) countQuery = where.fingerprintHash === null ? countQuery.is("fingerprint_hash", null) : countQuery.eq("fingerprint_hash", where.fingerprintHash);
|
|
192
|
-
if (where?.targetAppVersion !== void 0) countQuery = where.targetAppVersion === null ? countQuery.is("target_app_version", null) : countQuery.eq("target_app_version", where.targetAppVersion);
|
|
193
|
-
if (where?.targetAppVersionIn) countQuery = countQuery.in("target_app_version", where.targetAppVersionIn);
|
|
194
|
-
if (where?.targetAppVersionNotNull) countQuery = countQuery.not("target_app_version", "is", null);
|
|
195
|
-
if (where?.id?.eq) countQuery = countQuery.eq("id", where.id.eq);
|
|
196
|
-
if (where?.id?.gt) countQuery = countQuery.gt("id", where.id.gt);
|
|
197
|
-
if (where?.id?.gte) countQuery = countQuery.gte("id", where.id.gte);
|
|
198
|
-
if (where?.id?.lt) countQuery = countQuery.lt("id", where.id.lt);
|
|
199
|
-
if (where?.id?.lte) countQuery = countQuery.lte("id", where.id.lte);
|
|
200
|
-
if (where?.id?.in) countQuery = countQuery.in("id", where.id.in);
|
|
201
|
-
const { count: total = 0 } = await countQuery;
|
|
202
|
-
let query = supabase.from("bundles").select(BUNDLE_SELECT_COLUMNS).order("id", { ascending: orderBy?.direction === "asc" });
|
|
203
|
-
if (where?.channel) query = query.eq("channel", where.channel);
|
|
204
|
-
if (where?.platform) query = query.eq("platform", where.platform);
|
|
205
|
-
if (where?.enabled !== void 0) query = query.eq("enabled", where.enabled);
|
|
206
|
-
if (where?.fingerprintHash !== void 0) query = where.fingerprintHash === null ? query.is("fingerprint_hash", null) : query.eq("fingerprint_hash", where.fingerprintHash);
|
|
207
|
-
if (where?.targetAppVersion !== void 0) query = where.targetAppVersion === null ? query.is("target_app_version", null) : query.eq("target_app_version", where.targetAppVersion);
|
|
208
|
-
if (where?.targetAppVersionIn) query = query.in("target_app_version", where.targetAppVersionIn);
|
|
209
|
-
if (where?.targetAppVersionNotNull) query = query.not("target_app_version", "is", null);
|
|
210
|
-
if (where?.id?.eq) query = query.eq("id", where.id.eq);
|
|
211
|
-
if (where?.id?.gt) query = query.gt("id", where.id.gt);
|
|
212
|
-
if (where?.id?.gte) query = query.gte("id", where.id.gte);
|
|
213
|
-
if (where?.id?.lt) query = query.lt("id", where.id.lt);
|
|
214
|
-
if (where?.id?.lte) query = query.lte("id", where.id.lte);
|
|
215
|
-
if (where?.id?.in) query = query.in("id", where.id.in);
|
|
216
|
-
if (limit) query = query.limit(limit);
|
|
217
|
-
if (offset) query = query.range(offset, offset + (limit || 20) - 1);
|
|
218
|
-
const { data } = await query;
|
|
219
|
-
const patchMap = await fetchPatchMap((data ?? []).map((bundle) => bundle.id));
|
|
220
|
-
return {
|
|
221
|
-
data: (data ?? []).map((bundle) => mapRowToBundle(bundle, patchMap.get(bundle.id) ?? [])),
|
|
222
|
-
pagination: (0, _hot_updater_plugin_core.calculatePagination)(total ?? 0, {
|
|
223
|
-
limit,
|
|
224
|
-
offset
|
|
225
|
-
})
|
|
226
|
-
};
|
|
227
|
-
},
|
|
228
|
-
async getChannels() {
|
|
229
|
-
const { data, error } = await supabase.rpc("get_channels");
|
|
230
|
-
if (error) throw error;
|
|
231
|
-
return data.map((bundle) => bundle.channel);
|
|
232
|
-
},
|
|
233
|
-
async commitBundle({ changedSets }) {
|
|
234
|
-
if (changedSets.length === 0) return;
|
|
235
|
-
for (const op of changedSets) if (op.operation === "delete") {
|
|
236
|
-
const { error: patchDeleteError } = await supabase.from("bundle_patches").delete().eq("bundle_id", op.data.id);
|
|
237
|
-
if (patchDeleteError) throw new Error(`Failed to delete bundle patches: ${patchDeleteError.message}`);
|
|
238
|
-
const { error: basePatchDeleteError } = await supabase.from("bundle_patches").delete().eq("base_bundle_id", op.data.id);
|
|
239
|
-
if (basePatchDeleteError) throw new Error(`Failed to delete base bundle patches: ${basePatchDeleteError.message}`);
|
|
240
|
-
const { error } = await supabase.from("bundles").delete().eq("id", op.data.id);
|
|
241
|
-
if (error) throw new Error(`Failed to delete bundle: ${error.message}`);
|
|
242
|
-
} else if (op.operation === "insert" || op.operation === "update") {
|
|
243
|
-
const bundle = op.data;
|
|
244
|
-
const patchRows = bundleToPatchRows(bundle);
|
|
245
|
-
const { error } = await supabase.from("bundles").upsert(bundleToRow(bundle), { onConflict: "id" });
|
|
246
|
-
if (error) throw error;
|
|
247
|
-
const { error: patchDeleteError } = await supabase.from("bundle_patches").delete().eq("bundle_id", bundle.id);
|
|
248
|
-
if (patchDeleteError) throw patchDeleteError;
|
|
249
|
-
if (patchRows.length > 0) {
|
|
250
|
-
const { error: patchInsertError } = await supabase.from("bundle_patches").upsert(patchRows, { onConflict: "id" });
|
|
251
|
-
if (patchInsertError) throw patchInsertError;
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
};
|
|
256
|
-
}
|
|
257
|
-
});
|
|
258
|
-
//#endregion
|
|
259
|
-
//#region src/supabaseEdgeFunctionDatabase.ts
|
|
260
|
-
const supabaseEdgeFunctionDatabase = (config, hooks) => {
|
|
261
|
-
return supabaseDatabase({
|
|
262
|
-
supabaseUrl: config.supabaseUrl,
|
|
263
|
-
supabaseServiceRoleKey: config.supabaseServiceRoleKey
|
|
264
|
-
}, hooks);
|
|
265
|
-
};
|
|
266
|
-
//#endregion
|
|
267
|
-
//#region src/supabaseSignedUrlBatcher.ts
|
|
268
|
-
function getErrorMessage(error) {
|
|
269
|
-
if (error instanceof Error) return error.message;
|
|
270
|
-
if (error && typeof error === "object" && "message" in error && typeof error.message === "string") return error.message;
|
|
271
|
-
return String(error);
|
|
272
|
-
}
|
|
273
|
-
const createSupabaseSignedUrlBatcher = ({ createSignedUrls, expiresIn, formatObjectPath }) => {
|
|
274
|
-
let pendingByBucket = /* @__PURE__ */ new Map();
|
|
275
|
-
let flushScheduled = false;
|
|
276
|
-
const createSignedUrlError = (bucketName, key, error) => /* @__PURE__ */ new Error(`Failed to generate download URL for "${formatObjectPath(bucketName, key)}": ${getErrorMessage(error)}`);
|
|
277
|
-
const flush = async () => {
|
|
278
|
-
const batches = pendingByBucket;
|
|
279
|
-
pendingByBucket = /* @__PURE__ */ new Map();
|
|
280
|
-
flushScheduled = false;
|
|
281
|
-
await Promise.all([...batches.entries()].map(async ([bucketName, pending]) => {
|
|
282
|
-
try {
|
|
283
|
-
const { data, error } = await createSignedUrls(bucketName, pending.map(({ key }) => key), expiresIn);
|
|
284
|
-
if (error || !data) {
|
|
285
|
-
const batchError = error ?? /* @__PURE__ */ new Error("missing signed URL response");
|
|
286
|
-
for (const request of pending) request.reject(createSignedUrlError(bucketName, request.key, batchError));
|
|
287
|
-
return;
|
|
288
|
-
}
|
|
289
|
-
pending.forEach((request, index) => {
|
|
290
|
-
const result = data[index];
|
|
291
|
-
if (!result?.error && result?.signedUrl) {
|
|
292
|
-
request.resolve(result.signedUrl);
|
|
293
|
-
return;
|
|
294
|
-
}
|
|
295
|
-
request.reject(createSignedUrlError(bucketName, request.key, result?.error ?? /* @__PURE__ */ new Error("missing signed URL")));
|
|
296
|
-
});
|
|
297
|
-
} catch (error) {
|
|
298
|
-
for (const request of pending) request.reject(createSignedUrlError(bucketName, request.key, error));
|
|
299
|
-
}
|
|
300
|
-
}));
|
|
301
|
-
};
|
|
302
|
-
return (bucketName, key) => new Promise((resolve, reject) => {
|
|
303
|
-
const pending = pendingByBucket.get(bucketName) ?? [];
|
|
304
|
-
pending.push({
|
|
305
|
-
key,
|
|
306
|
-
reject,
|
|
307
|
-
resolve
|
|
308
|
-
});
|
|
309
|
-
pendingByBucket.set(bucketName, pending);
|
|
310
|
-
if (!flushScheduled) {
|
|
311
|
-
flushScheduled = true;
|
|
312
|
-
queueMicrotask(() => {
|
|
313
|
-
flush();
|
|
314
|
-
});
|
|
315
|
-
}
|
|
316
|
-
});
|
|
317
|
-
};
|
|
318
|
-
//#endregion
|
|
319
|
-
//#region src/supabaseEdgeFunctionStorage.ts
|
|
320
|
-
const parseSupabaseStorageUri = (storageUri) => {
|
|
321
|
-
const { bucket: bucketName, key } = (0, _hot_updater_plugin_core.parseStorageUri)(storageUri, "supabase-storage");
|
|
322
|
-
if (!bucketName || !key) throw new Error("Invalid Supabase storage URI");
|
|
323
|
-
return {
|
|
324
|
-
bucketName,
|
|
325
|
-
key
|
|
326
|
-
};
|
|
327
|
-
};
|
|
328
|
-
const supabaseEdgeFunctionStorage = (0, _hot_updater_plugin_core.createRuntimeStoragePlugin)({
|
|
329
|
-
name: "supabaseEdgeFunctionStorage",
|
|
330
|
-
supportedProtocol: "supabase-storage",
|
|
331
|
-
factory: (config) => {
|
|
332
|
-
const supabase = (0, _supabase_supabase_js.createClient)(config.supabaseUrl, config.supabaseServiceRoleKey);
|
|
333
|
-
const resolveSignedUrl = createSupabaseSignedUrlBatcher({
|
|
334
|
-
createSignedUrls: (bucketName, keys, expiresIn) => supabase.storage.from(bucketName).createSignedUrls(keys, expiresIn),
|
|
335
|
-
expiresIn: config.signedUrlExpiresIn ?? 3600,
|
|
336
|
-
formatObjectPath: (bucketName, key) => `${bucketName}/${key}`
|
|
337
|
-
});
|
|
338
|
-
return {
|
|
339
|
-
async readText(storageUri) {
|
|
340
|
-
const { bucketName, key } = parseSupabaseStorageUri(storageUri);
|
|
341
|
-
const { data, error } = await supabase.storage.from(bucketName).download(key);
|
|
342
|
-
if (error) {
|
|
343
|
-
if (error.message?.includes("not found")) return null;
|
|
344
|
-
throw new Error(`Failed to read storage text: ${error.message}`);
|
|
345
|
-
}
|
|
346
|
-
if (!data) return null;
|
|
347
|
-
return data.text();
|
|
348
|
-
},
|
|
349
|
-
async getDownloadUrl(storageUri) {
|
|
350
|
-
const { bucketName, key } = parseSupabaseStorageUri(storageUri);
|
|
351
|
-
return { fileUrl: await resolveSignedUrl(bucketName, key) };
|
|
352
|
-
}
|
|
353
|
-
};
|
|
354
|
-
}
|
|
355
|
-
});
|
|
356
|
-
//#endregion
|
|
357
|
-
Object.defineProperty(exports, "createSupabaseSignedUrlBatcher", {
|
|
358
|
-
enumerable: true,
|
|
359
|
-
get: function() {
|
|
360
|
-
return createSupabaseSignedUrlBatcher;
|
|
361
|
-
}
|
|
362
|
-
});
|
|
363
|
-
Object.defineProperty(exports, "resolveSupabaseServiceRoleKey", {
|
|
364
|
-
enumerable: true,
|
|
365
|
-
get: function() {
|
|
366
|
-
return resolveSupabaseServiceRoleKey;
|
|
367
|
-
}
|
|
368
|
-
});
|
|
369
|
-
Object.defineProperty(exports, "supabaseDatabase", {
|
|
370
|
-
enumerable: true,
|
|
371
|
-
get: function() {
|
|
372
|
-
return supabaseDatabase;
|
|
373
|
-
}
|
|
374
|
-
});
|
|
375
|
-
Object.defineProperty(exports, "supabaseEdgeFunctionDatabase", {
|
|
376
|
-
enumerable: true,
|
|
377
|
-
get: function() {
|
|
378
|
-
return supabaseEdgeFunctionDatabase;
|
|
379
|
-
}
|
|
380
|
-
});
|
|
381
|
-
Object.defineProperty(exports, "supabaseEdgeFunctionStorage", {
|
|
382
|
-
enumerable: true,
|
|
383
|
-
get: function() {
|
|
384
|
-
return supabaseEdgeFunctionStorage;
|
|
385
|
-
}
|
|
386
|
-
});
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import * as _$_hot_updater_plugin_core0 from "@hot-updater/plugin-core";
|
|
2
|
-
import { DatabasePluginHooks } from "@hot-updater/plugin-core";
|
|
3
|
-
|
|
4
|
-
//#region src/supabaseEdgeFunctionDatabase.d.ts
|
|
5
|
-
interface SupabaseEdgeFunctionDatabaseConfig {
|
|
6
|
-
supabaseUrl: string;
|
|
7
|
-
supabaseServiceRoleKey: string;
|
|
8
|
-
}
|
|
9
|
-
declare const supabaseEdgeFunctionDatabase: (config: SupabaseEdgeFunctionDatabaseConfig, hooks?: DatabasePluginHooks) => () => _$_hot_updater_plugin_core0.DatabasePlugin<unknown>;
|
|
10
|
-
//#endregion
|
|
11
|
-
//#region src/supabaseEdgeFunctionStorage.d.ts
|
|
12
|
-
interface SupabaseEdgeFunctionStorageConfig {
|
|
13
|
-
supabaseUrl: string;
|
|
14
|
-
supabaseServiceRoleKey: string;
|
|
15
|
-
signedUrlExpiresIn?: number;
|
|
16
|
-
}
|
|
17
|
-
declare const supabaseEdgeFunctionStorage: (config: SupabaseEdgeFunctionStorageConfig, hooks?: _$_hot_updater_plugin_core0.StoragePluginHooks) => () => _$_hot_updater_plugin_core0.RuntimeStoragePlugin<unknown>;
|
|
18
|
-
//#endregion
|
|
19
|
-
export { supabaseEdgeFunctionDatabase as i, supabaseEdgeFunctionStorage as n, SupabaseEdgeFunctionDatabaseConfig as r, SupabaseEdgeFunctionStorageConfig as t };
|