@_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,298 @@
1
+ /// Faithful port of hot-updater `plugins/supabase/src/supabaseStorage.ts`.
2
+ library;
3
+
4
+ import 'dart:io' as io;
5
+
6
+ import 'package:flutter_patcher_plugin_core/flutter_patcher_plugin_core.dart';
7
+
8
+ import 'supabase_client_adapter.dart';
9
+ import 'supabase_client_http.dart';
10
+ import 'supabase_config.dart';
11
+ import 'supabase_signed_url_batcher.dart';
12
+
13
+ /// Parse a `supabase-storage://` URI into bucket + key.
14
+ ParsedStorageUri parseSupabaseStorageUri(String storageUri) =>
15
+ parseStorageUri(storageUri, 'supabase-storage');
16
+
17
+ /// Configuration for the Supabase storage plugin.
18
+ class SupabaseStorageConfig extends SupabaseServiceRoleConfig {
19
+ final String bucketName;
20
+
21
+ /// Base path where bundles are stored in the bucket.
22
+ final String? basePath;
23
+
24
+ const SupabaseStorageConfig({
25
+ required super.supabaseUrl,
26
+ super.supabaseServiceRoleKey,
27
+ super.supabaseAnonKey,
28
+ super.clientFactory,
29
+ required this.bucketName,
30
+ this.basePath,
31
+ });
32
+ }
33
+
34
+ String _getErrorMessage(Object? error) {
35
+ if (error is Error) return error.toString();
36
+ if (error is Exception) return error.toString();
37
+ if (error is Map && error['message'] is String) {
38
+ return error['message'] as String;
39
+ }
40
+ return error.toString();
41
+ }
42
+
43
+ Future<String> _createSignedUrlOrThrow({
44
+ required SupabaseStorageBucketLike bucket,
45
+ required String key,
46
+ required int expiresIn,
47
+ }) async {
48
+ late Object? error;
49
+ String? signedUrl;
50
+ try {
51
+ final response = await bucket.createSignedUrl(key, expiresIn);
52
+ signedUrl = response.signedUrl;
53
+ error = response.error;
54
+ } catch (thrown) {
55
+ error = thrown;
56
+ }
57
+
58
+ if (error == null && signedUrl != null) {
59
+ return signedUrl;
60
+ }
61
+
62
+ throw StateError(
63
+ 'Failed to generate download URL for "$key": '
64
+ '${_getErrorMessage(error ?? StateError("missing signed URL"))}',
65
+ );
66
+ }
67
+
68
+ Future<void> _verifyObjectCanBeSignedForRuntime({
69
+ required SupabaseStorageBucketLike bucket,
70
+ required String key,
71
+ }) async {
72
+ await _createSignedUrlOrThrow(
73
+ bucket: bucket,
74
+ key: key,
75
+ expiresIn: 3600,
76
+ );
77
+ }
78
+
79
+ /// Supabase-backed universal (node + runtime) storage plugin.
80
+ final supabaseStorage = createUniversalStoragePlugin<SupabaseStorageConfig>(
81
+ name: 'supabaseStorage',
82
+ supportedProtocol: 'supabase-storage',
83
+ factory: (config) {
84
+ final supabase = config.clientFactory != null
85
+ ? config.clientFactory!(config.supabaseUrl, config.resolveKey())
86
+ : createSupabaseHttpClient(config.supabaseUrl, config.resolveKey());
87
+
88
+ final bucket = supabase.storage.from(config.bucketName);
89
+ final getStorageKey = createStorageKeyBuilder(config.basePath);
90
+ final resolveSignedUrl = createSupabaseSignedUrlBatcher(
91
+ createSignedUrls: (bucketName, keys, expiresIn) =>
92
+ supabase.storage.from(bucketName).createSignedUrls(keys, expiresIn),
93
+ expiresIn: 3600,
94
+ formatObjectPath: (bucketName, key) => key,
95
+ );
96
+
97
+ return (
98
+ node: NodeStorageProfileImpl(
99
+ bucket: bucket,
100
+ getStorageKey: getStorageKey,
101
+ bucketName: config.bucketName,
102
+ verifySigned: _verifyObjectCanBeSignedForRuntime,
103
+ createSignedUrlOrThrow: _createSignedUrlOrThrow,
104
+ ),
105
+ runtime: RuntimeStorageProfileImpl(
106
+ bucket: bucket,
107
+ resolveSignedUrl: resolveSignedUrl,
108
+ bucketName: config.bucketName,
109
+ ),
110
+ );
111
+ },
112
+ );
113
+
114
+ class NodeStorageProfileImpl implements NodeStorageProfile {
115
+ NodeStorageProfileImpl({
116
+ required this.bucket,
117
+ required this.getStorageKey,
118
+ required this.bucketName,
119
+ required this.verifySigned,
120
+ required this.createSignedUrlOrThrow,
121
+ });
122
+
123
+ final SupabaseStorageBucketLike bucket;
124
+ final String Function(String, [String, String, String]) getStorageKey;
125
+ final String bucketName;
126
+ final Future<void> Function({
127
+ required SupabaseStorageBucketLike bucket,
128
+ required String key,
129
+ }) verifySigned;
130
+ final Future<String> Function({
131
+ required SupabaseStorageBucketLike bucket,
132
+ required String key,
133
+ required int expiresIn,
134
+ }) createSignedUrlOrThrow;
135
+
136
+ @override
137
+ Future<Map<String, String>> upload(String key, String filePath) async {
138
+ final body = await io.File(filePath).readAsBytes();
139
+ final contentType = getContentType(filePath);
140
+ final filename = filePath.split(io.Platform.pathSeparator).last;
141
+ final storageKey = getStorageKey(key, filename);
142
+
143
+ final upload = await bucket.upload(
144
+ storageKey,
145
+ body,
146
+ contentType: contentType,
147
+ cacheControl: 'max-age=31536000',
148
+ );
149
+ if (upload.error != null) {
150
+ throw StateError(_getErrorMessage(upload.error));
151
+ }
152
+
153
+ await verifySigned(bucket: bucket, key: storageKey);
154
+ final fullPath = upload.data is Map
155
+ ? (upload.data as Map)['fullPath'] as String?
156
+ : null;
157
+ return {
158
+ 'storageUri': 'supabase-storage://${fullPath ?? storageKey}',
159
+ };
160
+ }
161
+
162
+ @override
163
+ Future<bool> exists(String storageUri) async {
164
+ final parsed = parseSupabaseStorageUri(storageUri);
165
+ if (parsed.bucket != bucketName) {
166
+ throw StateError(
167
+ 'Bucket name mismatch: expected "$bucketName", but found '
168
+ '"${parsed.bucket}".',
169
+ );
170
+ }
171
+ final res = await bucket.exists(parsed.key);
172
+ if (res.data == false) return false;
173
+ if (res.error != null) {
174
+ throw StateError(_getErrorMessage(res.error));
175
+ }
176
+ await verifySigned(bucket: bucket, key: parsed.key);
177
+ return res.data as bool;
178
+ }
179
+
180
+ @override
181
+ Future<void> delete(String storageUri) async {
182
+ final parsed = parseSupabaseStorageUri(storageUri);
183
+ if (parsed.bucket != bucketName) {
184
+ throw StateError(
185
+ 'Bucket name mismatch: expected "$bucketName", but found '
186
+ '"${parsed.bucket}".',
187
+ );
188
+ }
189
+ final res = await bucket.remove([parsed.key]);
190
+ if (res.error != null) {
191
+ final msg = res.message ?? _getErrorMessage(res.error);
192
+ if (msg.contains('not found')) {
193
+ throw StateError('Bundle not found');
194
+ }
195
+ throw StateError('Failed to delete bundle: $msg');
196
+ }
197
+ }
198
+
199
+ @override
200
+ Future<void> downloadFile(String storageUri, String filePath) async {
201
+ final parsed = parseSupabaseStorageUri(storageUri);
202
+ if (parsed.bucket != bucketName) {
203
+ throw StateError(
204
+ 'Bucket name mismatch: expected "$bucketName", but found '
205
+ '"${parsed.bucket}".',
206
+ );
207
+ }
208
+ final res = await bucket.download(parsed.key);
209
+ if (res.error != null) {
210
+ throw StateError('Failed to download bundle: ${res.message}');
211
+ }
212
+ if (res.data == null) {
213
+ throw StateError('Failed to download bundle');
214
+ }
215
+ final dir = filePath.substring(0, filePath.lastIndexOf(io.Platform.pathSeparator));
216
+ await io.Directory(dir).create(recursive: true);
217
+ await io.File(filePath).writeAsBytes(res.data!);
218
+ }
219
+
220
+ @override
221
+ Future<List<StorageObject>> listObjects([String? prefix]) async {
222
+ final res = await bucket.list(prefix);
223
+ if (res.error != null) {
224
+ throw StateError(
225
+ 'Failed to list objects: ${_getErrorMessage(res.error)}',
226
+ );
227
+ }
228
+ final data = res.data ?? const <SupabaseStorageObject>[];
229
+ return data
230
+ .map(
231
+ (o) => StorageObject(
232
+ key: o.key,
233
+ storageUri: 'supabase-storage://$bucketName/${o.key}',
234
+ size: o.size,
235
+ lastModifiedAt: o.lastModifiedAt != null
236
+ ? DateTime.tryParse(o.lastModifiedAt!)
237
+ : null,
238
+ ),
239
+ )
240
+ .toList();
241
+ }
242
+
243
+ @override
244
+ Future<void> deleteObjects(List<String> keys) async {
245
+ if (keys.isEmpty) return;
246
+ final res = await bucket.remove(keys);
247
+ if (res.error != null) {
248
+ final msg = res.message ?? _getErrorMessage(res.error);
249
+ if (!msg.contains('not found')) {
250
+ throw StateError('Failed to delete objects: $msg');
251
+ }
252
+ }
253
+ }
254
+ }
255
+
256
+ class RuntimeStorageProfileImpl implements RuntimeStorageProfile {
257
+ RuntimeStorageProfileImpl({
258
+ required this.bucket,
259
+ required this.resolveSignedUrl,
260
+ required this.bucketName,
261
+ });
262
+
263
+ final SupabaseStorageBucketLike bucket;
264
+ final ResolveSignedUrl resolveSignedUrl;
265
+ final String bucketName;
266
+
267
+ @override
268
+ Future<String?> readText(String storageUri) async {
269
+ final parsed = parseSupabaseStorageUri(storageUri);
270
+ if (parsed.bucket != bucketName) {
271
+ throw StateError(
272
+ 'Bucket name mismatch: expected "$bucketName", but found '
273
+ '"${parsed.bucket}".',
274
+ );
275
+ }
276
+ final res = await bucket.download(parsed.key);
277
+ if (res.error != null) {
278
+ final msg = res.message ?? _getErrorMessage(res.error);
279
+ if (msg.contains('not found')) return null;
280
+ throw StateError('Failed to read storage text: $msg');
281
+ }
282
+ if (res.data == null) return null;
283
+ return String.fromCharCodes(res.data!);
284
+ }
285
+
286
+ @override
287
+ Future<Map<String, String>> getDownloadUrl(String storageUri) async {
288
+ final parsed = parseSupabaseStorageUri(storageUri);
289
+ if (parsed.bucket != bucketName) {
290
+ throw StateError(
291
+ 'Bucket name mismatch: expected "$bucketName", but found '
292
+ '"${parsed.bucket}".',
293
+ );
294
+ }
295
+ final signedUrl = await resolveSignedUrl(bucketName, parsed.key);
296
+ return {'fileUrl': signedUrl};
297
+ }
298
+ }
@@ -0,0 +1,130 @@
1
+ /// Faithful port of hot-updater `plugins/supabase/src/types.ts`.
2
+ library;
3
+
4
+ /// Row type from Supabase `bundles` table.
5
+ class SupabaseBundleRow {
6
+ final String id;
7
+ final String channel;
8
+ final bool enabled;
9
+ final bool shouldForceUpdate;
10
+ final String fileHash;
11
+ final String? gitCommitHash;
12
+ final String? message;
13
+ final String platform;
14
+ final String? targetAppVersion;
15
+ final String? fingerprintHash;
16
+ final String storageUri;
17
+ final Map<String, Object?>? metadata;
18
+ final String? manifestStorageUri;
19
+ final String? manifestFileHash;
20
+ final String? assetBaseStorageUri;
21
+ final int? rolloutCohortCount;
22
+ final List<String>? targetCohorts;
23
+
24
+ const SupabaseBundleRow({
25
+ required this.id,
26
+ required this.channel,
27
+ required this.enabled,
28
+ required this.shouldForceUpdate,
29
+ required this.fileHash,
30
+ this.gitCommitHash,
31
+ this.message,
32
+ required this.platform,
33
+ this.targetAppVersion,
34
+ this.fingerprintHash,
35
+ required this.storageUri,
36
+ this.metadata,
37
+ this.manifestStorageUri,
38
+ this.manifestFileHash,
39
+ this.assetBaseStorageUri,
40
+ this.rolloutCohortCount,
41
+ this.targetCohorts,
42
+ });
43
+
44
+ factory SupabaseBundleRow.fromJson(Map<String, dynamic> j) =>
45
+ SupabaseBundleRow(
46
+ id: j['id'] as String,
47
+ channel: j['channel'] as String,
48
+ enabled: j['enabled'] as bool,
49
+ shouldForceUpdate: j['should_force_update'] as bool,
50
+ fileHash: j['file_hash'] as String,
51
+ gitCommitHash: j['git_commit_hash'] as String?,
52
+ message: j['message'] as String?,
53
+ platform: j['platform'] as String,
54
+ targetAppVersion: j['target_app_version'] as String?,
55
+ fingerprintHash: j['fingerprint_hash'] as String?,
56
+ storageUri: j['storage_uri'] as String,
57
+ metadata: j['metadata'] is Map
58
+ ? (j['metadata'] as Map).cast<String, Object?>()
59
+ : null,
60
+ manifestStorageUri: j['manifest_storage_uri'] as String?,
61
+ manifestFileHash: j['manifest_file_hash'] as String?,
62
+ assetBaseStorageUri: j['asset_base_storage_uri'] as String?,
63
+ rolloutCohortCount: j['rollout_cohort_count'] as int?,
64
+ targetCohorts: (j['target_cohorts'] as List?)
65
+ ?.map((e) => e as String)
66
+ .toList(),
67
+ );
68
+
69
+ Map<String, dynamic> toJson() => {
70
+ 'id': id,
71
+ 'channel': channel,
72
+ 'enabled': enabled,
73
+ 'should_force_update': shouldForceUpdate,
74
+ 'file_hash': fileHash,
75
+ 'git_commit_hash': gitCommitHash,
76
+ 'message': message,
77
+ 'platform': platform,
78
+ 'target_app_version': targetAppVersion,
79
+ 'fingerprint_hash': fingerprintHash,
80
+ 'storage_uri': storageUri,
81
+ 'metadata': metadata,
82
+ 'manifest_storage_uri': manifestStorageUri,
83
+ 'manifest_file_hash': manifestFileHash,
84
+ 'asset_base_storage_uri': assetBaseStorageUri,
85
+ 'rollout_cohort_count': rolloutCohortCount,
86
+ 'target_cohorts': targetCohorts,
87
+ };
88
+ }
89
+
90
+ /// Row type from Supabase `bundle_patches` table.
91
+ class SupabaseBundlePatchRow {
92
+ final String id;
93
+ final String bundleId;
94
+ final String baseBundleId;
95
+ final String baseFileHash;
96
+ final String patchFileHash;
97
+ final String patchStorageUri;
98
+ final int orderIndex;
99
+
100
+ const SupabaseBundlePatchRow({
101
+ required this.id,
102
+ required this.bundleId,
103
+ required this.baseBundleId,
104
+ required this.baseFileHash,
105
+ required this.patchFileHash,
106
+ required this.patchStorageUri,
107
+ required this.orderIndex,
108
+ });
109
+
110
+ factory SupabaseBundlePatchRow.fromJson(Map<String, dynamic> j) =>
111
+ SupabaseBundlePatchRow(
112
+ id: j['id'] as String,
113
+ bundleId: j['bundle_id'] as String,
114
+ baseBundleId: j['base_bundle_id'] as String,
115
+ baseFileHash: j['base_file_hash'] as String,
116
+ patchFileHash: j['patch_file_hash'] as String,
117
+ patchStorageUri: j['patch_storage_uri'] as String,
118
+ orderIndex: j['order_index'] as int,
119
+ );
120
+
121
+ Map<String, dynamic> toJson() => {
122
+ 'id': id,
123
+ 'bundle_id': bundleId,
124
+ 'base_bundle_id': baseBundleId,
125
+ 'base_file_hash': baseFileHash,
126
+ 'patch_file_hash': patchFileHash,
127
+ 'patch_storage_uri': patchStorageUri,
128
+ 'order_index': orderIndex,
129
+ };
130
+ }
@@ -0,0 +1,19 @@
1
+ name: flutter_patcher_supabase
2
+ description: Supabase plugin for flutter_patcher — database + storage backends.
3
+ version: 0.1.0
4
+ publish_to: none
5
+
6
+ environment:
7
+ sdk: ">=3.3.0 <4.0.0"
8
+
9
+ dependencies:
10
+ flutter_patcher_core:
11
+ path: ../../packages/core
12
+ flutter_patcher_plugin_core:
13
+ path: ../../plugins/plugin-core
14
+ http: ^1.2.0
15
+ supabase: ^2.14.0
16
+
17
+ dev_dependencies:
18
+ test: ^1.25.0
19
+ lints: ^4.0.0
@@ -0,0 +1,44 @@
1
+ import "jsr:@supabase/functions-js/edge-runtime.d.ts";
2
+ import { createHotUpdater } from "@hot-updater/server";
3
+ import {
4
+ supabaseEdgeFunctionDatabase,
5
+ supabaseEdgeFunctionStorage,
6
+ } from "@hot-updater/supabase";
7
+ import { Hono } from "hono";
8
+
9
+ declare global {
10
+ var HotUpdater: {
11
+ FUNCTION_NAME: string;
12
+ };
13
+ }
14
+
15
+ const functionName = HotUpdater.FUNCTION_NAME;
16
+ const supabaseUrl = Deno.env.get("SUPABASE_URL") ?? "";
17
+ const supabaseServiceRoleKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") ?? "";
18
+ const functionBasePath = `/${functionName}`;
19
+ const hotUpdaterBasePath = "/";
20
+
21
+ const hotUpdater = createHotUpdater({
22
+ database: supabaseEdgeFunctionDatabase({
23
+ supabaseUrl,
24
+ supabaseServiceRoleKey,
25
+ }),
26
+ storages: [
27
+ supabaseEdgeFunctionStorage({
28
+ supabaseUrl,
29
+ supabaseServiceRoleKey,
30
+ }),
31
+ ],
32
+ basePath: hotUpdaterBasePath,
33
+ routes: {
34
+ updateCheck: true,
35
+ bundles: false,
36
+ },
37
+ });
38
+
39
+ const app = new Hono().basePath(functionBasePath);
40
+
41
+ app.get("/ping", (c) => c.text("pong"));
42
+ app.mount(hotUpdaterBasePath, hotUpdater.handler);
43
+
44
+ Deno.serve(app.fetch);