@_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
@@ -5,11 +5,17 @@ import 'package:test/test.dart';
5
5
 
6
6
  void main() {
7
7
  group('paginateBundles', () {
8
- List<Bundle> makeBundles(int count) =>
9
- List.generate(count, (i) => _testBundle(id: 'b-${i.toString().padLeft(3, '0')}'));
8
+ List<Bundle> makeBundles(int count) => List.generate(
9
+ count,
10
+ (i) => _testBundle(id: 'b-${i.toString().padLeft(3, '0')}'),
11
+ );
10
12
 
11
13
  test('offset-based first page', () {
12
- final result = paginateBundles(bundles: makeBundles(25), limit: 10, offset: 0);
14
+ final result = paginateBundles(
15
+ bundles: makeBundles(25),
16
+ limit: 10,
17
+ offset: 0,
18
+ );
13
19
  expect(result.data.length, 10);
14
20
  expect(result.pagination.total, 25);
15
21
  expect(result.pagination.hasNextPage, isTrue);
@@ -20,7 +26,11 @@ void main() {
20
26
  });
21
27
 
22
28
  test('offset-based last page', () {
23
- final result = paginateBundles(bundles: makeBundles(25), limit: 10, offset: 20);
29
+ final result = paginateBundles(
30
+ bundles: makeBundles(25),
31
+ limit: 10,
32
+ offset: 20,
33
+ );
24
34
  expect(result.data.length, 5);
25
35
  expect(result.pagination.hasNextPage, isFalse);
26
36
  expect(result.pagination.hasPreviousPage, isTrue);
@@ -66,18 +76,20 @@ void main() {
66
76
 
67
77
  group('filterCompatibleAppVersions', () {
68
78
  test('filters and sorts descending', () {
69
- final result = filterCompatibleAppVersions(
70
- ['^1.0.0', '^2.0.0', '^0.5.0'],
71
- '1.3.0',
72
- );
79
+ final result = filterCompatibleAppVersions([
80
+ '^1.0.0',
81
+ '^2.0.0',
82
+ '^0.5.0',
83
+ ], '1.3.0');
73
84
  expect(result, ['^1.0.0']);
74
85
  });
75
86
 
76
87
  test('multiple compatible versions sorted desc', () {
77
- final result = filterCompatibleAppVersions(
78
- ['>=1.0.0', '>=1.2.0', '>=2.0.0'],
79
- '1.5.0',
80
- );
88
+ final result = filterCompatibleAppVersions([
89
+ '>=1.0.0',
90
+ '>=1.2.0',
91
+ '>=2.0.0',
92
+ ], '1.5.0');
81
93
  expect(result, ['>=1.2.0', '>=1.0.0']);
82
94
  });
83
95
  });
@@ -143,19 +155,31 @@ void main() {
143
155
 
144
156
  group('compressionFormat', () {
145
157
  test('detects zip', () {
146
- expect(detectCompressionFormat('bundle.zip').format, CompressionFormat.zip);
158
+ expect(
159
+ detectCompressionFormat('bundle.zip').format,
160
+ CompressionFormat.zip,
161
+ );
147
162
  });
148
163
 
149
164
  test('detects tar.br', () {
150
- expect(detectCompressionFormat('bundle.tar.br').format, CompressionFormat.tarBr);
165
+ expect(
166
+ detectCompressionFormat('bundle.tar.br').format,
167
+ CompressionFormat.tarBr,
168
+ );
151
169
  });
152
170
 
153
171
  test('detects tar.gz', () {
154
- expect(detectCompressionFormat('bundle.tar.gz').format, CompressionFormat.tarGz);
172
+ expect(
173
+ detectCompressionFormat('bundle.tar.gz').format,
174
+ CompressionFormat.tarGz,
175
+ );
155
176
  });
156
177
 
157
178
  test('defaults to zip', () {
158
- expect(detectCompressionFormat('bundle.xyz').format, CompressionFormat.zip);
179
+ expect(
180
+ detectCompressionFormat('bundle.xyz').format,
181
+ CompressionFormat.zip,
182
+ );
159
183
  });
160
184
 
161
185
  test('getContentType returns mime type for zip', () {
@@ -217,8 +241,8 @@ Bundle _testBundle({String id = 'test-bundle-id'}) {
217
241
 
218
242
  class _DummyStoragePlugin extends StoragePlugin {
219
243
  _DummyStoragePlugin({required bool node, required bool runtime})
220
- : _node = node,
221
- _runtime = runtime;
244
+ : _node = node,
245
+ _runtime = runtime;
222
246
 
223
247
  final bool _node;
224
248
  final bool _runtime;
@@ -231,9 +255,9 @@ class _DummyStoragePlugin extends StoragePlugin {
231
255
 
232
256
  @override
233
257
  StoragePluginProfiles get profiles => StoragePluginProfiles(
234
- node: _node ? _DummyNodeStorageProfile() : null,
235
- runtime: _runtime ? _DummyRuntimeStorageProfile() : null,
236
- );
258
+ node: _node ? _DummyNodeStorageProfile() : null,
259
+ runtime: _runtime ? _DummyRuntimeStorageProfile() : null,
260
+ );
237
261
  }
238
262
 
239
263
  class _DummyNodeStorageProfile extends NodeStorageProfile {
@@ -44,18 +44,42 @@ void main() {
44
44
  });
45
45
 
46
46
  test('eq match', () {
47
- expect(bundleIdMatchesFilter('abc', const DatabaseBundleIdFilter(eq: 'abc')), isTrue);
48
- expect(bundleIdMatchesFilter('abc', const DatabaseBundleIdFilter(eq: 'def')), isFalse);
47
+ expect(
48
+ bundleIdMatchesFilter('abc', const DatabaseBundleIdFilter(eq: 'abc')),
49
+ isTrue,
50
+ );
51
+ expect(
52
+ bundleIdMatchesFilter('abc', const DatabaseBundleIdFilter(eq: 'def')),
53
+ isFalse,
54
+ );
49
55
  });
50
56
 
51
57
  test('gt match', () {
52
- expect(bundleIdMatchesFilter('b', const DatabaseBundleIdFilter(gt: 'a')), isTrue);
53
- expect(bundleIdMatchesFilter('a', const DatabaseBundleIdFilter(gt: 'b')), isFalse);
58
+ expect(
59
+ bundleIdMatchesFilter('b', const DatabaseBundleIdFilter(gt: 'a')),
60
+ isTrue,
61
+ );
62
+ expect(
63
+ bundleIdMatchesFilter('a', const DatabaseBundleIdFilter(gt: 'b')),
64
+ isFalse,
65
+ );
54
66
  });
55
67
 
56
68
  test('in match', () {
57
- expect(bundleIdMatchesFilter('a', const DatabaseBundleIdFilter(ins: ['a', 'b'])), isTrue);
58
- expect(bundleIdMatchesFilter('c', const DatabaseBundleIdFilter(ins: ['a', 'b'])), isFalse);
69
+ expect(
70
+ bundleIdMatchesFilter(
71
+ 'a',
72
+ const DatabaseBundleIdFilter(ins: ['a', 'b']),
73
+ ),
74
+ isTrue,
75
+ );
76
+ expect(
77
+ bundleIdMatchesFilter(
78
+ 'c',
79
+ const DatabaseBundleIdFilter(ins: ['a', 'b']),
80
+ ),
81
+ isFalse,
82
+ );
59
83
  });
60
84
 
61
85
  test('combined filters', () {
@@ -73,20 +97,56 @@ void main() {
73
97
 
74
98
  test('filters by channel', () {
75
99
  final bundle = _testBundle(channel: 'prod');
76
- expect(bundleMatchesQueryWhere(bundle, const DatabaseBundleQueryWhere(channel: 'prod')), isTrue);
77
- expect(bundleMatchesQueryWhere(bundle, const DatabaseBundleQueryWhere(channel: 'dev')), isFalse);
100
+ expect(
101
+ bundleMatchesQueryWhere(
102
+ bundle,
103
+ const DatabaseBundleQueryWhere(channel: 'prod'),
104
+ ),
105
+ isTrue,
106
+ );
107
+ expect(
108
+ bundleMatchesQueryWhere(
109
+ bundle,
110
+ const DatabaseBundleQueryWhere(channel: 'dev'),
111
+ ),
112
+ isFalse,
113
+ );
78
114
  });
79
115
 
80
116
  test('filters by enabled', () {
81
117
  final bundle = _testBundle(enabled: true);
82
- expect(bundleMatchesQueryWhere(bundle, const DatabaseBundleQueryWhere(enabled: true)), isTrue);
83
- expect(bundleMatchesQueryWhere(bundle, const DatabaseBundleQueryWhere(enabled: false)), isFalse);
118
+ expect(
119
+ bundleMatchesQueryWhere(
120
+ bundle,
121
+ const DatabaseBundleQueryWhere(enabled: true),
122
+ ),
123
+ isTrue,
124
+ );
125
+ expect(
126
+ bundleMatchesQueryWhere(
127
+ bundle,
128
+ const DatabaseBundleQueryWhere(enabled: false),
129
+ ),
130
+ isFalse,
131
+ );
84
132
  });
85
133
 
86
134
  test('filters by targetAppVersion null', () {
87
135
  final bundle = _testBundle(targetAppVersion: null);
88
- expect(bundleMatchesQueryWhere(bundle, const DatabaseBundleQueryWhere(targetAppVersionNotNull: true)), isFalse);
89
- expect(bundleMatchesQueryWhere(bundle, const DatabaseBundleQueryWhere(targetAppVersionNotNull: false)), isTrue);
136
+ expect(
137
+ bundleMatchesQueryWhere(
138
+ bundle,
139
+ const DatabaseBundleQueryWhere(targetAppVersionNotNull: true),
140
+ ),
141
+ isFalse,
142
+ );
143
+ expect(
144
+ bundleMatchesQueryWhere(
145
+ bundle,
146
+ const DatabaseBundleQueryWhere(targetAppVersionNotNull: false),
147
+ ),
148
+ isTrue,
149
+ );
90
150
  });
91
151
  });
92
152
 
@@ -107,7 +167,10 @@ void main() {
107
167
  _testBundle(id: 'a'),
108
168
  _testBundle(id: 'b'),
109
169
  ];
110
- final sorted = sortBundles(bundles, const DatabaseBundleQueryOrder(direction: 'asc'));
170
+ final sorted = sortBundles(
171
+ bundles,
172
+ const DatabaseBundleQueryOrder(direction: 'asc'),
173
+ );
111
174
  expect(sorted.map((b) => b.id).toList(), ['a', 'b', 'c']);
112
175
  });
113
176
  });
@@ -23,9 +23,14 @@ void main() {
23
23
  test('produces valid UUID v7 format', () {
24
24
  final uuid = createUUIDv7();
25
25
  // 8-4-4-4-12 format
26
- expect(uuid, matches(RegExp(
27
- r'^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$',
28
- )));
26
+ expect(
27
+ uuid,
28
+ matches(
29
+ RegExp(
30
+ r'^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$',
31
+ ),
32
+ ),
33
+ );
29
34
  });
30
35
 
31
36
  test('generates unique UUIDs', () {
@@ -60,18 +65,24 @@ void main() {
60
65
  test('produces different random bits', () {
61
66
  final original = createUUIDv7();
62
67
  // Generate a few — at least one should differ in random part
63
- final differentFound = List.generate(10, (_) =>
64
- createUUIDv7WithSameTimestamp(original))
65
- .any((u) => u != original);
68
+ final differentFound = List.generate(
69
+ 10,
70
+ (_) => createUUIDv7WithSameTimestamp(original),
71
+ ).any((u) => u != original);
66
72
  expect(differentFound, isTrue);
67
73
  });
68
74
 
69
75
  test('derived UUID still has version 7 and correct variant', () {
70
76
  final original = createUUIDv7();
71
77
  final derived = createUUIDv7WithSameTimestamp(original);
72
- expect(derived, matches(RegExp(
73
- r'^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$',
74
- )));
78
+ expect(
79
+ derived,
80
+ matches(
81
+ RegExp(
82
+ r'^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$',
83
+ ),
84
+ ),
85
+ );
75
86
  });
76
87
  });
77
88
  }
@@ -0,0 +1,8 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ - Initial release of `flutter_ota_kit_pocketbase` — PocketBase
6
+ database + storage plugins for the flutter_ota_kit OTA toolkit.
7
+ See the [PocketBase backend guide](https://github.com/HYPER12755/flutter-ota-kit/blob/main/doc/backends.md#pocketbase)
8
+ for usage.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 xuelinger
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,54 @@
1
+ # flutter_ota_kit_pocketbase
2
+
3
+ PocketBase backend plugin for
4
+ [`flutter_ota_kit`](https://github.com/HYPER12755/flutter-ota-kit) — a
5
+ self-hostable code-push (OTA update) toolkit for Flutter Android.
6
+
7
+ PocketBase is a single-binary, Go-based backend that gives you SQLite,
8
+ auth, file storage, realtime, and an admin UI in one ~15 MB executable.
9
+ This plugin makes PocketBase a drop-in `DatabasePlugin` + `StoragePlugin`
10
+ for `flutter_ota_kit`.
11
+
12
+ ## Install
13
+
14
+ ```yaml
15
+ dependencies:
16
+ flutter_ota_kit: ^0.1.10
17
+ flutter_ota_kit_pocketbase: ^0.1.0
18
+ ```
19
+
20
+ ## Quick start
21
+
22
+ ```dart
23
+ import 'package:flutter_ota_kit/flutter_ota_kit.dart';
24
+ import 'package:flutter_ota_kit_pocketbase/flutter_ota_kit_pocketbase.dart';
25
+
26
+ await FlutterPatcher.init(
27
+ pocketbaseConfig: PocketBaseUpdateConfig(
28
+ url: 'http://localhost:8090',
29
+ adminEmail: 'admin@example.com',
30
+ adminPassword: 'super-secret',
31
+ ),
32
+ // ... other FlutterPatcher options
33
+ );
34
+ ```
35
+
36
+ ## Start a local PocketBase with the schema pre-installed
37
+
38
+ ```bash
39
+ flutter-ota serve -b pocketbase
40
+ ```
41
+
42
+ This downloads PocketBase (if missing), creates the `bundles` and
43
+ `channels` collections, installs validation hooks, and starts the
44
+ admin UI at `http://localhost:8090/_/`.
45
+
46
+ ## Documentation
47
+
48
+ - [Backend guide → PocketBase](https://github.com/HYPER12755/flutter-ota-kit/blob/main/doc/backends.md#pocketbase)
49
+ - [API reference](https://github.com/HYPER12755/flutter-ota-kit/blob/main/doc/api-reference.md)
50
+ - [Main README](https://github.com/HYPER12755/flutter-ota-kit)
51
+
52
+ ## License
53
+
54
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,62 @@
1
+ // flutter_ota_kit_pocketbase example
2
+ //
3
+ // `flutter_ota_kit_pocketbase` is a PocketBase backend for
4
+ // `flutter_ota_kit`. PocketBase is a single-binary, self-hostable
5
+ // backend (similar to Supabase) that gives you SQLite, auth, file
6
+ // storage, realtime, and an admin UI in one ~15MB Go binary. This
7
+ // plugin makes it a drop-in `DatabasePlugin` + `StoragePlugin`.
8
+ //
9
+ // Pair this with `flutter_ota_kit serve` (cli-tools) to start a local
10
+ // PocketBase instance with the flutter_ota_kit schema pre-installed,
11
+ // plus an admin UI at `http://localhost:8090/_/`.
12
+ //
13
+ // Run: `dart run example/main.dart`
14
+
15
+ import 'package:flutter_ota_kit_pocketbase/flutter_ota_kit_pocketbase.dart';
16
+
17
+ void main() {
18
+ // ── 1. Database config (the `bundles` collection). ──────────────
19
+ // `url` is the base URL of your PocketBase instance. `adminEmail` /
20
+ // `adminPassword` are the superuser credentials — used only for
21
+ // server-side reads/writes against the `bundles` and `channels`
22
+ // collections.
23
+ const dbConfig = PocketBaseConfig(
24
+ url: 'http://localhost:8090',
25
+ adminEmail: 'admin@example.com',
26
+ adminPassword: 'super-secret',
27
+ );
28
+ print('PocketBase URL: ${dbConfig.url}');
29
+ print('Bundles collection: ${dbConfig.bundlesCollection}');
30
+
31
+ // ── 2. Storage config (the `bundles` file bucket). ───────────────
32
+ // The `bundlesCollection` is the collection whose records carry the
33
+ // patch file attachments. The `bundlesBucket` is the file-storage
34
+ // bucket name within PocketBase.
35
+ const storageConfig = PocketBaseStorageConfig(
36
+ url: 'http://localhost:8090',
37
+ adminEmail: 'admin@example.com',
38
+ adminPassword: 'super-secret',
39
+ bundlesCollection: 'bundles',
40
+ bundlesBucket: 'bundles',
41
+ );
42
+ print('Bucket: ${storageConfig.bundlesBucket}');
43
+
44
+ // ── 3. Storage URI helpers. ──────────────────────────────────────
45
+ // Build a `pb://<bucket>/<recordId>/<file>` URI for a given
46
+ // record+filename. The device SDK understands this format and
47
+ // downloads via the PocketBase `/api/files/{collection}/{id}/{file}`
48
+ // endpoint (with a short-lived token).
49
+ final uri = buildPocketBaseStorageUri(
50
+ '01a059a6-7d3c-4f3e-8a2c-9c2e3a4b5d6e',
51
+ 'patch.zip',
52
+ );
53
+ print('Storage URI: $uri');
54
+
55
+ // Round-trip it:
56
+ final parsed = parsePocketBaseStorageUri(uri);
57
+ if (parsed != null) {
58
+ print('Parsed bucket: ${parsed.bucket}');
59
+ print('Parsed recordId: ${parsed.recordId}');
60
+ print('Parsed filename: ${parsed.filename}');
61
+ }
62
+ }
@@ -0,0 +1,28 @@
1
+ /// flutter_ota_kit_pocketbase — PocketBase backend for flutter_ota_kit.
2
+ ///
3
+ /// PocketBase is a single-binary, self-hostable backend (similar to Supabase)
4
+ /// that gives you SQLite, auth, file storage, realtime, and an admin UI in
5
+ /// one ~15MB Go binary. This plugin makes it a drop-in `DatabasePlugin` +
6
+ /// `StoragePlugin` for flutter_ota_kit.
7
+ ///
8
+ /// Pair this with `flutter_ota_kit serve` (cli-tools) to start a local
9
+ /// PocketBase instance with the flutter_ota_kit schema pre-installed, plus
10
+ /// an admin UI at `http://localhost:8090/_/`.
11
+ library;
12
+
13
+ export 'src/pocketbase_config.dart';
14
+ export 'src/pocketbase_client.dart'
15
+ show
16
+ PocketBaseClient,
17
+ PocketBaseClientFactory,
18
+ PocketBaseException,
19
+ PocketBaseList;
20
+ export 'src/pocketbase_database.dart'
21
+ show pocketbaseDatabase, PocketBaseDatabaseConfig;
22
+ export 'src/pocketbase_storage.dart'
23
+ show
24
+ pocketbaseStorage,
25
+ PocketBaseStorageConfig,
26
+ parsePocketBaseStorageUri,
27
+ buildPocketBaseStorageUri;
28
+ export 'src/pocketbase_bundle_mapper.dart';
@@ -0,0 +1,121 @@
1
+ /// Maps PocketBase bundle records to [Bundle] and back.
2
+ library;
3
+
4
+ import 'package:flutter_ota_kit_core/flutter_ota_kit_core.dart';
5
+ import 'package:flutter_ota_kit_plugin_core/flutter_ota_kit_plugin_core.dart'
6
+ show normalizeMetadata;
7
+
8
+ /// Row shape of a `bundles` record as stored in PocketBase.
9
+ class PocketBaseBundleRow {
10
+ PocketBaseBundleRow({
11
+ required this.id,
12
+ required this.channel,
13
+ required this.enabled,
14
+ required this.platform,
15
+ required this.shouldForceUpdate,
16
+ required this.fileHash,
17
+ required this.storageUri,
18
+ required this.rolloutCohortCount,
19
+ this.gitCommitHash,
20
+ this.message,
21
+ this.fingerprintHash,
22
+ this.targetAppVersion,
23
+ this.manifestStorageUri,
24
+ this.manifestFileHash,
25
+ this.assetBaseStorageUri,
26
+ this.targetCohorts,
27
+ this.metadata,
28
+ this.created,
29
+ this.updated,
30
+ });
31
+
32
+ factory PocketBaseBundleRow.fromJson(Map<String, dynamic> j) =>
33
+ PocketBaseBundleRow(
34
+ id: j['id'] as String? ?? '',
35
+ channel: j['channel'] as String? ?? 'production',
36
+ enabled: j['enabled'] as bool? ?? true,
37
+ platform: j['platform'] as String? ?? 'android',
38
+ shouldForceUpdate: j['should_force_update'] as bool? ?? false,
39
+ fileHash: j['file_hash'] as String? ?? '',
40
+ storageUri: j['storage_uri'] as String? ?? '',
41
+ rolloutCohortCount:
42
+ (j['rollout_cohort_count'] as num?)?.toInt() ?? 1000,
43
+ gitCommitHash: j['git_commit_hash'] as String?,
44
+ message: j['message'] as String?,
45
+ fingerprintHash: j['fingerprint_hash'] as String?,
46
+ targetAppVersion: j['target_app_version'] as String?,
47
+ manifestStorageUri: j['manifest_storage_uri'] as String?,
48
+ manifestFileHash: j['manifest_file_hash'] as String?,
49
+ assetBaseStorageUri: j['asset_base_storage_uri'] as String?,
50
+ targetCohorts: (j['target_cohorts'] as List?)?.cast<String>(),
51
+ metadata: (j['metadata'] as Map?)?.cast<String, dynamic>(),
52
+ created: j['created'] as String?,
53
+ updated: j['updated'] as String?,
54
+ );
55
+
56
+ final String id;
57
+ final String channel;
58
+ final bool enabled;
59
+ final String platform;
60
+ final bool shouldForceUpdate;
61
+ final String fileHash;
62
+ final String storageUri;
63
+ final int rolloutCohortCount;
64
+ final String? gitCommitHash;
65
+ final String? message;
66
+ final String? fingerprintHash;
67
+ final String? targetAppVersion;
68
+ final String? manifestStorageUri;
69
+ final String? manifestFileHash;
70
+ final String? assetBaseStorageUri;
71
+ final List<String>? targetCohorts;
72
+ final Map<String, dynamic>? metadata;
73
+ final String? created;
74
+ final String? updated;
75
+
76
+ Map<String, dynamic> toCreateJson() => {
77
+ 'id': id,
78
+ 'channel': channel,
79
+ 'enabled': enabled,
80
+ 'platform': platform,
81
+ 'should_force_update': shouldForceUpdate,
82
+ 'file_hash': fileHash,
83
+ 'storage_uri': storageUri,
84
+ 'rollout_cohort_count': rolloutCohortCount,
85
+ if (gitCommitHash != null) 'git_commit_hash': gitCommitHash,
86
+ if (message != null) 'message': message,
87
+ if (fingerprintHash != null) 'fingerprint_hash': fingerprintHash,
88
+ if (targetAppVersion != null) 'target_app_version': targetAppVersion,
89
+ if (manifestStorageUri != null) 'manifest_storage_uri': manifestStorageUri,
90
+ if (manifestFileHash != null) 'manifest_file_hash': manifestFileHash,
91
+ if (assetBaseStorageUri != null)
92
+ 'asset_base_storage_uri': assetBaseStorageUri,
93
+ if (targetCohorts != null) 'target_cohorts': targetCohorts,
94
+ if (metadata != null) 'metadata': metadata,
95
+ };
96
+ }
97
+
98
+ /// Convert a [PocketBaseBundleRow] into a [Bundle].
99
+ Bundle mapRowToBundle(PocketBaseBundleRow row) {
100
+ final rawMetadata = normalizeMetadata(row.metadata);
101
+ return Bundle(
102
+ id: row.id,
103
+ channel: row.channel,
104
+ enabled: row.enabled,
105
+ shouldForceUpdate: row.shouldForceUpdate,
106
+ fileHash: row.fileHash,
107
+ gitCommitHash: row.gitCommitHash,
108
+ message: row.message,
109
+ platform: Platform.fromValue(row.platform),
110
+ targetAppVersion: row.targetAppVersion,
111
+ fingerprintHash: row.fingerprintHash,
112
+ storageUri: row.storageUri,
113
+ metadata: rawMetadata != null ? BundleMetadata.fromJson(rawMetadata) : null,
114
+ manifestStorageUri: row.manifestStorageUri,
115
+ manifestFileHash: row.manifestFileHash,
116
+ assetBaseStorageUri: row.assetBaseStorageUri,
117
+ patches: const [],
118
+ rolloutCohortCount: row.rolloutCohortCount,
119
+ targetCohorts: row.targetCohorts,
120
+ );
121
+ }