@_nazmiforreal/flutter-ota 0.1.12 → 0.1.13

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 (176) hide show
  1. package/README.md +1 -1
  2. package/bin/flutter-ota-linux-x64 +0 -0
  3. package/dart-src/packages/cli-tools/.dart_tool/package_config.json +46 -76
  4. package/dart-src/packages/cli-tools/.dart_tool/package_graph.json +140 -166
  5. package/dart-src/packages/cli-tools/README.md +1 -1
  6. package/dart-src/packages/cli-tools/lib/flutter_ota_kit_cli.dart +30 -17
  7. package/dart-src/packages/cli-tools/lib/src/backend.dart +30 -31
  8. package/dart-src/packages/cli-tools/lib/src/cli_base.dart +3 -1
  9. package/dart-src/packages/cli-tools/lib/src/commands/build.dart +44 -43
  10. package/dart-src/packages/cli-tools/lib/src/commands/bundle.dart +265 -239
  11. package/dart-src/packages/cli-tools/lib/src/commands/channel.dart +75 -66
  12. package/dart-src/packages/cli-tools/lib/src/commands/config_command.dart +39 -35
  13. package/dart-src/packages/cli-tools/lib/src/commands/console.dart +30 -27
  14. package/dart-src/packages/cli-tools/lib/src/commands/deploy.dart +88 -72
  15. package/dart-src/packages/cli-tools/lib/src/commands/doctor.dart +66 -29
  16. package/dart-src/packages/cli-tools/lib/src/commands/fingerprint.dart +10 -6
  17. package/dart-src/packages/cli-tools/lib/src/commands/init.dart +290 -181
  18. package/dart-src/packages/cli-tools/lib/src/commands/keys.dart +24 -23
  19. package/dart-src/packages/cli-tools/lib/src/commands/migrate.dart +111 -97
  20. package/dart-src/packages/cli-tools/lib/src/commands/pocketbase.dart +237 -0
  21. package/dart-src/packages/cli-tools/lib/src/commands/rollback.dart +43 -34
  22. package/dart-src/packages/cli-tools/lib/src/commands/storage.dart +57 -53
  23. package/dart-src/packages/cli-tools/lib/src/config.dart +227 -93
  24. package/dart-src/packages/cli-tools/lib/src/operations.dart +9 -8
  25. package/dart-src/packages/cli-tools/lib/src/pack.dart +26 -25
  26. package/dart-src/packages/cli-tools/lib/src/pocketbase/data_bootstrap.dart +39 -0
  27. package/dart-src/packages/cli-tools/lib/src/pocketbase/hooks/bundles.pb.js +56 -0
  28. package/dart-src/packages/cli-tools/lib/src/pocketbase/installer.dart +189 -0
  29. package/dart-src/packages/cli-tools/lib/src/pocketbase/process_manager.dart +123 -0
  30. package/dart-src/packages/cli-tools/lib/src/pocketbase/schema_installer.dart +237 -0
  31. package/dart-src/packages/cli-tools/lib/src/runner.dart +36 -15
  32. package/dart-src/packages/cli-tools/lib/src/sign.dart +13 -5
  33. package/dart-src/packages/cli-tools/lib/src/ui/ui.dart +10 -13
  34. package/dart-src/packages/cli-tools/lib/src/util.dart +3 -1
  35. package/dart-src/packages/cli-tools/pubspec.lock +69 -116
  36. package/dart-src/packages/cli-tools/pubspec.yaml +25 -7
  37. package/dart-src/packages/cli-tools/test/backends_test.dart +62 -32
  38. package/dart-src/packages/cli-tools/test/build_pack_test.dart +4 -4
  39. package/dart-src/packages/cli-tools/test/cli_test.dart +89 -42
  40. package/dart-src/packages/cli-tools/test/mocks/mock_aws_s3_client.dart +8 -13
  41. package/dart-src/packages/cli-tools/test/mocks/mock_d1_client.dart +23 -18
  42. package/dart-src/packages/cli-tools/test/mocks/mock_pocketbase_client.dart +197 -0
  43. package/dart-src/packages/cli-tools/test/mocks/mock_postgres_client.dart +70 -46
  44. package/dart-src/packages/cli-tools/test/pocketbase_installer_test.dart +72 -0
  45. package/dart-src/packages/cli-tools/test/pocketbase_schema_test.dart +107 -0
  46. package/dart-src/packages/cli-tools/test/pocketbase_smoke_test.dart +59 -0
  47. package/dart-src/packages/core/README.md +2 -2
  48. package/dart-src/packages/core/example/lib/main.dart +69 -0
  49. package/dart-src/packages/core/lib/flutter_ota_kit_core.dart +1 -1
  50. package/dart-src/packages/core/lib/src/app_update_info.dart +17 -15
  51. package/dart-src/packages/core/lib/src/bundle.dart +102 -94
  52. package/dart-src/packages/core/lib/src/bundle_artifacts.dart +1 -2
  53. package/dart-src/packages/core/lib/src/bundle_patch_artifact.dart +15 -16
  54. package/dart-src/packages/core/lib/src/changed_asset.dart +24 -22
  55. package/dart-src/packages/core/lib/src/get_bundles_args.dart +13 -13
  56. package/dart-src/packages/core/lib/src/metadata.dart +9 -7
  57. package/dart-src/packages/core/lib/src/patch_info.dart +8 -7
  58. package/dart-src/packages/core/lib/src/platform.dart +4 -3
  59. package/dart-src/packages/core/lib/src/rollout.dart +14 -11
  60. package/dart-src/packages/core/lib/src/semver_range.dart +49 -39
  61. package/dart-src/packages/core/lib/src/semver_version.dart +8 -7
  62. package/dart-src/packages/core/lib/src/status.dart +4 -3
  63. package/dart-src/packages/core/lib/src/update_bundle_params.dart +7 -7
  64. package/dart-src/packages/core/lib/src/uuid.dart +1 -2
  65. package/dart-src/packages/core/pubspec.yaml +6 -7
  66. package/dart-src/packages/core/test/rollout_test.dart +15 -8
  67. package/dart-src/packages/core/test/semver_test.dart +5 -2
  68. package/dart-src/packages/core/test/types_test.dart +26 -24
  69. package/dart-src/packages/core/test/uuid_test.dart +3 -1
  70. package/dart-src/packages/core/tool/dbg.dart +1 -0
  71. package/dart-src/plugins/aws/CHANGELOG.md +6 -0
  72. package/dart-src/plugins/aws/example/lib/main.dart +60 -0
  73. package/dart-src/plugins/aws/lib/flutter_ota_kit_aws.dart +5 -1
  74. package/dart-src/plugins/aws/lib/src/aws_cloudfront_client.dart +11 -16
  75. package/dart-src/plugins/aws/lib/src/aws_cloudfront_signer.dart +7 -2
  76. package/dart-src/plugins/aws/lib/src/aws_database.dart +14 -18
  77. package/dart-src/plugins/aws/lib/src/aws_s3_client.dart +23 -18
  78. package/dart-src/plugins/aws/lib/src/aws_ssm_client.dart +5 -4
  79. package/dart-src/plugins/aws/lib/src/aws_storage.dart +2 -3
  80. package/dart-src/plugins/aws/lib/src/aws_storage_profile.dart +10 -16
  81. package/dart-src/plugins/aws/lib/src/with_cloudfront_signed_url.dart +6 -9
  82. package/dart-src/plugins/aws/pubspec.yaml +15 -10
  83. package/dart-src/plugins/aws/test/aws_database_test.dart +51 -57
  84. package/dart-src/plugins/aws/test/mock_aws_s3_client.dart +8 -13
  85. package/dart-src/plugins/cloudflare/CHANGELOG.md +7 -0
  86. package/dart-src/plugins/cloudflare/example/lib/main.dart +62 -0
  87. package/dart-src/plugins/cloudflare/lib/flutter_ota_kit_cloudflare.dart +4 -2
  88. package/dart-src/plugins/cloudflare/lib/src/cloudflare_worker_database.dart +3 -3
  89. package/dart-src/plugins/cloudflare/lib/src/d1_build_where.dart +3 -2
  90. package/dart-src/plugins/cloudflare/lib/src/d1_bundle_mapper.dart +24 -27
  91. package/dart-src/plugins/cloudflare/lib/src/d1_database_plugin.dart +22 -30
  92. package/dart-src/plugins/cloudflare/lib/src/r2_config.dart +1 -1
  93. package/dart-src/plugins/cloudflare/lib/src/r2_s3_client.dart +21 -29
  94. package/dart-src/plugins/cloudflare/lib/src/r2_storage.dart +7 -7
  95. package/dart-src/plugins/cloudflare/lib/src/r2_storage_profile.dart +7 -15
  96. package/dart-src/plugins/cloudflare/pubspec.yaml +14 -9
  97. package/dart-src/plugins/cloudflare/test/cloudflare_worker_database_test.dart +46 -42
  98. package/dart-src/plugins/cloudflare/test/d1_database_test.dart +100 -90
  99. package/dart-src/plugins/cloudflare/test/mock_d1_client.dart +23 -18
  100. package/dart-src/plugins/cloudflare/test/mock_r2_client.dart +8 -13
  101. package/dart-src/plugins/cloudflare/test/r2_storage_test.dart +1 -4
  102. package/dart-src/plugins/plugin-core/CHANGELOG.md +16 -0
  103. package/dart-src/plugins/plugin-core/example/lib/main.dart +61 -0
  104. package/dart-src/plugins/plugin-core/lib/flutter_ota_kit_plugin_core.dart +3 -7
  105. package/dart-src/plugins/plugin-core/lib/src/asset_storage_layout.dart +5 -5
  106. package/dart-src/plugins/plugin-core/lib/src/bundle_helpers.dart +23 -0
  107. package/dart-src/plugins/plugin-core/lib/src/bundle_storage_layout.dart +16 -10
  108. package/dart-src/plugins/plugin-core/lib/src/bundle_unit_of_work.dart +31 -26
  109. package/dart-src/plugins/plugin-core/lib/src/calculate_pagination.dart +5 -1
  110. package/dart-src/plugins/plugin-core/lib/src/content_addressed_assets.dart +1 -1
  111. package/dart-src/plugins/plugin-core/lib/src/create_blob_database_plugin.dart +78 -71
  112. package/dart-src/plugins/plugin-core/lib/src/create_database_plugin.dart +56 -35
  113. package/dart-src/plugins/plugin-core/lib/src/create_storage_plugin.dart +10 -13
  114. package/dart-src/plugins/plugin-core/lib/src/paginate_bundles.dart +30 -17
  115. package/dart-src/plugins/plugin-core/lib/src/parse_storage_uri.dart +3 -1
  116. package/dart-src/plugins/plugin-core/lib/src/query_bundles.dart +5 -3
  117. package/dart-src/plugins/plugin-core/lib/src/request_update_bundle_state.dart +2 -4
  118. package/dart-src/plugins/plugin-core/lib/src/storage_helpers.dart +18 -0
  119. package/dart-src/plugins/plugin-core/lib/src/types.dart +7 -6
  120. package/dart-src/plugins/plugin-core/pubspec.yaml +12 -9
  121. package/dart-src/plugins/plugin-core/test/asset_storage_layout_test.dart +13 -9
  122. package/dart-src/plugins/plugin-core/test/bundle_unit_of_work_test.dart +28 -15
  123. package/dart-src/plugins/plugin-core/test/create_blob_database_plugin_test.dart +254 -122
  124. package/dart-src/plugins/plugin-core/test/create_database_plugin_test.dart +36 -32
  125. package/dart-src/plugins/plugin-core/test/create_storage_plugin_test.dart +18 -15
  126. package/dart-src/plugins/plugin-core/test/generate_min_bundle_id_test.dart +8 -3
  127. package/dart-src/plugins/plugin-core/test/get_update_info_test.dart +682 -693
  128. package/dart-src/plugins/plugin-core/test/helpers_test.dart +45 -21
  129. package/dart-src/plugins/plugin-core/test/query_bundles_test.dart +76 -13
  130. package/dart-src/plugins/plugin-core/test/uuidv7_test.dart +20 -9
  131. package/dart-src/plugins/pocketbase/CHANGELOG.md +8 -0
  132. package/dart-src/plugins/pocketbase/LICENSE +21 -0
  133. package/dart-src/plugins/pocketbase/README.md +54 -0
  134. package/dart-src/plugins/pocketbase/example/lib/main.dart +62 -0
  135. package/dart-src/plugins/pocketbase/lib/flutter_ota_kit_pocketbase.dart +28 -0
  136. package/dart-src/plugins/pocketbase/lib/src/pocketbase_bundle_mapper.dart +121 -0
  137. package/dart-src/plugins/pocketbase/lib/src/pocketbase_client.dart +349 -0
  138. package/dart-src/plugins/pocketbase/lib/src/pocketbase_config.dart +61 -0
  139. package/dart-src/plugins/pocketbase/lib/src/pocketbase_database.dart +278 -0
  140. package/dart-src/plugins/pocketbase/lib/src/pocketbase_storage.dart +255 -0
  141. package/dart-src/plugins/pocketbase/pubspec.yaml +26 -0
  142. package/dart-src/plugins/pocketbase/test/pb_uri_test.dart +12 -0
  143. package/dart-src/plugins/postgres/CHANGELOG.md +6 -0
  144. package/dart-src/plugins/postgres/example/lib/main.dart +67 -0
  145. package/dart-src/plugins/postgres/lib/flutter_ota_kit_postgres.dart +3 -6
  146. package/dart-src/plugins/postgres/lib/src/postgres_bundle_mapper.dart +32 -52
  147. package/dart-src/plugins/postgres/lib/src/postgres_config.dart +3 -1
  148. package/dart-src/plugins/postgres/lib/src/postgres_database.dart +28 -38
  149. package/dart-src/plugins/postgres/lib/src/postgres_get_update_info.dart +16 -14
  150. package/dart-src/plugins/postgres/lib/src/postgres_storage.dart +26 -14
  151. package/dart-src/plugins/postgres/lib/src/postgres_types.dart +27 -28
  152. package/dart-src/plugins/postgres/pubspec.yaml +14 -9
  153. package/dart-src/plugins/postgres/test/mock_postgres_client.dart +62 -39
  154. package/dart-src/plugins/postgres/test/postgres_bundle_mapper_test.dart +3 -4
  155. package/dart-src/plugins/postgres/test/postgres_database_test.dart +102 -73
  156. package/dart-src/plugins/postgres/test/postgres_storage_runtime_test.dart +60 -0
  157. package/dart-src/plugins/supabase/CHANGELOG.md +17 -0
  158. package/dart-src/plugins/supabase/README.md +2 -2
  159. package/dart-src/plugins/supabase/example/lib/main.dart +63 -0
  160. package/dart-src/plugins/supabase/lib/flutter_ota_kit_supabase.dart +2 -0
  161. package/dart-src/plugins/supabase/lib/src/error_message.dart +11 -0
  162. package/dart-src/plugins/supabase/lib/src/supabase_bundle_mapper.dart +34 -58
  163. package/dart-src/plugins/supabase/lib/src/supabase_client_adapter.dart +3 -13
  164. package/dart-src/plugins/supabase/lib/src/supabase_client_http.dart +34 -52
  165. package/dart-src/plugins/supabase/lib/src/supabase_database.dart +38 -30
  166. package/dart-src/plugins/supabase/lib/src/supabase_edge_function_storage.dart +21 -29
  167. package/dart-src/plugins/supabase/lib/src/supabase_signed_url_batcher.dart +7 -19
  168. package/dart-src/plugins/supabase/lib/src/supabase_storage.dart +18 -29
  169. package/dart-src/plugins/supabase/lib/src/types.dart +26 -26
  170. package/dart-src/plugins/supabase/pubspec.yaml +14 -9
  171. package/dart-src/plugins/supabase/test/mock_supabase_client.dart +57 -57
  172. package/dart-src/plugins/supabase/test/supabase_bundle_mapper_test.dart +30 -27
  173. package/dart-src/plugins/supabase/test/supabase_database_test.dart +26 -35
  174. package/dart-src/plugins/supabase/test/supabase_storage_test.dart +27 -34
  175. package/package.json +3 -3
  176. package/scripts/vendor.sh +1 -1
@@ -8,48 +8,49 @@ import 'package:flutter_ota_kit_core/flutter_ota_kit_core.dart'
8
8
  UpdateStatus,
9
9
  nilUuid;
10
10
  import 'package:flutter_ota_kit_plugin_core/flutter_ota_kit_plugin_core.dart'
11
- show
12
- DatabaseBundleQueryOptions,
13
- DatabaseBundleQueryWhere,
14
- Paginated;
11
+ show DatabaseBundleQueryOptions, DatabaseBundleQueryWhere, Paginated;
15
12
  import 'package:test/test.dart';
16
13
 
17
14
  import 'mock_postgres_client.dart';
18
15
 
19
- Bundle _bundle(String id,
20
- {String channel = 'production',
21
- String? fingerprintHash}) =>
22
- Bundle(
23
- id: id,
24
- platform: Platform.android,
25
- shouldForceUpdate: false,
26
- enabled: true,
27
- fileHash: 'file-hash-$id',
28
- channel: channel,
29
- storageUri: 'file:///bundle-$id',
30
- targetAppVersion: '1.0.0',
31
- message: null,
32
- fingerprintHash: fingerprintHash,
33
- metadata: null,
34
- manifestStorageUri: null,
35
- manifestFileHash: null,
36
- assetBaseStorageUri: null,
37
- patches: const [],
38
- patchBaseBundleId: null,
39
- patchBaseFileHash: null,
40
- patchFileHash: null,
41
- patchStorageUri: null,
42
- rolloutCohortCount: 1000,
43
- targetCohorts: null,
44
- );
16
+ Bundle _bundle(
17
+ String id, {
18
+ String channel = 'production',
19
+ String? fingerprintHash,
20
+ }) => Bundle(
21
+ id: id,
22
+ platform: Platform.android,
23
+ shouldForceUpdate: false,
24
+ enabled: true,
25
+ fileHash: 'file-hash-$id',
26
+ channel: channel,
27
+ storageUri: 'file:///bundle-$id',
28
+ targetAppVersion: '1.0.0',
29
+ message: null,
30
+ fingerprintHash: fingerprintHash,
31
+ metadata: null,
32
+ manifestStorageUri: null,
33
+ manifestFileHash: null,
34
+ assetBaseStorageUri: null,
35
+ patches: const [],
36
+ patchBaseBundleId: null,
37
+ patchBaseFileHash: null,
38
+ patchFileHash: null,
39
+ patchStorageUri: null,
40
+ rolloutCohortCount: 1000,
41
+ targetCohorts: null,
42
+ );
45
43
 
46
44
  void main() {
47
45
  group('postgresDatabase', () {
48
46
  test('getChannels returns distinct channels', () async {
49
47
  final plugin = newPlugin();
50
- await plugin.appendBundle(_bundle('018f0000-0000-7000-8000-000000000001'));
51
- await plugin.appendBundle(_bundle('018f0000-0000-7000-8000-000000000002',
52
- channel: 'staging'));
48
+ await plugin.appendBundle(
49
+ _bundle('018f0000-0000-7000-8000-000000000001'),
50
+ );
51
+ await plugin.appendBundle(
52
+ _bundle('018f0000-0000-7000-8000-000000000002', channel: 'staging'),
53
+ );
53
54
  await plugin.commitBundle();
54
55
 
55
56
  final channels = await plugin.getChannels();
@@ -59,8 +60,12 @@ void main() {
59
60
 
60
61
  test('getBundles returns bundles and pagination', () async {
61
62
  final plugin = newPlugin();
62
- await plugin.appendBundle(_bundle('018f0000-0000-7000-8000-000000000001'));
63
- await plugin.appendBundle(_bundle('018f0000-0000-7000-8000-000000000002'));
63
+ await plugin.appendBundle(
64
+ _bundle('018f0000-0000-7000-8000-000000000001'),
65
+ );
66
+ await plugin.appendBundle(
67
+ _bundle('018f0000-0000-7000-8000-000000000002'),
68
+ );
64
69
  await plugin.commitBundle();
65
70
 
66
71
  final result = await plugin.getBundles(
@@ -73,9 +78,12 @@ void main() {
73
78
 
74
79
  test('getBundles filters by channel', () async {
75
80
  final plugin = newPlugin();
76
- await plugin.appendBundle(_bundle('018f0000-0000-7000-8000-000000000001'));
77
- await plugin.appendBundle(_bundle('018f0000-0000-7000-8000-000000000002',
78
- channel: 'staging'));
81
+ await plugin.appendBundle(
82
+ _bundle('018f0000-0000-7000-8000-000000000001'),
83
+ );
84
+ await plugin.appendBundle(
85
+ _bundle('018f0000-0000-7000-8000-000000000002', channel: 'staging'),
86
+ );
79
87
  await plugin.commitBundle();
80
88
 
81
89
  final result = await plugin.getBundles(
@@ -88,24 +96,34 @@ void main() {
88
96
  expect(result.data.first.channel, 'staging');
89
97
  });
90
98
 
91
- test('getBundleById returns null for missing, bundle for existing',
92
- () async {
93
- final plugin = newPlugin();
94
- await plugin.appendBundle(_bundle('018f0000-0000-7000-8000-000000000001'));
95
- await plugin.commitBundle();
96
-
97
- expect(await plugin.getBundleById('does-not-exist'), isNull);
98
- final got = await plugin.getBundleById('018f0000-0000-7000-8000-000000000001');
99
- expect(got, isNotNull);
100
- expect(got!.id, '018f0000-0000-7000-8000-000000000001');
101
- });
99
+ test(
100
+ 'getBundleById returns null for missing, bundle for existing',
101
+ () async {
102
+ final plugin = newPlugin();
103
+ await plugin.appendBundle(
104
+ _bundle('018f0000-0000-7000-8000-000000000001'),
105
+ );
106
+ await plugin.commitBundle();
107
+
108
+ expect(await plugin.getBundleById('does-not-exist'), isNull);
109
+ final got = await plugin.getBundleById(
110
+ '018f0000-0000-7000-8000-000000000001',
111
+ );
112
+ expect(got, isNotNull);
113
+ expect(got!.id, '018f0000-0000-7000-8000-000000000001');
114
+ },
115
+ );
102
116
 
103
117
  test('commitBundle append -> update -> delete reflects in store', () async {
104
118
  final plugin = newPlugin();
105
- await plugin.appendBundle(_bundle('018f0000-0000-7000-8000-000000000001'));
119
+ await plugin.appendBundle(
120
+ _bundle('018f0000-0000-7000-8000-000000000001'),
121
+ );
106
122
  await plugin.commitBundle();
107
123
 
108
- var got = await plugin.getBundleById('018f0000-0000-7000-8000-000000000001');
124
+ var got = await plugin.getBundleById(
125
+ '018f0000-0000-7000-8000-000000000001',
126
+ );
109
127
  expect(got!.enabled, true);
110
128
 
111
129
  await plugin.updateBundle(got.id, {'enabled': false});
@@ -121,30 +139,39 @@ void main() {
121
139
  );
122
140
  });
123
141
 
124
- test('getUpdateInfo via RPC returns UpdateInfo for compatible bundle',
125
- () async {
126
- final plugin = newPlugin();
127
- await plugin.appendBundle(_bundle('018f0000-0000-7000-8000-000000000001'));
128
- await plugin.commitBundle();
129
-
130
- final info = await plugin.getUpdateInfo(
131
- AppVersionGetBundlesArgs(
132
- appVersion: '1.0.0',
133
- bundleId: nilUuid,
134
- channel: 'production',
135
- minBundleId: nilUuid,
136
- platform: Platform.android,
137
- ),
138
- );
139
- expect(info, isA<UpdateInfo>());
140
- expect(info!.status, UpdateStatus.update);
141
- expect(info.storageUri, 'file:///bundle-018f0000-0000-7000-8000-000000000001');
142
- });
142
+ test(
143
+ 'getUpdateInfo via RPC returns UpdateInfo for compatible bundle',
144
+ () async {
145
+ final plugin = newPlugin();
146
+ await plugin.appendBundle(
147
+ _bundle('018f0000-0000-7000-8000-000000000001'),
148
+ );
149
+ await plugin.commitBundle();
150
+
151
+ final info = await plugin.getUpdateInfo(
152
+ AppVersionGetBundlesArgs(
153
+ appVersion: '1.0.0',
154
+ bundleId: nilUuid,
155
+ channel: 'production',
156
+ minBundleId: nilUuid,
157
+ platform: Platform.android,
158
+ ),
159
+ );
160
+ expect(info, isA<UpdateInfo>());
161
+ expect(info!.status, UpdateStatus.update);
162
+ expect(
163
+ info.storageUri,
164
+ 'file:///bundle-018f0000-0000-7000-8000-000000000001',
165
+ );
166
+ },
167
+ );
143
168
 
144
169
  test('getUpdateInfo via fingerprint returns matching bundle', () async {
145
170
  final plugin = newPlugin();
146
- final fpBundle = _bundle('018f0000-0000-7000-8000-000000000001',
147
- fingerprintHash: 'fp-abc');
171
+ final fpBundle = _bundle(
172
+ '018f0000-0000-7000-8000-000000000001',
173
+ fingerprintHash: 'fp-abc',
174
+ );
148
175
  await plugin.appendBundle(fpBundle);
149
176
  await plugin.commitBundle();
150
177
 
@@ -163,7 +190,9 @@ void main() {
163
190
 
164
191
  test('getUpdateInfo returns null when no compatible bundle', () async {
165
192
  final plugin = newPlugin();
166
- await plugin.appendBundle(_bundle('018f0000-0000-7000-8000-000000000001'));
193
+ await plugin.appendBundle(
194
+ _bundle('018f0000-0000-7000-8000-000000000001'),
195
+ );
167
196
  await plugin.commitBundle();
168
197
 
169
198
  final info = await plugin.getUpdateInfo(
@@ -0,0 +1,60 @@
1
+ import 'package:flutter_ota_kit_postgres/flutter_ota_kit_postgres.dart';
2
+ import 'package:test/test.dart';
3
+
4
+ void main() {
5
+ group('Postgres storage runtime', () {
6
+ test(
7
+ 'getDownloadUrl throws a clear error when no servingBaseUrl is set',
8
+ () async {
9
+ final storage = postgresStorage(
10
+ PostgresStorageConfig(
11
+ db: PostgresConfig(
12
+ host: 'mock',
13
+ database: 'mock',
14
+ username: 'u',
15
+ password: 'p',
16
+ ),
17
+ // servingBaseUrl intentionally omitted.
18
+ ),
19
+ );
20
+ final node = storage.profiles.node!;
21
+ // Use a dummy storageUri just to exercise the runtime path.
22
+ expect(
23
+ () => storage.profiles.runtime!.getDownloadUrl(
24
+ 'postgres://bundles/abc/zip',
25
+ ),
26
+ throwsA(
27
+ isA<StateError>().having(
28
+ (e) => e.message,
29
+ 'message',
30
+ contains('servingBaseUrl'),
31
+ ),
32
+ ),
33
+ );
34
+ // The node profile must still work because it goes through the DB.
35
+ expect(node, isNotNull);
36
+ },
37
+ );
38
+
39
+ test(
40
+ 'getDownloadUrl builds an http url when servingBaseUrl is set',
41
+ () async {
42
+ final storage = postgresStorage(
43
+ PostgresStorageConfig(
44
+ db: PostgresConfig(
45
+ host: 'mock',
46
+ database: 'mock',
47
+ username: 'u',
48
+ password: 'p',
49
+ ),
50
+ servingBaseUrl: 'https://cdn.example.com',
51
+ ),
52
+ );
53
+ final url = (await storage.profiles.runtime!.getDownloadUrl(
54
+ 'postgres://bundles/abc/zip',
55
+ ))['fileUrl'];
56
+ expect(url, 'https://cdn.example.com/bundles/abc/zip');
57
+ },
58
+ );
59
+ });
60
+ }
@@ -1,3 +1,20 @@
1
+ ## 0.1.2
2
+
3
+ ### Changed
4
+
5
+ - **De-duplicated the error-to-message helper.** `_getErrorMessage` (supabase_storage,
6
+ supabase_signed_url_batcher) and `_errorToString` (supabase_edge_function_storage)
7
+ were byte-identical copies. They now all call one shared `errorMessage(Object?)` in
8
+ `lib/src/error_message.dart`. No behavior change; the internal names are gone.
9
+
10
+ ## 0.1.1
11
+
12
+ - fix(supabase): the `migrate` RLS migration now creates the public-read policies
13
+ (`allow public read bundles`, `allow public read bundle_patches`, `public bundles read`)
14
+ so the anon-key app can actually fetch bundles. Previously RLS was enabled with zero
15
+ policies, so every fresh `flutter-ota migrate supabase` produced a DB where clients
16
+ received no OTA.
17
+
1
18
  ## 0.1.0
2
19
 
3
20
  - Initial release of `flutter_ota_kit_supabase` as part of the flutter_ota_kit OTA toolkit.
@@ -1,6 +1,6 @@
1
1
  # flutter_ota_kit_supabase
2
2
 
3
- Supabase backend for [flutter_ota_kit](https://github.com/HYPER12755/flutter_ota_kit).
3
+ Supabase backend for [flutter_ota_kit](https://github.com/HYPER12755/flutter-ota-kit).
4
4
  Implements the bundle **database** (PostgREST `bundles` table) and **storage**
5
5
  (signed URLs) used both by the `flutter-ota` CLI and by the Flutter SDK's
6
6
  `FlutterPatcher.configureSupabase(...)`.
@@ -46,7 +46,7 @@ FlutterPatcher.configureSupabase(SupabaseUpdateConfig(
46
46
  ));
47
47
  ```
48
48
 
49
- See [doc/backends.md](https://github.com/HYPER12755/flutter_ota_kit/blob/main/doc/backends.md).
49
+ See [doc/backends.md](https://github.com/HYPER12755/flutter-ota-kit/blob/main/doc/backends.md).
50
50
 
51
51
  ## License
52
52
 
@@ -0,0 +1,63 @@
1
+ // flutter_ota_kit_supabase example
2
+ //
3
+ // `flutter_ota_kit_supabase` is a Supabase backend for `flutter_ota_kit`.
4
+ // It provides two factories — `supabaseDatabase` and `supabaseStorage` —
5
+ // that read/write the `bundles` table and patch files in a Supabase
6
+ // Storage bucket.
7
+ //
8
+ // Most apps don't import this package directly; they wire it through
9
+ // `flutter_ota_kit.configureSupabase(...)` in their app boot path. This
10
+ // file shows the low-level config types in case you're writing a custom
11
+ // server, a migration script, or a CLI extension.
12
+ //
13
+ // Run: `dart run example/main.dart`
14
+
15
+ import 'package:flutter_ota_kit_supabase/flutter_ota_kit_supabase.dart';
16
+
17
+ void main() {
18
+ // ── 1. Database config (PostgREST + service-role key). ─────────────
19
+ // The `supabaseServiceRoleKey` is server-side only — never ship it in
20
+ // a Flutter app. Use `supabaseAnonKey` + RLS policies instead.
21
+ const dbConfig = SupabaseServiceRoleConfig(
22
+ supabaseUrl: 'https://your-project.supabase.co',
23
+ supabaseServiceRoleKey: 'eyJ...', // server-side only
24
+ );
25
+ print('Supabase URL: ${dbConfig.supabaseUrl}');
26
+
27
+ // ── 2. Storage config (adds bucket + base path). ──────────────────
28
+ // The `basePath` is the prefix inside the bucket where bundles live.
29
+ // The default convention is `bundles/{id}/patch.zip`.
30
+ const storageConfig = SupabaseStorageConfig(
31
+ supabaseUrl: 'https://your-project.supabase.co',
32
+ supabaseServiceRoleKey: 'eyJ...',
33
+ bucketName: 'flutter-ota-bundles',
34
+ basePath: 'bundles',
35
+ );
36
+ print('Bucket: ${storageConfig.bucketName}');
37
+ print('Base path: ${storageConfig.basePath}');
38
+
39
+ // ── 3. Row-to-Bundle mapping (server-side helper). ────────────────
40
+ // Use this when reading bundles from PostgREST and passing them to
41
+ // the SDK's `PatchInfo.fromJson`. The mapper handles both camelCase
42
+ // and snake_case column names.
43
+ const row = SupabaseBundleRow(
44
+ id: '01a059a6-7d3c-4f3e-8a2c-9c2e3a4b5d6e',
45
+ channel: 'production',
46
+ enabled: true,
47
+ shouldForceUpdate: false,
48
+ fileHash: '7c4a8d09ca3762af61e59520943dc26494f8941b',
49
+ platform: 'android',
50
+ storageUri:
51
+ 'supabase-storage://flutter-ota-bundles/'
52
+ '01a059a6/patch.zip',
53
+ targetAppVersion: '1.0.0',
54
+ rolloutCohortCount: 100,
55
+ );
56
+ final bundle = mapRowToBundle(row);
57
+ print('Mapped bundle: ${bundle.id} (${bundle.storageUri})');
58
+
59
+ // ── 4. URL parsing helpers. ───────────────────────────────────────
60
+ final uri = parseSupabaseStorageUri(bundle.storageUri);
61
+ print('Bucket: ${uri.bucket}');
62
+ print('Key: ${uri.key}');
63
+ }
@@ -19,3 +19,5 @@ export 'src/supabase_edge_function_database.dart'
19
19
  show supabaseEdgeFunctionDatabase, SupabaseEdgeFunctionDatabaseConfig;
20
20
  export 'src/supabase_edge_function_storage.dart'
21
21
  show supabaseEdgeFunctionStorage, SupabaseEdgeFunctionStorageConfig;
22
+ export 'src/types.dart' show SupabaseBundleRow;
23
+ export 'src/supabase_bundle_mapper.dart' show mapRowToBundle;
@@ -0,0 +1,11 @@
1
+ /// Shared error-to-message helper used across the supabase plugin
2
+ /// (storage, signed-url batcher, edge-function storage). Identical copies used
3
+ /// to live in three files; they now all call this one.
4
+ String errorMessage(Object? error) {
5
+ if (error is Error) return error.toString();
6
+ if (error is Exception) return error.toString();
7
+ if (error is Map && error['message'] is String) {
8
+ return error['message'] as String;
9
+ }
10
+ return error.toString();
11
+ }
@@ -1,10 +1,11 @@
1
1
  /// Faithful port of hot-updater `plugins/supabase/src/supabaseBundleMapper.ts`.
2
2
  library;
3
3
 
4
- import 'dart:convert';
5
-
6
4
  import 'package:flutter_ota_kit_core/flutter_ota_kit_core.dart';
7
5
 
6
+ import 'package:flutter_ota_kit_plugin_core/flutter_ota_kit_plugin_core.dart'
7
+ show buildBundlePatchId, normalizeMetadata;
8
+
8
9
  import 'types.dart';
9
10
 
10
11
  const String bundleSelectColumns =
@@ -13,34 +14,12 @@ const String bundleSelectColumns =
13
14
  'storage_uri, metadata, manifest_storage_uri, manifest_file_hash, '
14
15
  'asset_base_storage_uri, rollout_cohort_count, target_cohorts';
15
16
 
16
- /// Normalize metadata from various shapes to a plain map.
17
- Map<String, Object?>? _normalizeMetadata(Object? value) {
18
- if (value == null) return null;
19
-
20
- if (value is String) {
21
- try {
22
- final parsed = jsonDecode(value);
23
- return _normalizeMetadata(parsed);
24
- } catch (_) {
25
- return null;
26
- }
27
- }
28
-
29
- if (value is Map) return value.cast<String, Object?>();
30
-
31
- return null;
32
- }
33
-
34
- /// Build the compound primary key for a patch row.
35
- String _buildBundlePatchId(String bundleId, String baseBundleId) =>
36
- '$bundleId:$baseBundleId';
37
-
38
17
  /// Map a Supabase bundle row + optional patch rows to a [Bundle].
39
18
  Bundle mapRowToBundle(
40
19
  SupabaseBundleRow row, [
41
20
  List<SupabaseBundlePatchRow> patchRows = const [],
42
21
  ]) {
43
- final rawMetadata = _normalizeMetadata(row.metadata);
22
+ final rawMetadata = normalizeMetadata(row.metadata);
44
23
  final patches = patchRows.toList()
45
24
  ..sort((a, b) {
46
25
  final cmp = a.orderIndex.compareTo(b.orderIndex);
@@ -49,16 +28,17 @@ Bundle mapRowToBundle(
49
28
  });
50
29
 
51
30
  final patchArtifacts = patches
52
- .map((p) => BundlePatchArtifact(
53
- baseBundleId: p.baseBundleId,
54
- baseFileHash: p.baseFileHash,
55
- patchFileHash: p.patchFileHash,
56
- patchStorageUri: p.patchStorageUri,
57
- ))
31
+ .map(
32
+ (p) => BundlePatchArtifact(
33
+ baseBundleId: p.baseBundleId,
34
+ baseFileHash: p.baseFileHash,
35
+ patchFileHash: p.patchFileHash,
36
+ patchStorageUri: p.patchStorageUri,
37
+ ),
38
+ )
58
39
  .toList();
59
40
 
60
- final primaryPatch =
61
- patchArtifacts.isNotEmpty ? patchArtifacts.first : null;
41
+ final primaryPatch = patchArtifacts.isNotEmpty ? patchArtifacts.first : null;
62
42
 
63
43
  return Bundle(
64
44
  id: row.id,
@@ -72,8 +52,7 @@ Bundle mapRowToBundle(
72
52
  targetAppVersion: row.targetAppVersion,
73
53
  fingerprintHash: row.fingerprintHash,
74
54
  storageUri: row.storageUri,
75
- metadata:
76
- rawMetadata != null ? BundleMetadata.fromJson(rawMetadata) : null,
55
+ metadata: rawMetadata != null ? BundleMetadata.fromJson(rawMetadata) : null,
77
56
  manifestStorageUri: row.manifestStorageUri,
78
57
  manifestFileHash: row.manifestFileHash,
79
58
  assetBaseStorageUri: row.assetBaseStorageUri,
@@ -82,33 +61,31 @@ Bundle mapRowToBundle(
82
61
  patchBaseFileHash: primaryPatch?.baseFileHash,
83
62
  patchFileHash: primaryPatch?.patchFileHash,
84
63
  patchStorageUri: primaryPatch?.patchStorageUri,
85
- rolloutCohortCount:
86
- row.rolloutCohortCount ?? defaultRolloutCohortCount,
64
+ rolloutCohortCount: row.rolloutCohortCount ?? defaultRolloutCohortCount,
87
65
  targetCohorts: row.targetCohorts,
88
66
  );
89
67
  }
90
68
 
91
69
  /// Map a [Bundle] to a Supabase row for upsert.
92
70
  SupabaseBundleRow bundleToRow(Bundle bundle) => SupabaseBundleRow(
93
- id: bundle.id,
94
- channel: bundle.channel,
95
- enabled: bundle.enabled,
96
- shouldForceUpdate: bundle.shouldForceUpdate,
97
- fileHash: bundle.fileHash,
98
- gitCommitHash: bundle.gitCommitHash,
99
- message: bundle.message,
100
- platform: bundle.platform.value,
101
- targetAppVersion: bundle.targetAppVersion,
102
- fingerprintHash: bundle.fingerprintHash,
103
- storageUri: bundle.storageUri,
104
- metadata: stripBundleArtifactMetadata(bundle.metadata?.toJson()),
105
- manifestStorageUri: getManifestStorageUri(bundle),
106
- manifestFileHash: getManifestFileHash(bundle),
107
- assetBaseStorageUri: getAssetBaseStorageUri(bundle),
108
- rolloutCohortCount:
109
- bundle.rolloutCohortCount ?? defaultRolloutCohortCount,
110
- targetCohorts: bundle.targetCohorts,
111
- );
71
+ id: bundle.id,
72
+ channel: bundle.channel,
73
+ enabled: bundle.enabled,
74
+ shouldForceUpdate: bundle.shouldForceUpdate,
75
+ fileHash: bundle.fileHash,
76
+ gitCommitHash: bundle.gitCommitHash,
77
+ message: bundle.message,
78
+ platform: bundle.platform.value,
79
+ targetAppVersion: bundle.targetAppVersion,
80
+ fingerprintHash: bundle.fingerprintHash,
81
+ storageUri: bundle.storageUri,
82
+ metadata: stripBundleArtifactMetadata(bundle.metadata?.toJson()),
83
+ manifestStorageUri: getManifestStorageUri(bundle),
84
+ manifestFileHash: getManifestFileHash(bundle),
85
+ assetBaseStorageUri: getAssetBaseStorageUri(bundle),
86
+ rolloutCohortCount: bundle.rolloutCohortCount ?? defaultRolloutCohortCount,
87
+ targetCohorts: bundle.targetCohorts,
88
+ );
112
89
 
113
90
  /// Map a [Bundle] to its patch rows for upsert.
114
91
  List<SupabaseBundlePatchRow> bundleToPatchRows(Bundle bundle) {
@@ -116,8 +93,7 @@ List<SupabaseBundlePatchRow> bundleToPatchRows(Bundle bundle) {
116
93
  return [
117
94
  for (var i = 0; i < patchArtifacts.length; i++)
118
95
  SupabaseBundlePatchRow(
119
- id: _buildBundlePatchId(
120
- bundle.id, patchArtifacts[i].baseBundleId),
96
+ id: buildBundlePatchId(bundle.id, patchArtifacts[i].baseBundleId),
121
97
  bundleId: bundle.id,
122
98
  baseBundleId: patchArtifacts[i].baseBundleId,
123
99
  baseFileHash: patchArtifacts[i].baseFileHash,
@@ -7,18 +7,11 @@ library;
7
7
 
8
8
  /// Minimal PostgREST query-builder surface used by the database plugin.
9
9
  abstract class SupabaseQueryBuilderLike {
10
- SupabaseFilterBuilderLike select(
11
- String columns, {
12
- bool? count,
13
- bool? head,
14
- });
10
+ SupabaseFilterBuilderLike select(String columns, {bool? count, bool? head});
15
11
 
16
12
  SupabaseFilterBuilderLike delete();
17
13
 
18
- Future<dynamic> upsert(
19
- dynamic values, {
20
- String? onConflict,
21
- });
14
+ Future<dynamic> upsert(dynamic values, {String? onConflict});
22
15
  }
23
16
 
24
17
  /// Minimal PostgREST filter-builder surface.
@@ -47,10 +40,7 @@ abstract class SupabaseResponseLike {
47
40
 
48
41
  /// Storage bucket surface used by the storage plugin.
49
42
  abstract class SupabaseStorageBucketLike {
50
- Future<SupabaseSignedUrlResult> createSignedUrl(
51
- String path,
52
- int expiresIn,
53
- );
43
+ Future<SupabaseSignedUrlResult> createSignedUrl(String path, int expiresIn);
54
44
 
55
45
  Future<SupabaseSignedUrlListResult> createSignedUrls(
56
46
  List<String> paths,