@_nazmiforreal/flutter-ota 0.1.0 → 0.1.2

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 (167) hide show
  1. package/bin/flutter-patcher.js +48 -39
  2. package/dart-src/packages/cli-tools/.gitkeep +0 -0
  3. package/dart-src/packages/cli-tools/bin/flutter_patcher.dart +9 -0
  4. package/dart-src/packages/cli-tools/lib/flutter_patcher_cli.dart +73 -0
  5. package/dart-src/packages/cli-tools/lib/src/backend.dart +162 -0
  6. package/dart-src/packages/cli-tools/lib/src/cli_base.dart +56 -0
  7. package/dart-src/packages/cli-tools/lib/src/commands/build.dart +99 -0
  8. package/dart-src/packages/cli-tools/lib/src/commands/bundle.dart +151 -0
  9. package/dart-src/packages/cli-tools/lib/src/commands/channel.dart +140 -0
  10. package/dart-src/packages/cli-tools/lib/src/commands/config_command.dart +113 -0
  11. package/dart-src/packages/cli-tools/lib/src/commands/console.dart +55 -0
  12. package/dart-src/packages/cli-tools/lib/src/commands/deploy.dart +95 -0
  13. package/dart-src/packages/cli-tools/lib/src/commands/doctor.dart +48 -0
  14. package/dart-src/packages/cli-tools/lib/src/commands/fingerprint.dart +29 -0
  15. package/dart-src/packages/cli-tools/lib/src/commands/init.dart +82 -0
  16. package/dart-src/packages/cli-tools/lib/src/commands/keys.dart +55 -0
  17. package/dart-src/packages/cli-tools/lib/src/commands/migrate.dart +132 -0
  18. package/dart-src/packages/cli-tools/lib/src/commands/rollback.dart +41 -0
  19. package/dart-src/packages/cli-tools/lib/src/config.dart +715 -0
  20. package/dart-src/packages/cli-tools/lib/src/operations.dart +206 -0
  21. package/dart-src/packages/cli-tools/lib/src/pack.dart +417 -0
  22. package/dart-src/packages/cli-tools/lib/src/sign.dart +60 -0
  23. package/dart-src/packages/cli-tools/lib/src/util.dart +86 -0
  24. package/dart-src/packages/cli-tools/migrations/cloudflare/0001_hot-updater_init.sql +16 -0
  25. package/dart-src/packages/cli-tools/migrations/cloudflare/0002_hot-updater_0.13.0.sql +8 -0
  26. package/dart-src/packages/cli-tools/migrations/cloudflare/0003_hot-updater_0.18.0.sql +38 -0
  27. package/dart-src/packages/cli-tools/migrations/cloudflare/0004_hot-updater_0.29.0.sql +11 -0
  28. package/dart-src/packages/cli-tools/migrations/cloudflare/0005_hot-updater_0.31.0.sql +24 -0
  29. package/dart-src/packages/cli-tools/migrations/cloudflare/bundles.sql +42 -0
  30. package/dart-src/packages/cli-tools/migrations/supabase/20250103114225_init.sql +228 -0
  31. package/dart-src/packages/cli-tools/migrations/supabase/20250314000000_hot-updater_0.13.0.sql +134 -0
  32. package/dart-src/packages/cli-tools/migrations/supabase/20250516000000_hot-updater_0.18.0.sql +194 -0
  33. package/dart-src/packages/cli-tools/migrations/supabase/20251014000000_hot-updater_0.21.0.sql +184 -0
  34. package/dart-src/packages/cli-tools/migrations/supabase/20260401000000_hot-updater_0.29.0.sql +503 -0
  35. package/dart-src/packages/cli-tools/migrations/supabase/20260414000000_hot-updater_0.30.0.sql +50 -0
  36. package/dart-src/packages/cli-tools/migrations/supabase/20260422000000_hot-updater_0.31.0.sql +20 -0
  37. package/dart-src/packages/cli-tools/migrations/supabase/20260520014100_hot-updater_rls.sql +48 -0
  38. package/dart-src/packages/cli-tools/pubspec.yaml +37 -0
  39. package/dart-src/packages/core/lib/flutter_patcher_core.dart +29 -0
  40. package/dart-src/packages/core/lib/src/app_update_info.dart +111 -0
  41. package/dart-src/packages/core/lib/src/bundle.dart +191 -0
  42. package/dart-src/packages/core/lib/src/bundle_artifacts.dart +39 -0
  43. package/dart-src/packages/core/lib/src/bundle_patch_artifact.dart +47 -0
  44. package/dart-src/packages/core/lib/src/changed_asset.dart +77 -0
  45. package/dart-src/packages/core/lib/src/get_bundles_args.dart +78 -0
  46. package/dart-src/packages/core/lib/src/metadata.dart +31 -0
  47. package/dart-src/packages/core/lib/src/patch_info.dart +284 -0
  48. package/dart-src/packages/core/lib/src/platform.dart +15 -0
  49. package/dart-src/packages/core/lib/src/rollout.dart +209 -0
  50. package/dart-src/packages/core/lib/src/semver.dart +70 -0
  51. package/dart-src/packages/core/lib/src/semver_range.dart +358 -0
  52. package/dart-src/packages/core/lib/src/semver_version.dart +94 -0
  53. package/dart-src/packages/core/lib/src/status.dart +24 -0
  54. package/dart-src/packages/core/lib/src/strategy.dart +10 -0
  55. package/dart-src/packages/core/lib/src/update_bundle_params.dart +30 -0
  56. package/dart-src/packages/core/lib/src/uuid.dart +45 -0
  57. package/dart-src/packages/core/pubspec.yaml +9 -0
  58. package/dart-src/packages/core/tool/dbg.dart +6 -0
  59. package/dart-src/plugins/aws/.gitkeep +0 -0
  60. package/dart-src/plugins/aws/lib/flutter_patcher_aws.dart +12 -0
  61. package/dart-src/plugins/aws/lib/src/aws_cloudfront_client.dart +207 -0
  62. package/dart-src/plugins/aws/lib/src/aws_cloudfront_signer.dart +130 -0
  63. package/dart-src/plugins/aws/lib/src/aws_config.dart +59 -0
  64. package/dart-src/plugins/aws/lib/src/aws_database.dart +198 -0
  65. package/dart-src/plugins/aws/lib/src/aws_lambda_edge_storage.dart +72 -0
  66. package/dart-src/plugins/aws/lib/src/aws_s3_client.dart +312 -0
  67. package/dart-src/plugins/aws/lib/src/aws_ssm_client.dart +125 -0
  68. package/dart-src/plugins/aws/lib/src/aws_storage.dart +20 -0
  69. package/dart-src/plugins/aws/lib/src/aws_storage_profile.dart +123 -0
  70. package/dart-src/plugins/aws/lib/src/with_cloudfront_signed_url.dart +136 -0
  71. package/dart-src/plugins/aws/pubspec.yaml +22 -0
  72. package/dart-src/plugins/cloudflare/.gitkeep +0 -0
  73. package/dart-src/plugins/cloudflare/lib/flutter_patcher_cloudflare.dart +18 -0
  74. package/dart-src/plugins/cloudflare/lib/src/cloudflare_worker_database.dart +29 -0
  75. package/dart-src/plugins/cloudflare/lib/src/d1_build_where.dart +93 -0
  76. package/dart-src/plugins/cloudflare/lib/src/d1_bundle_mapper.dart +131 -0
  77. package/dart-src/plugins/cloudflare/lib/src/d1_client.dart +66 -0
  78. package/dart-src/plugins/cloudflare/lib/src/d1_config.dart +30 -0
  79. package/dart-src/plugins/cloudflare/lib/src/d1_database.dart +16 -0
  80. package/dart-src/plugins/cloudflare/lib/src/d1_database_plugin.dart +314 -0
  81. package/dart-src/plugins/cloudflare/lib/src/r2_config.dart +50 -0
  82. package/dart-src/plugins/cloudflare/lib/src/r2_s3_client.dart +287 -0
  83. package/dart-src/plugins/cloudflare/lib/src/r2_storage.dart +23 -0
  84. package/dart-src/plugins/cloudflare/lib/src/r2_storage_profile.dart +110 -0
  85. package/dart-src/plugins/cloudflare/migrations/0001_hot-updater_init.sql +16 -0
  86. package/dart-src/plugins/cloudflare/migrations/0002_hot-updater_0.13.0.sql +8 -0
  87. package/dart-src/plugins/cloudflare/migrations/0003_hot-updater_0.18.0.sql +38 -0
  88. package/dart-src/plugins/cloudflare/migrations/0004_hot-updater_0.29.0.sql +11 -0
  89. package/dart-src/plugins/cloudflare/migrations/0005_hot-updater_0.31.0.sql +24 -0
  90. package/dart-src/plugins/cloudflare/pubspec.yaml +20 -0
  91. package/dart-src/plugins/cloudflare/sql/bundles.sql +42 -0
  92. package/dart-src/plugins/plugin-core/lib/flutter_patcher_plugin_core.dart +42 -0
  93. package/dart-src/plugins/plugin-core/lib/src/asset_storage_layout.dart +94 -0
  94. package/dart-src/plugins/plugin-core/lib/src/bundle_storage_layout.dart +44 -0
  95. package/dart-src/plugins/plugin-core/lib/src/bundle_unit_of_work.dart +259 -0
  96. package/dart-src/plugins/plugin-core/lib/src/bundle_unit_of_work_store.dart +20 -0
  97. package/dart-src/plugins/plugin-core/lib/src/calculate_pagination.dart +29 -0
  98. package/dart-src/plugins/plugin-core/lib/src/compression_format.dart +63 -0
  99. package/dart-src/plugins/plugin-core/lib/src/content_addressed_assets.dart +22 -0
  100. package/dart-src/plugins/plugin-core/lib/src/create_blob_database_plugin.dart +853 -0
  101. package/dart-src/plugins/plugin-core/lib/src/create_database_plugin.dart +525 -0
  102. package/dart-src/plugins/plugin-core/lib/src/create_storage_key_builder.dart +15 -0
  103. package/dart-src/plugins/plugin-core/lib/src/create_storage_plugin.dart +130 -0
  104. package/dart-src/plugins/plugin-core/lib/src/filter_compatible_app_versions.dart +18 -0
  105. package/dart-src/plugins/plugin-core/lib/src/generate_min_bundle_id.dart +19 -0
  106. package/dart-src/plugins/plugin-core/lib/src/get_update_info.dart +241 -0
  107. package/dart-src/plugins/plugin-core/lib/src/is_object.dart +5 -0
  108. package/dart-src/plugins/plugin-core/lib/src/legacy_asset_storage_layout.dart +9 -0
  109. package/dart-src/plugins/plugin-core/lib/src/paginate_bundles.dart +112 -0
  110. package/dart-src/plugins/plugin-core/lib/src/parse_storage_uri.dart +44 -0
  111. package/dart-src/plugins/plugin-core/lib/src/query_bundles.dart +70 -0
  112. package/dart-src/plugins/plugin-core/lib/src/request_update_bundle_state.dart +62 -0
  113. package/dart-src/plugins/plugin-core/lib/src/resolve_update_info_from_bundles.dart +43 -0
  114. package/dart-src/plugins/plugin-core/lib/src/storage_profile.dart +27 -0
  115. package/dart-src/plugins/plugin-core/lib/src/types.dart +454 -0
  116. package/dart-src/plugins/plugin-core/lib/src/uuidv7.dart +41 -0
  117. package/dart-src/plugins/plugin-core/pubspec.yaml +15 -0
  118. package/dart-src/plugins/postgres/.gitkeep +0 -0
  119. package/dart-src/plugins/postgres/lib/flutter_patcher_postgres.dart +17 -0
  120. package/dart-src/plugins/postgres/lib/src/postgres_bundle_mapper.dart +130 -0
  121. package/dart-src/plugins/postgres/lib/src/postgres_client.dart +90 -0
  122. package/dart-src/plugins/postgres/lib/src/postgres_config.dart +35 -0
  123. package/dart-src/plugins/postgres/lib/src/postgres_database.dart +302 -0
  124. package/dart-src/plugins/postgres/lib/src/postgres_get_update_info.dart +120 -0
  125. package/dart-src/plugins/postgres/lib/src/postgres_storage.dart +205 -0
  126. package/dart-src/plugins/postgres/lib/src/postgres_types.dart +132 -0
  127. package/dart-src/plugins/postgres/pubspec.yaml +18 -0
  128. package/dart-src/plugins/postgres/sql/bundles.sql +45 -0
  129. package/dart-src/plugins/postgres/sql/get_target_app_version_list.sql +21 -0
  130. package/dart-src/plugins/postgres/sql/get_update_info.spec.ts +171 -0
  131. package/dart-src/plugins/postgres/sql/get_update_info_by_app_version.sql +126 -0
  132. package/dart-src/plugins/postgres/sql/get_update_info_by_fingerprint_hash.sql +125 -0
  133. package/dart-src/plugins/postgres/sql/hash_user_id.sql +30 -0
  134. package/dart-src/plugins/postgres/sql/is_device_eligible.sql +237 -0
  135. package/dart-src/plugins/postgres/sql/prepareSql.ts +11 -0
  136. package/dart-src/plugins/standalone/.gitkeep +0 -0
  137. package/dart-src/plugins/standalone/lib/flutter_patcher_standalone.dart +12 -0
  138. package/dart-src/plugins/standalone/lib/src/standalone_config.dart +107 -0
  139. package/dart-src/plugins/standalone/lib/src/standalone_database.dart +233 -0
  140. package/dart-src/plugins/standalone/lib/src/standalone_storage.dart +175 -0
  141. package/dart-src/plugins/standalone/pubspec.yaml +18 -0
  142. package/dart-src/plugins/supabase/lib/edge.dart +10 -0
  143. package/dart-src/plugins/supabase/lib/flutter_patcher_supabase.dart +21 -0
  144. package/dart-src/plugins/supabase/lib/src/supabase_bundle_mapper.dart +129 -0
  145. package/dart-src/plugins/supabase/lib/src/supabase_client_adapter.dart +154 -0
  146. package/dart-src/plugins/supabase/lib/src/supabase_client_http.dart +516 -0
  147. package/dart-src/plugins/supabase/lib/src/supabase_config.dart +34 -0
  148. package/dart-src/plugins/supabase/lib/src/supabase_database.dart +422 -0
  149. package/dart-src/plugins/supabase/lib/src/supabase_edge_function_database.dart +32 -0
  150. package/dart-src/plugins/supabase/lib/src/supabase_edge_function_storage.dart +99 -0
  151. package/dart-src/plugins/supabase/lib/src/supabase_signed_url_batcher.dart +145 -0
  152. package/dart-src/plugins/supabase/lib/src/supabase_storage.dart +298 -0
  153. package/dart-src/plugins/supabase/lib/src/types.dart +130 -0
  154. package/dart-src/plugins/supabase/pubspec.yaml +19 -0
  155. package/dart-src/plugins/supabase/supabase/edge-functions/index.ts +44 -0
  156. package/dart-src/plugins/supabase/supabase/edge-functions/runtime.docker.integration.spec.ts +968 -0
  157. package/dart-src/plugins/supabase/supabase/migrations/20250103114225_init.sql +228 -0
  158. package/dart-src/plugins/supabase/supabase/migrations/20250314000000_hot-updater_0.13.0.sql +134 -0
  159. package/dart-src/plugins/supabase/supabase/migrations/20250516000000_hot-updater_0.18.0.sql +194 -0
  160. package/dart-src/plugins/supabase/supabase/migrations/20251014000000_hot-updater_0.21.0.sql +184 -0
  161. package/dart-src/plugins/supabase/supabase/migrations/20260401000000_hot-updater_0.29.0.sql +503 -0
  162. package/dart-src/plugins/supabase/supabase/migrations/20260414000000_hot-updater_0.30.0.sql +50 -0
  163. package/dart-src/plugins/supabase/supabase/migrations/20260422000000_hot-updater_0.31.0.sql +20 -0
  164. package/dart-src/plugins/supabase/supabase/migrations/20260520014100_hot-updater_rls.sql +48 -0
  165. package/package.json +2 -1
  166. package/scripts/postinstall.js +53 -25
  167. /package/bin/{flutter-patcher-linux → flutter-patcher-linux-x64} +0 -0
@@ -0,0 +1,503 @@
1
+ -- HotUpdater.bundles
2
+
3
+ ALTER TABLE bundles
4
+ ADD COLUMN IF NOT EXISTS rollout_cohort_count INTEGER DEFAULT 1000
5
+ CHECK (rollout_cohort_count >= 0 AND rollout_cohort_count <= 1000);
6
+
7
+ CREATE INDEX IF NOT EXISTS bundles_rollout_idx ON bundles(rollout_cohort_count);
8
+
9
+ ALTER TABLE bundles
10
+ ADD COLUMN IF NOT EXISTS target_cohorts TEXT[];
11
+
12
+ CREATE INDEX IF NOT EXISTS bundles_target_cohorts_idx ON bundles
13
+ USING GIN (target_cohorts);
14
+
15
+ -- HotUpdater.is_cohort_eligible
16
+ -- Cohort eligibility helpers matching @hot-updater/core rollout.ts
17
+
18
+ CREATE OR REPLACE FUNCTION positive_mod(
19
+ value INTEGER,
20
+ modulus INTEGER
21
+ )
22
+ RETURNS INTEGER
23
+ LANGUAGE plpgsql
24
+ IMMUTABLE
25
+ AS $$
26
+ BEGIN
27
+ RETURN ((value % modulus) + modulus) % modulus;
28
+ END;
29
+ $$;
30
+
31
+ CREATE OR REPLACE FUNCTION hash_rollout_value(input TEXT)
32
+ RETURNS INTEGER
33
+ LANGUAGE plpgsql
34
+ IMMUTABLE
35
+ AS $$
36
+ DECLARE
37
+ hash_value NUMERIC := 0;
38
+ char_code INTEGER;
39
+ i INTEGER;
40
+ BEGIN
41
+ FOR i IN 1..length(input) LOOP
42
+ char_code := ascii(substring(input from i for 1));
43
+ hash_value := mod((hash_value * 31) + char_code, 4294967296);
44
+ END LOOP;
45
+
46
+ IF hash_value >= 2147483648 THEN
47
+ hash_value := hash_value - 4294967296;
48
+ END IF;
49
+
50
+ RETURN hash_value::INTEGER;
51
+ END;
52
+ $$;
53
+
54
+ CREATE OR REPLACE FUNCTION normalize_cohort_value(cohort TEXT)
55
+ RETURNS TEXT
56
+ LANGUAGE plpgsql
57
+ IMMUTABLE
58
+ AS $$
59
+ DECLARE
60
+ normalized TEXT;
61
+ cohort_value INTEGER;
62
+ BEGIN
63
+ IF cohort IS NULL THEN
64
+ RETURN NULL;
65
+ END IF;
66
+
67
+ normalized := lower(btrim(cohort));
68
+
69
+ IF normalized ~ '^[0-9]+$' THEN
70
+ cohort_value := normalized::INTEGER;
71
+ IF cohort_value BETWEEN 1 AND 1000 THEN
72
+ RETURN cohort_value::TEXT;
73
+ END IF;
74
+ END IF;
75
+
76
+ RETURN normalized;
77
+ END;
78
+ $$;
79
+
80
+ CREATE OR REPLACE FUNCTION gcd_int(a INTEGER, b INTEGER)
81
+ RETURNS INTEGER
82
+ LANGUAGE plpgsql
83
+ IMMUTABLE
84
+ AS $$
85
+ DECLARE
86
+ x INTEGER := abs(a);
87
+ y INTEGER := abs(b);
88
+ next_value INTEGER;
89
+ BEGIN
90
+ WHILE y <> 0 LOOP
91
+ next_value := x % y;
92
+ x := y;
93
+ y := next_value;
94
+ END LOOP;
95
+
96
+ RETURN x;
97
+ END;
98
+ $$;
99
+
100
+ CREATE OR REPLACE FUNCTION get_rollout_multiplier(bundle_id UUID)
101
+ RETURNS INTEGER
102
+ LANGUAGE plpgsql
103
+ IMMUTABLE
104
+ AS $$
105
+ DECLARE
106
+ candidate INTEGER := positive_mod(
107
+ hash_rollout_value(bundle_id::TEXT || ':multiplier'),
108
+ 997
109
+ );
110
+ BEGIN
111
+ IF candidate = 0 THEN
112
+ candidate := 1;
113
+ END IF;
114
+
115
+ WHILE gcd_int(candidate, 1000) <> 1 LOOP
116
+ candidate := positive_mod(candidate + 1, 1000);
117
+ IF candidate = 0 THEN
118
+ candidate := 1;
119
+ END IF;
120
+ END LOOP;
121
+
122
+ RETURN candidate;
123
+ END;
124
+ $$;
125
+
126
+ CREATE OR REPLACE FUNCTION get_rollout_offset(bundle_id UUID)
127
+ RETURNS INTEGER
128
+ LANGUAGE plpgsql
129
+ IMMUTABLE
130
+ AS $$
131
+ BEGIN
132
+ RETURN positive_mod(hash_rollout_value(bundle_id::TEXT || ':offset'), 1000);
133
+ END;
134
+ $$;
135
+
136
+ CREATE OR REPLACE FUNCTION get_modular_inverse(value INTEGER, modulus INTEGER)
137
+ RETURNS INTEGER
138
+ LANGUAGE plpgsql
139
+ IMMUTABLE
140
+ AS $$
141
+ DECLARE
142
+ candidate INTEGER;
143
+ BEGIN
144
+ FOR candidate IN 1..(modulus - 1) LOOP
145
+ IF positive_mod(value * candidate, modulus) = 1 THEN
146
+ RETURN candidate;
147
+ END IF;
148
+ END LOOP;
149
+
150
+ RAISE EXCEPTION 'No modular inverse for % mod %', value, modulus;
151
+ END;
152
+ $$;
153
+
154
+ CREATE OR REPLACE FUNCTION is_numeric_cohort(cohort TEXT)
155
+ RETURNS BOOLEAN
156
+ LANGUAGE plpgsql
157
+ IMMUTABLE
158
+ AS $$
159
+ DECLARE
160
+ normalized_cohort TEXT := normalize_cohort_value(cohort);
161
+ cohort_value INTEGER;
162
+ BEGIN
163
+ IF normalized_cohort IS NULL OR normalized_cohort !~ '^[0-9]+$' THEN
164
+ RETURN FALSE;
165
+ END IF;
166
+
167
+ cohort_value := normalized_cohort::INTEGER;
168
+ RETURN cohort_value BETWEEN 1 AND 1000;
169
+ END;
170
+ $$;
171
+
172
+ CREATE OR REPLACE FUNCTION get_numeric_cohort_rollout_position(
173
+ bundle_id UUID,
174
+ cohort TEXT
175
+ )
176
+ RETURNS INTEGER
177
+ LANGUAGE plpgsql
178
+ IMMUTABLE
179
+ AS $$
180
+ DECLARE
181
+ normalized_cohort TEXT := normalize_cohort_value(cohort);
182
+ cohort_value INTEGER;
183
+ multiplier INTEGER;
184
+ offset_value INTEGER;
185
+ inverse_multiplier INTEGER;
186
+ BEGIN
187
+ IF NOT is_numeric_cohort(normalized_cohort) THEN
188
+ RAISE EXCEPTION 'Invalid numeric cohort: %', cohort;
189
+ END IF;
190
+
191
+ cohort_value := normalized_cohort::INTEGER - 1;
192
+ multiplier := get_rollout_multiplier(bundle_id);
193
+ offset_value := get_rollout_offset(bundle_id);
194
+ inverse_multiplier := get_modular_inverse(multiplier, 1000);
195
+
196
+ RETURN positive_mod(
197
+ inverse_multiplier * (cohort_value - offset_value),
198
+ 1000
199
+ );
200
+ END;
201
+ $$;
202
+
203
+ CREATE OR REPLACE FUNCTION is_cohort_eligible(
204
+ bundle_id UUID,
205
+ cohort TEXT,
206
+ rollout_cohort_count INTEGER,
207
+ target_cohorts TEXT[]
208
+ )
209
+ RETURNS BOOLEAN
210
+ LANGUAGE plpgsql
211
+ IMMUTABLE
212
+ AS $$
213
+ DECLARE
214
+ normalized_cohort TEXT := normalize_cohort_value(cohort);
215
+ normalized_rollout_count INTEGER := COALESCE(rollout_cohort_count, 1000);
216
+ normalized_target_cohorts TEXT[];
217
+ BEGIN
218
+ IF target_cohorts IS NOT NULL THEN
219
+ normalized_target_cohorts := ARRAY(
220
+ SELECT normalize_cohort_value(value)
221
+ FROM unnest(target_cohorts) AS value
222
+ );
223
+ END IF;
224
+
225
+ IF normalized_target_cohorts IS NOT NULL
226
+ AND array_length(normalized_target_cohorts, 1) > 0 THEN
227
+ RETURN normalized_cohort IS NOT NULL
228
+ AND normalized_cohort = ANY(normalized_target_cohorts);
229
+ END IF;
230
+
231
+ IF normalized_rollout_count <= 0 THEN
232
+ RETURN FALSE;
233
+ END IF;
234
+
235
+ IF normalized_cohort IS NULL THEN
236
+ RETURN normalized_rollout_count >= 1000;
237
+ END IF;
238
+
239
+ IF NOT is_numeric_cohort(normalized_cohort) THEN
240
+ RETURN FALSE;
241
+ END IF;
242
+
243
+ IF normalized_rollout_count >= 1000 THEN
244
+ RETURN TRUE;
245
+ END IF;
246
+
247
+ RETURN get_numeric_cohort_rollout_position(bundle_id, normalized_cohort)
248
+ < normalized_rollout_count;
249
+ END;
250
+ $$;
251
+
252
+ -- HotUpdater.get_update_info_by_fingerprint_hash
253
+
254
+ DROP FUNCTION IF EXISTS get_update_info_by_fingerprint_hash;
255
+
256
+ CREATE OR REPLACE FUNCTION get_update_info_by_fingerprint_hash (
257
+ app_platform platforms,
258
+ bundle_id uuid,
259
+ min_bundle_id uuid,
260
+ target_channel text,
261
+ target_fingerprint_hash text,
262
+ cohort TEXT DEFAULT NULL
263
+ )
264
+ RETURNS TABLE (
265
+ id uuid,
266
+ should_force_update boolean,
267
+ message text,
268
+ status text,
269
+ storage_uri text,
270
+ file_hash text
271
+ )
272
+ LANGUAGE plpgsql
273
+ AS
274
+ $$
275
+ DECLARE
276
+ NIL_UUID CONSTANT uuid := '00000000-0000-0000-0000-000000000000';
277
+ BEGIN
278
+ RETURN QUERY
279
+ WITH candidate_bundles AS (
280
+ SELECT
281
+ b.id,
282
+ b.should_force_update,
283
+ b.message,
284
+ b.storage_uri,
285
+ b.file_hash,
286
+ b.rollout_cohort_count,
287
+ b.target_cohorts
288
+ FROM bundles b
289
+ WHERE b.enabled = TRUE
290
+ AND b.platform = app_platform
291
+ AND b.id >= min_bundle_id
292
+ AND b.channel = target_channel
293
+ AND b.fingerprint_hash = target_fingerprint_hash
294
+ ),
295
+ current_candidate AS (
296
+ SELECT
297
+ cb.id,
298
+ is_cohort_eligible(
299
+ cb.id,
300
+ cohort,
301
+ cb.rollout_cohort_count,
302
+ cb.target_cohorts
303
+ ) AS is_eligible
304
+ FROM candidate_bundles cb
305
+ WHERE cb.id = bundle_id
306
+ LIMIT 1
307
+ ),
308
+ eligible_update_candidate AS (
309
+ SELECT
310
+ cb.id,
311
+ cb.should_force_update,
312
+ cb.message,
313
+ 'UPDATE' AS status,
314
+ cb.storage_uri,
315
+ cb.file_hash
316
+ FROM candidate_bundles cb
317
+ WHERE cb.id > bundle_id
318
+ AND is_cohort_eligible(
319
+ cb.id,
320
+ cohort,
321
+ cb.rollout_cohort_count,
322
+ cb.target_cohorts
323
+ )
324
+ ORDER BY cb.id DESC
325
+ LIMIT 1
326
+ ),
327
+ rollback_candidate AS (
328
+ SELECT
329
+ cb.id,
330
+ TRUE AS should_force_update,
331
+ cb.message,
332
+ 'ROLLBACK' AS status,
333
+ cb.storage_uri,
334
+ cb.file_hash
335
+ FROM candidate_bundles cb
336
+ WHERE cb.id < bundle_id
337
+ AND NOT EXISTS (
338
+ SELECT 1
339
+ FROM current_candidate
340
+ WHERE current_candidate.is_eligible = TRUE
341
+ )
342
+ AND NOT EXISTS (SELECT 1 FROM eligible_update_candidate)
343
+ ORDER BY cb.id DESC
344
+ LIMIT 1
345
+ ),
346
+ final_result AS (
347
+ SELECT * FROM eligible_update_candidate
348
+ UNION ALL
349
+ SELECT * FROM rollback_candidate
350
+ WHERE NOT EXISTS (SELECT 1 FROM eligible_update_candidate)
351
+ )
352
+ SELECT *
353
+ FROM final_result
354
+ WHERE final_result.id != bundle_id
355
+
356
+ UNION ALL
357
+
358
+ SELECT
359
+ NIL_UUID AS id,
360
+ TRUE AS should_force_update,
361
+ NULL AS message,
362
+ 'ROLLBACK' AS status,
363
+ NULL AS storage_uri,
364
+ NULL AS file_hash
365
+ WHERE (SELECT COUNT(*) FROM final_result) = 0
366
+ AND bundle_id != NIL_UUID
367
+ AND bundle_id > min_bundle_id
368
+ AND NOT EXISTS (
369
+ SELECT 1
370
+ FROM current_candidate
371
+ WHERE current_candidate.is_eligible = TRUE
372
+ )
373
+ AND NOT EXISTS (SELECT 1 FROM eligible_update_candidate)
374
+ AND NOT EXISTS (SELECT 1 FROM rollback_candidate);
375
+ END;
376
+ $$;
377
+
378
+ -- HotUpdater.get_update_info_by_app_version
379
+
380
+ DROP FUNCTION IF EXISTS get_update_info_by_app_version;
381
+
382
+ CREATE OR REPLACE FUNCTION get_update_info_by_app_version (
383
+ app_platform platforms,
384
+ app_version text,
385
+ bundle_id uuid,
386
+ min_bundle_id uuid,
387
+ target_channel text,
388
+ target_app_version_list text[],
389
+ cohort TEXT DEFAULT NULL
390
+ )
391
+ RETURNS TABLE (
392
+ id uuid,
393
+ should_force_update boolean,
394
+ message text,
395
+ status text,
396
+ storage_uri text,
397
+ file_hash text
398
+ )
399
+ LANGUAGE plpgsql
400
+ AS
401
+ $$
402
+ DECLARE
403
+ NIL_UUID CONSTANT uuid := '00000000-0000-0000-0000-000000000000';
404
+ BEGIN
405
+ RETURN QUERY
406
+ WITH candidate_bundles AS (
407
+ SELECT
408
+ b.id,
409
+ b.should_force_update,
410
+ b.message,
411
+ b.storage_uri,
412
+ b.file_hash,
413
+ b.rollout_cohort_count,
414
+ b.target_cohorts
415
+ FROM bundles b
416
+ WHERE b.enabled = TRUE
417
+ AND b.platform = app_platform
418
+ AND b.id >= min_bundle_id
419
+ AND b.target_app_version IN (SELECT unnest(target_app_version_list))
420
+ AND b.channel = target_channel
421
+ ),
422
+ current_candidate AS (
423
+ SELECT
424
+ cb.id,
425
+ is_cohort_eligible(
426
+ cb.id,
427
+ cohort,
428
+ cb.rollout_cohort_count,
429
+ cb.target_cohorts
430
+ ) AS is_eligible
431
+ FROM candidate_bundles cb
432
+ WHERE cb.id = bundle_id
433
+ LIMIT 1
434
+ ),
435
+ eligible_update_candidate AS (
436
+ SELECT
437
+ cb.id,
438
+ cb.should_force_update,
439
+ cb.message,
440
+ 'UPDATE' AS status,
441
+ cb.storage_uri,
442
+ cb.file_hash
443
+ FROM candidate_bundles cb
444
+ WHERE cb.id > bundle_id
445
+ AND is_cohort_eligible(
446
+ cb.id,
447
+ cohort,
448
+ cb.rollout_cohort_count,
449
+ cb.target_cohorts
450
+ )
451
+ ORDER BY cb.id DESC
452
+ LIMIT 1
453
+ ),
454
+ rollback_candidate AS (
455
+ SELECT
456
+ cb.id,
457
+ TRUE AS should_force_update,
458
+ cb.message,
459
+ 'ROLLBACK' AS status,
460
+ cb.storage_uri,
461
+ cb.file_hash
462
+ FROM candidate_bundles cb
463
+ WHERE cb.id < bundle_id
464
+ AND NOT EXISTS (
465
+ SELECT 1
466
+ FROM current_candidate
467
+ WHERE current_candidate.is_eligible = TRUE
468
+ )
469
+ AND NOT EXISTS (SELECT 1 FROM eligible_update_candidate)
470
+ ORDER BY cb.id DESC
471
+ LIMIT 1
472
+ ),
473
+ final_result AS (
474
+ SELECT * FROM eligible_update_candidate
475
+ UNION ALL
476
+ SELECT * FROM rollback_candidate
477
+ WHERE NOT EXISTS (SELECT 1 FROM eligible_update_candidate)
478
+ )
479
+ SELECT *
480
+ FROM final_result
481
+ WHERE final_result.id != bundle_id
482
+
483
+ UNION ALL
484
+
485
+ SELECT
486
+ NIL_UUID AS id,
487
+ TRUE AS should_force_update,
488
+ NULL AS message,
489
+ 'ROLLBACK' AS status,
490
+ NULL AS storage_uri,
491
+ NULL AS file_hash
492
+ WHERE (SELECT COUNT(*) FROM final_result) = 0
493
+ AND bundle_id != NIL_UUID
494
+ AND bundle_id > min_bundle_id
495
+ AND NOT EXISTS (
496
+ SELECT 1
497
+ FROM current_candidate
498
+ WHERE current_candidate.is_eligible = TRUE
499
+ )
500
+ AND NOT EXISTS (SELECT 1 FROM eligible_update_candidate)
501
+ AND NOT EXISTS (SELECT 1 FROM rollback_candidate);
502
+ END;
503
+ $$;
@@ -0,0 +1,50 @@
1
+ -- HotUpdater.is_cohort_eligible
2
+ CREATE OR REPLACE FUNCTION is_cohort_eligible(
3
+ bundle_id UUID,
4
+ cohort TEXT,
5
+ rollout_cohort_count INTEGER,
6
+ target_cohorts TEXT[]
7
+ )
8
+ RETURNS BOOLEAN
9
+ LANGUAGE plpgsql
10
+ IMMUTABLE
11
+ AS $$
12
+ DECLARE
13
+ normalized_cohort TEXT := normalize_cohort_value(cohort);
14
+ normalized_rollout_count INTEGER := COALESCE(rollout_cohort_count, 1000);
15
+ normalized_target_cohorts TEXT[];
16
+ BEGIN
17
+ IF target_cohorts IS NOT NULL THEN
18
+ normalized_target_cohorts := ARRAY(
19
+ SELECT normalize_cohort_value(value)
20
+ FROM unnest(target_cohorts) AS value
21
+ );
22
+ END IF;
23
+
24
+ IF normalized_target_cohorts IS NOT NULL
25
+ AND array_length(normalized_target_cohorts, 1) > 0
26
+ AND normalized_cohort IS NOT NULL
27
+ AND normalized_cohort = ANY(normalized_target_cohorts) THEN
28
+ RETURN TRUE;
29
+ END IF;
30
+
31
+ IF normalized_rollout_count <= 0 THEN
32
+ RETURN FALSE;
33
+ END IF;
34
+
35
+ IF normalized_cohort IS NULL THEN
36
+ RETURN normalized_rollout_count >= 1000;
37
+ END IF;
38
+
39
+ IF NOT is_numeric_cohort(normalized_cohort) THEN
40
+ RETURN FALSE;
41
+ END IF;
42
+
43
+ IF normalized_rollout_count >= 1000 THEN
44
+ RETURN TRUE;
45
+ END IF;
46
+
47
+ RETURN get_numeric_cohort_rollout_position(bundle_id, normalized_cohort)
48
+ < normalized_rollout_count;
49
+ END;
50
+ $$;
@@ -0,0 +1,20 @@
1
+ -- HotUpdater.bundle_artifact_columns
2
+
3
+ ALTER TABLE bundles ADD COLUMN IF NOT EXISTS manifest_storage_uri text;
4
+ ALTER TABLE bundles ADD COLUMN IF NOT EXISTS manifest_file_hash text;
5
+ ALTER TABLE bundles ADD COLUMN IF NOT EXISTS asset_base_storage_uri text;
6
+
7
+ CREATE TABLE IF NOT EXISTS bundle_patches (
8
+ id text PRIMARY KEY,
9
+ bundle_id uuid NOT NULL REFERENCES bundles(id) ON DELETE CASCADE,
10
+ base_bundle_id uuid NOT NULL REFERENCES bundles(id) ON DELETE CASCADE,
11
+ base_file_hash text NOT NULL,
12
+ patch_file_hash text NOT NULL,
13
+ patch_storage_uri text NOT NULL,
14
+ order_index integer NOT NULL DEFAULT 0
15
+ );
16
+
17
+ CREATE INDEX IF NOT EXISTS bundle_patches_bundle_id_idx
18
+ ON bundle_patches(bundle_id);
19
+ CREATE INDEX IF NOT EXISTS bundle_patches_base_bundle_id_idx
20
+ ON bundle_patches(base_bundle_id);
@@ -0,0 +1,48 @@
1
+ -- HotUpdater.supabase_rls
2
+
3
+ ALTER TABLE public.bundles ENABLE ROW LEVEL SECURITY;
4
+ ALTER TABLE public.bundle_patches ENABLE ROW LEVEL SECURITY;
5
+
6
+ ALTER FUNCTION public.get_target_app_version_list(public.platforms, uuid)
7
+ SET search_path = public, pg_catalog;
8
+ ALTER FUNCTION public.get_channels()
9
+ SET search_path = public, pg_catalog;
10
+ ALTER FUNCTION public.positive_mod(integer, integer)
11
+ SET search_path = public, pg_catalog;
12
+ ALTER FUNCTION public.hash_rollout_value(text)
13
+ SET search_path = public, pg_catalog;
14
+ ALTER FUNCTION public.normalize_cohort_value(text)
15
+ SET search_path = public, pg_catalog;
16
+ ALTER FUNCTION public.gcd_int(integer, integer)
17
+ SET search_path = public, pg_catalog;
18
+ ALTER FUNCTION public.get_rollout_multiplier(uuid)
19
+ SET search_path = public, pg_catalog;
20
+ ALTER FUNCTION public.get_rollout_offset(uuid)
21
+ SET search_path = public, pg_catalog;
22
+ ALTER FUNCTION public.get_modular_inverse(integer, integer)
23
+ SET search_path = public, pg_catalog;
24
+ ALTER FUNCTION public.is_numeric_cohort(text)
25
+ SET search_path = public, pg_catalog;
26
+ ALTER FUNCTION public.get_numeric_cohort_rollout_position(uuid, text)
27
+ SET search_path = public, pg_catalog;
28
+ ALTER FUNCTION public.is_cohort_eligible(uuid, text, integer, text[])
29
+ SET search_path = public, pg_catalog;
30
+ ALTER FUNCTION public.get_update_info_by_fingerprint_hash(
31
+ public.platforms,
32
+ uuid,
33
+ uuid,
34
+ text,
35
+ text,
36
+ text
37
+ )
38
+ SET search_path = public, pg_catalog;
39
+ ALTER FUNCTION public.get_update_info_by_app_version(
40
+ public.platforms,
41
+ text,
42
+ uuid,
43
+ uuid,
44
+ text,
45
+ text[],
46
+ text
47
+ )
48
+ SET search_path = public, pg_catalog;
@@ -0,0 +1,37 @@
1
+ name: flutter_patcher_cli
2
+ description: >-
3
+ Command-line interface for flutter_patcher (a hot-updater compatible OTA code
4
+ push tool for Flutter). Deploy bundles, manage channels/rollbacks, run
5
+ migrations, and inspect the backend — mirroring the `hot-updater` CLI UX.
6
+ version: 0.1.0
7
+ publish_to: none
8
+
9
+ environment:
10
+ sdk: '>=3.3.0 <4.0.0'
11
+
12
+ dependencies:
13
+ args: '>=2.4.0 <3.0.0'
14
+ crypto: '>=3.0.3 <4.0.0'
15
+ path: ^1.9.0
16
+ archive: '>=3.4.10 <5.0.0'
17
+ collection: ^1.18.0
18
+ cryptography: ^2.7.0
19
+ postgres: ^3.4.0
20
+ flutter_patcher_core:
21
+ path: ../core
22
+ flutter_patcher_plugin_core:
23
+ path: ../../plugins/plugin-core
24
+ flutter_patcher_supabase:
25
+ path: ../../plugins/supabase
26
+ flutter_patcher_postgres:
27
+ path: ../../plugins/postgres
28
+ flutter_patcher_cloudflare:
29
+ path: ../../plugins/cloudflare
30
+ flutter_patcher_aws:
31
+ path: ../../plugins/aws
32
+ flutter_patcher_standalone:
33
+ path: ../../plugins/standalone
34
+
35
+ dev_dependencies:
36
+ test: ^1.25.0
37
+ http: ^1.2.0
@@ -0,0 +1,29 @@
1
+ /// flutter_patcher core — Dart translation of hot-updater's packages/core.
2
+ ///
3
+ /// Pure Dart, no Flutter dependency. Modules:
4
+ /// - types: bundle/update data model (platform, status, strategy)
5
+ /// - rollout: deterministic staged-rollout cohort math
6
+ /// - uuid: uuidv7 generation + NIL sentinel
7
+ library;
8
+
9
+ // Data model (one module per concept).
10
+ export 'src/platform.dart';
11
+ export 'src/status.dart';
12
+ export 'src/strategy.dart';
13
+ export 'src/metadata.dart';
14
+ export 'src/bundle_patch_artifact.dart';
15
+ export 'src/changed_asset.dart';
16
+ export 'src/bundle.dart';
17
+ export 'src/app_update_info.dart';
18
+ export 'src/get_bundles_args.dart';
19
+ export 'src/update_bundle_params.dart';
20
+
21
+ // Algorithms.
22
+ export 'src/rollout.dart';
23
+ export 'src/semver.dart';
24
+ export 'src/uuid.dart';
25
+
26
+ // Bundle artifact helpers.
27
+ export 'src/bundle_artifacts.dart';
28
+
29
+ export 'src/patch_info.dart';