@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
@@ -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 rlsMigrationPath = path.resolve(
7
- "plugins/supabase/supabase/migrations/20260520014100_hot-updater_rls.sql",
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
- describe("Supabase RLS migration", () => {
12
- it("runs after Supabase function redefinition migrations", async () => {
13
- const migrations = (await fs.readdir(migrationsDir))
14
- .filter((file) => file.endsWith(".sql"))
15
- .sort();
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
- expect(migrations.at(-1)).toBe(path.basename(rlsMigrationPath));
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("enables RLS on Hot Updater tables", async () => {
21
- const sql = await fs.readFile(rlsMigrationPath, "utf8");
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
- "ALTER TABLE public.bundles ENABLE ROW LEVEL SECURITY;",
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.bundle_patches ENABLE ROW LEVEL SECURITY;",
46
+ "ALTER TABLE public.hot_updater_v1_bundles ENABLE ROW LEVEL SECURITY",
28
47
  );
29
- expect(sql).not.toContain("REVOKE ALL ON TABLE");
30
- expect(sql).not.toContain("GRANT SELECT, INSERT, UPDATE, DELETE");
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("does not change function execution grants", async () => {
62
- const sql = await fs.readFile(rlsMigrationPath, "utf8");
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
- expect(sql).not.toContain("REVOKE EXECUTE");
65
- expect(sql).not.toContain("GRANT EXECUTE");
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 };