@hot-updater/supabase 0.36.7 → 1.0.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/edge.cjs +6 -3
  2. package/dist/edge.d.cts +14 -2
  3. package/dist/edge.d.mts +14 -2
  4. package/dist/edge.mjs +5 -2
  5. package/dist/iac/index.cjs +607 -50
  6. package/dist/iac/index.d.cts +26 -8
  7. package/dist/iac/index.d.mts +26 -8
  8. package/dist/iac/index.mjs +604 -52
  9. package/dist/index.cjs +3 -145
  10. package/dist/index.d.cts +7 -24
  11. package/dist/index.d.mts +7 -24
  12. package/dist/index.mjs +2 -117
  13. package/dist/init/index.cjs +5 -2
  14. package/dist/init/index.d.cts +2 -2
  15. package/dist/init/index.d.mts +2 -2
  16. package/dist/init/index.mjs +5 -2
  17. package/dist/supabaseDatabase-BDIBsXpz.d.cts +13 -0
  18. package/dist/supabaseDatabase-CP9WalBi.d.mts +13 -0
  19. package/dist/supabaseStorage-Cw294tR4.mjs +557 -0
  20. package/dist/supabaseStorage-yP7u4m7E.cjs +568 -0
  21. package/package.json +9 -9
  22. package/supabase/edge-functions/index.ts +9 -13
  23. package/supabase/edge-functions/runtime.docker.integration.spec.ts +417 -202
  24. package/supabase/migrations/20260818000000_hot-updater_1.0.0.sql +583 -0
  25. package/supabase/migrations.spec.ts +127 -46
  26. package/dist/supabaseEdgeFunctionStorage-BRxGvt-r.d.mts +0 -19
  27. package/dist/supabaseEdgeFunctionStorage-BSLlUeqi.cjs +0 -386
  28. package/dist/supabaseEdgeFunctionStorage-CU396KO3.d.cts +0 -19
  29. package/dist/supabaseEdgeFunctionStorage-pV0s_mTF.mjs +0 -356
  30. package/supabase/migrations/20250103114225_init.sql +0 -228
  31. package/supabase/migrations/20250314000000_hot-updater_0.13.0.sql +0 -134
  32. package/supabase/migrations/20250516000000_hot-updater_0.18.0.sql +0 -194
  33. package/supabase/migrations/20251014000000_hot-updater_0.21.0.sql +0 -184
  34. package/supabase/migrations/20260401000000_hot-updater_0.29.0.sql +0 -503
  35. package/supabase/migrations/20260414000000_hot-updater_0.30.0.sql +0 -50
  36. package/supabase/migrations/20260422000000_hot-updater_0.31.0.sql +0 -20
  37. package/supabase/migrations/20260520014100_hot-updater_rls.sql +0 -48
@@ -0,0 +1,583 @@
1
+ -- HotUpdater.schema
2
+
3
+ CREATE TABLE public.hot_updater_v1_channels (
4
+ id text COLLATE "C" PRIMARY KEY NOT NULL
5
+ CHECK (pg_catalog.char_length(id) BETWEEN 1 AND 255),
6
+ name text COLLATE "C" NOT NULL UNIQUE
7
+ CHECK (pg_catalog.char_length(name) BETWEEN 1 AND 255)
8
+ );
9
+
10
+ CREATE TABLE public.hot_updater_v1_bundles (
11
+ id uuid PRIMARY KEY NOT NULL,
12
+ platform text NOT NULL,
13
+ file_hash text NOT NULL,
14
+ git_commit_hash text,
15
+ storage_uri text NOT NULL,
16
+ archive_byte_size double precision NOT NULL CHECK (
17
+ archive_byte_size BETWEEN 0 AND 9007199254740991
18
+ ),
19
+ metadata jsonb NOT NULL DEFAULT '{}'::jsonb,
20
+ manifest_storage_uri text,
21
+ manifest_file_hash text,
22
+ asset_base_storage_uri text
23
+ );
24
+
25
+ CREATE TABLE public.hot_updater_v1_bundle_patches (
26
+ id varchar(255) PRIMARY KEY NOT NULL,
27
+ bundle_id uuid NOT NULL,
28
+ base_bundle_id uuid NOT NULL,
29
+ base_file_hash text NOT NULL,
30
+ patch_file_hash text NOT NULL,
31
+ patch_storage_uri text NOT NULL,
32
+ byte_size double precision NOT NULL CHECK (
33
+ byte_size BETWEEN 0 AND 9007199254740991
34
+ ),
35
+ order_index integer NOT NULL DEFAULT 0,
36
+ CONSTRAINT hot_updater_v1_bundle_patches_bundle_id_fk FOREIGN KEY (bundle_id)
37
+ REFERENCES public.hot_updater_v1_bundles(id) ON UPDATE RESTRICT ON DELETE CASCADE,
38
+ CONSTRAINT hot_updater_v1_bundle_patches_base_bundle_id_fk FOREIGN KEY (base_bundle_id)
39
+ REFERENCES public.hot_updater_v1_bundles(id) ON UPDATE RESTRICT ON DELETE CASCADE
40
+ );
41
+
42
+ CREATE TABLE public.hot_updater_v1_releases (
43
+ id uuid PRIMARY KEY NOT NULL,
44
+ revision integer NOT NULL CHECK (revision >= 1),
45
+ scope_key varchar(2048) COLLATE "C" NOT NULL,
46
+ channel_id text COLLATE "C" NOT NULL,
47
+ platform text NOT NULL,
48
+ kind text NOT NULL CHECK (
49
+ (kind = 'BUNDLE' AND bundle_id IS NOT NULL)
50
+ OR (kind = 'EMBEDDED' AND bundle_id IS NULL)
51
+ ),
52
+ bundle_id uuid,
53
+ strategy text NOT NULL,
54
+ target_app_version text,
55
+ fingerprint_hash text,
56
+ enabled boolean NOT NULL,
57
+ should_force_update boolean NOT NULL,
58
+ message text,
59
+ rollout_cohort_count integer NOT NULL DEFAULT 1000
60
+ CHECK (rollout_cohort_count BETWEEN 0 AND 1000),
61
+ target_cohorts jsonb NOT NULL DEFAULT '[]'::jsonb,
62
+ operation text NOT NULL CHECK (operation IN ('DEPLOY', 'PROMOTE', 'ROLLBACK')),
63
+ source_release_id uuid,
64
+ created_at_ms double precision NOT NULL,
65
+ updated_at_ms double precision NOT NULL,
66
+ CONSTRAINT hot_updater_v1_releases_strategy_target_check CHECK (
67
+ (strategy = 'APP_VERSION' AND target_app_version IS NOT NULL AND fingerprint_hash IS NULL)
68
+ OR (strategy = 'FINGERPRINT' AND target_app_version IS NULL AND fingerprint_hash IS NOT NULL)
69
+ ),
70
+ CONSTRAINT hot_updater_v1_releases_channel_id_fk FOREIGN KEY (channel_id)
71
+ REFERENCES public.hot_updater_v1_channels(id) ON UPDATE RESTRICT ON DELETE RESTRICT,
72
+ CONSTRAINT hot_updater_v1_releases_bundle_id_fk FOREIGN KEY (bundle_id)
73
+ REFERENCES public.hot_updater_v1_bundles(id) ON UPDATE RESTRICT ON DELETE RESTRICT,
74
+ CONSTRAINT hot_updater_v1_releases_source_release_id_fk FOREIGN KEY (source_release_id)
75
+ REFERENCES public.hot_updater_v1_releases(id) ON UPDATE RESTRICT ON DELETE SET NULL
76
+ );
77
+
78
+ CREATE TABLE public.hot_updater_v1_release_catalogs (
79
+ scope_key varchar(2048) COLLATE "C" PRIMARY KEY NOT NULL,
80
+ catalog_id varchar(255) COLLATE "C" NOT NULL,
81
+ strategy text NOT NULL,
82
+ channel_id text COLLATE "C" NOT NULL,
83
+ channel_key varchar(1400) COLLATE "C" NOT NULL,
84
+ platform text NOT NULL,
85
+ fingerprint_hash text,
86
+ generation double precision NOT NULL
87
+ CHECK (generation BETWEEN 1 AND 9007199254740991),
88
+ payload text NOT NULL,
89
+ catalog_hash varchar(71) COLLATE "C" NOT NULL,
90
+ byte_size integer NOT NULL CHECK (byte_size BETWEEN 0 AND 262144),
91
+ is_tombstone boolean NOT NULL,
92
+ updated_at_ms double precision NOT NULL,
93
+ CONSTRAINT hot_updater_v1_release_catalogs_strategy_target_check CHECK (
94
+ (strategy = 'APP_VERSION' AND fingerprint_hash IS NULL)
95
+ OR (strategy = 'FINGERPRINT' AND fingerprint_hash IS NOT NULL)
96
+ ),
97
+ CONSTRAINT hot_updater_v1_release_catalogs_channel_id_fk FOREIGN KEY (channel_id)
98
+ REFERENCES public.hot_updater_v1_channels(id) ON UPDATE RESTRICT ON DELETE RESTRICT
99
+ );
100
+
101
+ CREATE TABLE public.hot_updater_v1_bundle_events (
102
+ id uuid PRIMARY KEY NOT NULL,
103
+ type text NOT NULL,
104
+ install_id text NOT NULL,
105
+ user_id text,
106
+ username text,
107
+ from_release_id uuid,
108
+ from_bundle_id uuid,
109
+ to_release_id uuid,
110
+ to_bundle_id uuid NOT NULL,
111
+ platform text NOT NULL,
112
+ app_version text NOT NULL,
113
+ channel text NOT NULL,
114
+ cohort text NOT NULL,
115
+ update_strategy text,
116
+ fingerprint_hash text,
117
+ sdk_version text,
118
+ received_at_ms double precision NOT NULL,
119
+ CONSTRAINT hot_updater_v1_bundle_events_type_check CHECK (
120
+ type IN ('UPDATE_APPLIED', 'RECOVERED', 'RELEASE_ADOPTED', 'UNCHANGED')
121
+ ),
122
+ CONSTRAINT hot_updater_v1_bundle_events_platform_check CHECK (
123
+ platform IN ('ios', 'android')
124
+ ),
125
+ CONSTRAINT hot_updater_v1_bundle_events_shape_check CHECK (
126
+ (type IN ('UPDATE_APPLIED', 'RECOVERED', 'RELEASE_ADOPTED')
127
+ AND from_bundle_id IS NOT NULL
128
+ AND update_strategy IS NOT NULL
129
+ AND update_strategy IN ('fingerprint', 'appVersion'))
130
+ OR (type = 'UNCHANGED'
131
+ AND from_bundle_id IS NULL
132
+ AND update_strategy IS NULL)
133
+ ),
134
+ CONSTRAINT hot_updater_v1_bundle_events_received_at_check CHECK (received_at_ms >= 0)
135
+ );
136
+
137
+ CREATE TABLE public.hot_updater_v1_api_keys (
138
+ id text PRIMARY KEY NOT NULL,
139
+ hash text NOT NULL,
140
+ name text NOT NULL,
141
+ prefix text NOT NULL,
142
+ role text NOT NULL CHECK (role = 'client'),
143
+ created_at_ms double precision NOT NULL CHECK (created_at_ms >= 0),
144
+ revoked_at_ms double precision CHECK (
145
+ revoked_at_ms IS NULL OR revoked_at_ms >= 0
146
+ )
147
+ );
148
+
149
+ CREATE TABLE public.hot_updater_v1_private_settings (
150
+ key varchar(255) PRIMARY KEY NOT NULL,
151
+ value text NOT NULL DEFAULT '1.0.0'
152
+ );
153
+
154
+ CREATE INDEX hot_updater_v1_releases_scope_order_idx ON public.hot_updater_v1_releases(scope_key, id);
155
+ CREATE INDEX hot_updater_v1_releases_channel_platform_order_idx
156
+ ON public.hot_updater_v1_releases(channel_id, platform, id);
157
+ CREATE INDEX hot_updater_v1_releases_bundle_id_idx ON public.hot_updater_v1_releases(bundle_id);
158
+ CREATE INDEX hot_updater_v1_releases_fingerprint_hash_idx ON public.hot_updater_v1_releases(fingerprint_hash);
159
+ CREATE INDEX hot_updater_v1_releases_enabled_idx ON public.hot_updater_v1_releases(enabled);
160
+ CREATE INDEX hot_updater_v1_release_catalogs_channel_idx ON public.hot_updater_v1_release_catalogs(channel_id);
161
+ CREATE INDEX hot_updater_v1_bundle_patches_bundle_id_idx ON public.hot_updater_v1_bundle_patches(bundle_id);
162
+ CREATE INDEX hot_updater_v1_bundle_patches_base_bundle_id_idx
163
+ ON public.hot_updater_v1_bundle_patches(base_bundle_id);
164
+ CREATE INDEX hot_updater_v1_bundle_events_received_at_idx
165
+ ON public.hot_updater_v1_bundle_events(received_at_ms, id);
166
+ CREATE INDEX hot_updater_v1_bundle_events_install_idx
167
+ ON public.hot_updater_v1_bundle_events(install_id, received_at_ms, id);
168
+ CREATE INDEX hot_updater_v1_bundle_events_user_id_idx
169
+ ON public.hot_updater_v1_bundle_events(user_id, received_at_ms, id);
170
+ CREATE INDEX hot_updater_v1_bundle_events_username_idx
171
+ ON public.hot_updater_v1_bundle_events(username, received_at_ms, id);
172
+ CREATE INDEX hot_updater_v1_bundle_events_to_bundle_idx
173
+ ON public.hot_updater_v1_bundle_events(type, to_bundle_id, received_at_ms, id);
174
+ CREATE INDEX hot_updater_v1_bundle_events_from_bundle_idx
175
+ ON public.hot_updater_v1_bundle_events(type, from_bundle_id, received_at_ms, id);
176
+ CREATE INDEX hot_updater_v1_bundle_events_to_release_idx
177
+ ON public.hot_updater_v1_bundle_events(type, to_release_id, received_at_ms, id);
178
+ CREATE INDEX hot_updater_v1_bundle_events_from_release_idx
179
+ ON public.hot_updater_v1_bundle_events(type, from_release_id, received_at_ms, id);
180
+ CREATE UNIQUE INDEX hot_updater_v1_api_keys_hash_key
181
+ ON public.hot_updater_v1_api_keys(hash);
182
+ CREATE INDEX hot_updater_v1_api_keys_created_at_idx
183
+ ON public.hot_updater_v1_api_keys(created_at_ms, id);
184
+
185
+ ALTER TABLE public.hot_updater_v1_channels ENABLE ROW LEVEL SECURITY;
186
+ ALTER TABLE public.hot_updater_v1_bundles ENABLE ROW LEVEL SECURITY;
187
+ ALTER TABLE public.hot_updater_v1_bundle_patches ENABLE ROW LEVEL SECURITY;
188
+ ALTER TABLE public.hot_updater_v1_releases ENABLE ROW LEVEL SECURITY;
189
+ ALTER TABLE public.hot_updater_v1_release_catalogs ENABLE ROW LEVEL SECURITY;
190
+ ALTER TABLE public.hot_updater_v1_bundle_events ENABLE ROW LEVEL SECURITY;
191
+ ALTER TABLE public.hot_updater_v1_api_keys ENABLE ROW LEVEL SECURITY;
192
+ ALTER TABLE public.hot_updater_v1_private_settings ENABLE ROW LEVEL SECURITY;
193
+
194
+ INSERT INTO public.hot_updater_v1_private_settings (key, value)
195
+ VALUES ('schema.core', '1.0.0')
196
+ ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value;
197
+ CREATE FUNCTION public.hot_updater_v1_commit(p_commit jsonb)
198
+ RETURNS jsonb
199
+ LANGUAGE plpgsql
200
+ SECURITY INVOKER
201
+ SET search_path = pg_catalog
202
+ AS $$
203
+ DECLARE
204
+ v_change jsonb;
205
+ v_change_index integer := -1;
206
+ v_expectation jsonb;
207
+ v_expected double precision;
208
+ v_actual double precision;
209
+ v_found boolean;
210
+ v_bundle public.hot_updater_v1_bundles;
211
+ v_patch public.hot_updater_v1_bundle_patches;
212
+ v_release public.hot_updater_v1_releases;
213
+ v_catalog public.hot_updater_v1_release_catalogs;
214
+ v_channel public.hot_updater_v1_channels;
215
+ v_event public.hot_updater_v1_bundle_events;
216
+ v_api_key public.hot_updater_v1_api_keys;
217
+ BEGIN
218
+ IF pg_catalog.jsonb_typeof(p_commit) IS DISTINCT FROM 'object'
219
+ OR pg_catalog.jsonb_typeof(p_commit->'changes') IS DISTINCT FROM 'array'
220
+ OR (
221
+ p_commit ? 'expectations'
222
+ AND pg_catalog.jsonb_typeof(p_commit->'expectations') IS DISTINCT FROM 'array'
223
+ )
224
+ THEN
225
+ RAISE EXCEPTION 'Hot Updater commit has an invalid envelope'
226
+ USING ERRCODE = '22023';
227
+ END IF;
228
+
229
+ FOR v_expectation IN
230
+ SELECT value
231
+ FROM pg_catalog.jsonb_array_elements(
232
+ COALESCE(p_commit->'expectations', '[]'::jsonb)
233
+ ) AS expectation(value)
234
+ LOOP
235
+ IF v_expectation->>'model' = 'releases' THEN
236
+ PERFORM pg_catalog.pg_advisory_xact_lock(
237
+ pg_catalog.hashtextextended(
238
+ 'hot_updater_v1:release:' || (v_expectation->>'id'),
239
+ 0
240
+ )
241
+ );
242
+ SELECT release.revision::double precision
243
+ INTO v_actual
244
+ FROM public.hot_updater_v1_releases AS release
245
+ WHERE release.id = (v_expectation->>'id')::uuid
246
+ FOR UPDATE;
247
+ v_found := FOUND;
248
+ v_expected := (v_expectation->>'revision')::double precision;
249
+ ELSIF v_expectation->>'model' = 'releaseCatalogs' THEN
250
+ PERFORM pg_catalog.pg_advisory_xact_lock(
251
+ pg_catalog.hashtextextended(
252
+ 'hot_updater_v1:catalog:' || (v_expectation->>'scopeKey'),
253
+ 0
254
+ )
255
+ );
256
+ SELECT catalog.generation
257
+ INTO v_actual
258
+ FROM public.hot_updater_v1_release_catalogs AS catalog
259
+ WHERE catalog.scope_key = v_expectation->>'scopeKey'
260
+ FOR UPDATE;
261
+ v_found := FOUND;
262
+ v_expected := (v_expectation->>'generation')::double precision;
263
+ ELSE
264
+ RAISE EXCEPTION 'Unsupported Hot Updater commit expectation'
265
+ USING ERRCODE = '22023';
266
+ END IF;
267
+
268
+ IF (v_expected IS NULL AND v_found)
269
+ OR (v_expected IS NOT NULL AND (NOT v_found OR v_actual <> v_expected))
270
+ THEN
271
+ RETURN pg_catalog.jsonb_build_object(
272
+ 'committed', false,
273
+ 'conflict', pg_catalog.jsonb_build_object(
274
+ 'changeIndex', -1,
275
+ 'reason', 'version_conflict',
276
+ 'model', v_expectation->>'model',
277
+ 'key', COALESCE(
278
+ v_expectation->>'id',
279
+ v_expectation->>'scopeKey'
280
+ ),
281
+ 'expectedVersion', v_expected,
282
+ 'actualVersion', CASE WHEN v_found THEN v_actual ELSE NULL END
283
+ )
284
+ );
285
+ END IF;
286
+ END LOOP;
287
+
288
+ BEGIN
289
+ FOR v_change_index, v_change IN
290
+ SELECT ordinal - 1, value
291
+ FROM pg_catalog.jsonb_array_elements(p_commit->'changes')
292
+ WITH ORDINALITY AS change(value, ordinal)
293
+ ORDER BY ordinal
294
+ LOOP
295
+ CASE v_change->>'model'
296
+ WHEN 'channels' THEN
297
+ CASE v_change->>'operation'
298
+ WHEN 'insert' THEN
299
+ IF v_change->>'onConflict' <> 'ignore' THEN
300
+ RAISE EXCEPTION 'Channel insert requires onConflict ignore'
301
+ USING ERRCODE = '22023';
302
+ END IF;
303
+ v_channel := pg_catalog.jsonb_populate_record(
304
+ NULL::public.hot_updater_v1_channels,
305
+ v_change->'row'
306
+ );
307
+ INSERT INTO public.hot_updater_v1_channels (id, name)
308
+ VALUES (v_channel.id, v_channel.name)
309
+ ON CONFLICT (name) DO NOTHING;
310
+ WHEN 'delete' THEN
311
+ BEGIN
312
+ DELETE FROM public.hot_updater_v1_channels
313
+ WHERE id = v_change->'where'->>'id';
314
+ IF NOT FOUND THEN RAISE no_data_found; END IF;
315
+ EXCEPTION
316
+ WHEN foreign_key_violation THEN RAISE SQLSTATE 'HU001';
317
+ END;
318
+ ELSE
319
+ RAISE EXCEPTION 'Unsupported Channel change'
320
+ USING ERRCODE = '22023';
321
+ END CASE;
322
+
323
+ WHEN 'bundles' THEN
324
+ CASE v_change->>'operation'
325
+ WHEN 'insert' THEN
326
+ v_bundle := pg_catalog.jsonb_populate_record(
327
+ NULL::public.hot_updater_v1_bundles,
328
+ v_change->'row'
329
+ );
330
+ INSERT INTO public.hot_updater_v1_bundles (
331
+ id, platform, file_hash, git_commit_hash, storage_uri,
332
+ archive_byte_size, metadata, manifest_storage_uri, manifest_file_hash,
333
+ asset_base_storage_uri
334
+ ) VALUES (
335
+ v_bundle.id, v_bundle.platform, v_bundle.file_hash,
336
+ v_bundle.git_commit_hash, v_bundle.storage_uri,
337
+ v_bundle.archive_byte_size, v_bundle.metadata,
338
+ v_bundle.manifest_storage_uri,
339
+ v_bundle.manifest_file_hash, v_bundle.asset_base_storage_uri
340
+ );
341
+ WHEN 'update' THEN
342
+ SELECT bundle.* INTO v_bundle
343
+ FROM public.hot_updater_v1_bundles AS bundle
344
+ WHERE bundle.id = (v_change->'where'->>'id')::uuid
345
+ FOR UPDATE;
346
+ IF NOT FOUND THEN RAISE no_data_found; END IF;
347
+ v_bundle := pg_catalog.jsonb_populate_record(
348
+ v_bundle,
349
+ v_change->'update'
350
+ );
351
+ UPDATE public.hot_updater_v1_bundles SET
352
+ platform = v_bundle.platform,
353
+ file_hash = v_bundle.file_hash,
354
+ git_commit_hash = v_bundle.git_commit_hash,
355
+ storage_uri = v_bundle.storage_uri,
356
+ archive_byte_size = v_bundle.archive_byte_size,
357
+ metadata = v_bundle.metadata,
358
+ manifest_storage_uri = v_bundle.manifest_storage_uri,
359
+ manifest_file_hash = v_bundle.manifest_file_hash,
360
+ asset_base_storage_uri = v_bundle.asset_base_storage_uri
361
+ WHERE id = v_bundle.id;
362
+ WHEN 'delete' THEN
363
+ BEGIN
364
+ DELETE FROM public.hot_updater_v1_bundles
365
+ WHERE id = (v_change->'where'->>'id')::uuid;
366
+ IF NOT FOUND THEN RAISE no_data_found; END IF;
367
+ EXCEPTION
368
+ WHEN foreign_key_violation THEN RAISE SQLSTATE 'HU001';
369
+ END;
370
+ ELSE
371
+ RAISE EXCEPTION 'Unsupported Bundle change'
372
+ USING ERRCODE = '22023';
373
+ END CASE;
374
+
375
+ WHEN 'bundlePatches' THEN
376
+ CASE v_change->>'operation'
377
+ WHEN 'insert' THEN
378
+ v_patch := pg_catalog.jsonb_populate_record(
379
+ NULL::public.hot_updater_v1_bundle_patches,
380
+ v_change->'row'
381
+ );
382
+ INSERT INTO public.hot_updater_v1_bundle_patches (
383
+ id, bundle_id, base_bundle_id, base_file_hash,
384
+ patch_file_hash, patch_storage_uri, byte_size, order_index
385
+ ) VALUES (
386
+ v_patch.id, v_patch.bundle_id, v_patch.base_bundle_id,
387
+ v_patch.base_file_hash, v_patch.patch_file_hash,
388
+ v_patch.patch_storage_uri, v_patch.byte_size,
389
+ v_patch.order_index
390
+ );
391
+ WHEN 'delete' THEN
392
+ DELETE FROM public.hot_updater_v1_bundle_patches
393
+ WHERE bundle_id = (v_change->'where'->>'bundleId')::uuid;
394
+ ELSE
395
+ RAISE EXCEPTION 'Unsupported Bundle patch change'
396
+ USING ERRCODE = '22023';
397
+ END CASE;
398
+
399
+ WHEN 'releases' THEN
400
+ CASE v_change->>'operation'
401
+ WHEN 'insert' THEN
402
+ v_release := pg_catalog.jsonb_populate_record(
403
+ NULL::public.hot_updater_v1_releases,
404
+ v_change->'row'
405
+ );
406
+ INSERT INTO public.hot_updater_v1_releases SELECT v_release.*;
407
+ WHEN 'update' THEN
408
+ SELECT release.* INTO v_release
409
+ FROM public.hot_updater_v1_releases AS release
410
+ WHERE release.id = (v_change->'where'->>'id')::uuid
411
+ FOR UPDATE;
412
+ IF NOT FOUND THEN RAISE no_data_found; END IF;
413
+ v_release := pg_catalog.jsonb_populate_record(
414
+ v_release,
415
+ v_change->'update'
416
+ );
417
+ UPDATE public.hot_updater_v1_releases SET
418
+ revision = v_release.revision,
419
+ scope_key = v_release.scope_key,
420
+ target_app_version = v_release.target_app_version,
421
+ fingerprint_hash = v_release.fingerprint_hash,
422
+ enabled = v_release.enabled,
423
+ should_force_update = v_release.should_force_update,
424
+ message = v_release.message,
425
+ rollout_cohort_count = v_release.rollout_cohort_count,
426
+ target_cohorts = v_release.target_cohorts,
427
+ updated_at_ms = v_release.updated_at_ms
428
+ WHERE id = v_release.id;
429
+ WHEN 'delete' THEN
430
+ DELETE FROM public.hot_updater_v1_releases
431
+ WHERE id = (v_change->'where'->>'id')::uuid;
432
+ IF NOT FOUND THEN RAISE no_data_found; END IF;
433
+ ELSE
434
+ RAISE EXCEPTION 'Unsupported Release change'
435
+ USING ERRCODE = '22023';
436
+ END CASE;
437
+
438
+ WHEN 'releaseCatalogs' THEN
439
+ IF v_change->>'operation' <> 'put' THEN
440
+ RAISE EXCEPTION 'Unsupported Release catalog change'
441
+ USING ERRCODE = '22023';
442
+ END IF;
443
+ v_catalog := pg_catalog.jsonb_populate_record(
444
+ NULL::public.hot_updater_v1_release_catalogs,
445
+ v_change->'row'
446
+ );
447
+ INSERT INTO public.hot_updater_v1_release_catalogs SELECT v_catalog.*
448
+ ON CONFLICT (scope_key) DO UPDATE SET
449
+ catalog_id = EXCLUDED.catalog_id,
450
+ strategy = EXCLUDED.strategy,
451
+ channel_id = EXCLUDED.channel_id,
452
+ channel_key = EXCLUDED.channel_key,
453
+ platform = EXCLUDED.platform,
454
+ fingerprint_hash = EXCLUDED.fingerprint_hash,
455
+ generation = EXCLUDED.generation,
456
+ payload = EXCLUDED.payload,
457
+ catalog_hash = EXCLUDED.catalog_hash,
458
+ byte_size = EXCLUDED.byte_size,
459
+ is_tombstone = EXCLUDED.is_tombstone,
460
+ updated_at_ms = EXCLUDED.updated_at_ms;
461
+
462
+ WHEN 'analytics' THEN
463
+ IF v_change->>'operation' <> 'insert' THEN
464
+ RAISE EXCEPTION 'Unsupported analytics change'
465
+ USING ERRCODE = '22023';
466
+ END IF;
467
+ v_event := pg_catalog.jsonb_populate_record(
468
+ NULL::public.hot_updater_v1_bundle_events,
469
+ v_change->'row'
470
+ );
471
+ INSERT INTO public.hot_updater_v1_bundle_events (
472
+ id, type, install_id, user_id, username, from_release_id,
473
+ from_bundle_id, to_release_id, to_bundle_id, platform,
474
+ app_version, channel, cohort, update_strategy, fingerprint_hash,
475
+ sdk_version, received_at_ms
476
+ ) VALUES (
477
+ v_event.id, v_event.type, v_event.install_id, v_event.user_id,
478
+ v_event.username, v_event.from_release_id, v_event.from_bundle_id,
479
+ v_event.to_release_id, v_event.to_bundle_id, v_event.platform,
480
+ v_event.app_version, v_event.channel, v_event.cohort,
481
+ v_event.update_strategy, v_event.fingerprint_hash,
482
+ v_event.sdk_version, v_event.received_at_ms
483
+ );
484
+
485
+ WHEN 'apiKeys' THEN
486
+ CASE v_change->>'operation'
487
+ WHEN 'insert' THEN
488
+ IF v_change->>'onConflict' <> 'ignore' THEN
489
+ RAISE EXCEPTION
490
+ 'API key insert requires onConflict ignore'
491
+ USING ERRCODE = '22023';
492
+ END IF;
493
+ v_api_key := pg_catalog.jsonb_populate_record(
494
+ NULL::public.hot_updater_v1_api_keys,
495
+ v_change->'row'
496
+ );
497
+ INSERT INTO public.hot_updater_v1_api_keys (
498
+ id, hash, name, prefix, role, created_at_ms, revoked_at_ms
499
+ ) VALUES (
500
+ v_api_key.id, v_api_key.hash, v_api_key.name,
501
+ v_api_key.prefix, v_api_key.role,
502
+ v_api_key.created_at_ms, v_api_key.revoked_at_ms
503
+ ) ON CONFLICT (hash) DO NOTHING;
504
+ WHEN 'update' THEN
505
+ UPDATE public.hot_updater_v1_api_keys
506
+ SET revoked_at_ms = (
507
+ v_change->'update'->>'revokedAtMs'
508
+ )::double precision
509
+ WHERE id = v_change->'where'->>'id';
510
+ IF NOT FOUND THEN RAISE no_data_found; END IF;
511
+ ELSE
512
+ RAISE EXCEPTION 'Unsupported API key change'
513
+ USING ERRCODE = '22023';
514
+ END CASE;
515
+
516
+ ELSE
517
+ RAISE EXCEPTION 'Unsupported commit model at index %', v_change_index
518
+ USING ERRCODE = '22023';
519
+ END CASE;
520
+ END LOOP;
521
+ EXCEPTION
522
+ WHEN no_data_found THEN
523
+ RETURN pg_catalog.jsonb_build_object(
524
+ 'committed', false,
525
+ 'conflict', pg_catalog.jsonb_build_object(
526
+ 'changeIndex', v_change_index,
527
+ 'reason', 'not_found'
528
+ )
529
+ );
530
+ WHEN SQLSTATE 'HU001' THEN
531
+ RETURN pg_catalog.jsonb_build_object(
532
+ 'committed', false,
533
+ 'conflict', pg_catalog.jsonb_build_object(
534
+ 'changeIndex', v_change_index,
535
+ 'reason', 'referenced'
536
+ )
537
+ );
538
+ END;
539
+
540
+ RETURN pg_catalog.jsonb_build_object('committed', true);
541
+ END;
542
+ $$;
543
+
544
+ REVOKE EXECUTE ON FUNCTION public.hot_updater_v1_commit(jsonb)
545
+ FROM PUBLIC, anon, authenticated;
546
+ GRANT EXECUTE ON FUNCTION public.hot_updater_v1_commit(jsonb)
547
+ TO service_role;
548
+
549
+ CREATE FUNCTION public.hot_updater_v1_delete_channel(p_id text)
550
+ RETURNS jsonb
551
+ LANGUAGE plpgsql
552
+ SECURITY INVOKER
553
+ SET search_path = pg_catalog
554
+ AS $$
555
+ BEGIN
556
+ BEGIN
557
+ DELETE FROM public.hot_updater_v1_channels
558
+ WHERE id = p_id;
559
+
560
+ IF NOT FOUND THEN
561
+ RETURN pg_catalog.jsonb_build_object(
562
+ 'deleted', false,
563
+ 'reason', 'not_found'
564
+ );
565
+ END IF;
566
+ EXCEPTION
567
+ WHEN foreign_key_violation THEN
568
+ RETURN pg_catalog.jsonb_build_object(
569
+ 'deleted', false,
570
+ 'reason', 'not_empty'
571
+ );
572
+ END;
573
+
574
+ RETURN pg_catalog.jsonb_build_object('deleted', true);
575
+ END;
576
+ $$;
577
+
578
+ REVOKE EXECUTE ON FUNCTION public.hot_updater_v1_delete_channel(text)
579
+ FROM PUBLIC, anon, authenticated;
580
+ GRANT EXECUTE ON FUNCTION public.hot_updater_v1_delete_channel(text)
581
+ TO service_role;
582
+
583
+ NOTIFY pgrst, 'reload schema';