@_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,259 @@
1
+ import 'query_bundles.dart';
2
+ import 'types.dart';
3
+
4
+ /// Change operation tracked by the unit of work.
5
+ enum BundleChangeOperation { insert, update, delete }
6
+
7
+ /// A recorded bundle change with optional before-snapshot.
8
+ class BundleChange {
9
+ const BundleChange({required this.operation, required this.data});
10
+
11
+ final BundleChangeOperation operation;
12
+ final Bundle data;
13
+ }
14
+
15
+ class _TrackedBundleChange {
16
+ _TrackedBundleChange({
17
+ required this.operation,
18
+ required this.data,
19
+ required this.before,
20
+ });
21
+
22
+ BundleChangeOperation operation;
23
+ Bundle data;
24
+ Bundle? before;
25
+ }
26
+
27
+ /// Discriminated union for in-memory entry store.
28
+ sealed class _BundleEntry {
29
+ const _BundleEntry();
30
+ }
31
+
32
+ class _PresentEntry extends _BundleEntry {
33
+ const _PresentEntry(this.bundle);
34
+ final Bundle bundle;
35
+ }
36
+
37
+ class _DeletedEntry extends _BundleEntry {
38
+ const _DeletedEntry(this.bundle);
39
+ final Bundle bundle;
40
+ }
41
+
42
+ class _MissingEntry extends _BundleEntry {
43
+ const _MissingEntry();
44
+ }
45
+
46
+ /// Result of peeking at a tracked change.
47
+ sealed class TrackedBundleValue {
48
+ const TrackedBundleValue();
49
+ }
50
+
51
+ class TrackedBundleFound extends TrackedBundleValue {
52
+ const TrackedBundleFound(this.value);
53
+ final Bundle? value;
54
+ }
55
+
56
+ class TrackedBundleNotFound extends TrackedBundleValue {
57
+ const TrackedBundleNotFound();
58
+ }
59
+
60
+ /// In-memory change tracker for a set of bundles.
61
+ ///
62
+ /// Provides deduped async loads, insert/update/delete tracking, and overlay
63
+ /// logic for paginated query results.
64
+ ///
65
+ /// Faithful port of hot-updater `bundleUnitOfWork.ts`.
66
+ class BundleUnitOfWork {
67
+ final _entries = <String, _BundleEntry>{};
68
+ final _pendingLoads = <String, Future<Bundle?>>{};
69
+ final _changes = <String, _TrackedBundleChange>{};
70
+ final _seededIds = <String>{};
71
+
72
+ /// Pre-populate entries from seed bundles.
73
+ void seed(List<Bundle?> seeds) {
74
+ for (final seed in seeds) {
75
+ if (seed == null) continue;
76
+ _seededIds.add(seed.id);
77
+ if (!_changes.containsKey(seed.id)) {
78
+ _entries[seed.id] = _PresentEntry(seed);
79
+ }
80
+ }
81
+ }
82
+
83
+ bool hasSeeds() => _seededIds.isNotEmpty;
84
+
85
+ List<Bundle> seededBundles() {
86
+ final bundles = <Bundle>[];
87
+ for (final id in _seededIds) {
88
+ final entry = _entries[id];
89
+ if (entry is _PresentEntry) {
90
+ bundles.add(entry.bundle);
91
+ }
92
+ }
93
+ return bundles;
94
+ }
95
+
96
+ /// Synchronous peek at a bundle by ID from the in-memory store.
97
+ Bundle? peek(String bundleId) {
98
+ final entry = _entries[bundleId];
99
+ return entry is _PresentEntry ? entry.bundle : null;
100
+ }
101
+
102
+ /// Check if a bundle has a pending tracked change.
103
+ TrackedBundleValue peekChanged(String bundleId) {
104
+ if (!_changes.containsKey(bundleId)) {
105
+ return const TrackedBundleNotFound();
106
+ }
107
+ final entry = _entries[bundleId];
108
+ return TrackedBundleFound(
109
+ entry is _PresentEntry ? entry.bundle : null,
110
+ );
111
+ }
112
+
113
+ /// Deduped async lookup: returns cached entry, coalesces concurrent loads,
114
+ /// and stores the result for future peeks.
115
+ Future<Bundle?> getById(
116
+ String bundleId,
117
+ Future<Bundle?> Function() loadBundleById,
118
+ ) async {
119
+ final entry = _entries[bundleId];
120
+ if (entry != null) {
121
+ return entry is _PresentEntry ? entry.bundle : null;
122
+ }
123
+
124
+ final pending = _pendingLoads[bundleId];
125
+ if (pending != null) return pending;
126
+
127
+ final load = loadBundleById().then<Bundle?>((bundle) {
128
+ _pendingLoads.remove(bundleId);
129
+ final currentEntry = _entries[bundleId];
130
+ if (currentEntry != null) {
131
+ return currentEntry is _PresentEntry ? currentEntry.bundle : null;
132
+ }
133
+ _entries[bundleId] =
134
+ bundle != null ? _PresentEntry(bundle) : const _MissingEntry();
135
+ return bundle;
136
+ }, onError: (Object error) {
137
+ _pendingLoads.remove(bundleId);
138
+ throw error;
139
+ });
140
+ _pendingLoads[bundleId] = load;
141
+ return load;
142
+ }
143
+
144
+ /// Merge in-memory changes over a database query result, filter by where,
145
+ /// sort, and slice to limit.
146
+ List<Bundle> overlayList(
147
+ List<Bundle> bundles, {
148
+ required int limit,
149
+ required DatabaseBundleQueryWhere? where,
150
+ required DatabaseBundleQueryOrder? orderBy,
151
+ }) {
152
+ final dataById = <String, Bundle>{};
153
+
154
+ for (final bundle in bundles) {
155
+ final entry = _entries[bundle.id];
156
+ if (entry is _DeletedEntry) continue;
157
+ if (entry is _PresentEntry) {
158
+ if (bundleMatchesQueryWhere(entry.bundle, where)) {
159
+ dataById[entry.bundle.id] = entry.bundle;
160
+ }
161
+ continue;
162
+ }
163
+ _entries[bundle.id] = _PresentEntry(bundle);
164
+ dataById[bundle.id] = bundle;
165
+ }
166
+
167
+ for (final change in _changes.values) {
168
+ if (change.operation == BundleChangeOperation.delete ||
169
+ change.operation == BundleChangeOperation.insert) {
170
+ dataById.remove(change.data.id);
171
+ continue;
172
+ }
173
+ if (bundleMatchesQueryWhere(change.data, where)) {
174
+ dataById[change.data.id] = change.data;
175
+ } else {
176
+ dataById.remove(change.data.id);
177
+ }
178
+ }
179
+
180
+ return sortBundles(dataById.values.toList(), orderBy)
181
+ .take(limit)
182
+ .toList();
183
+ }
184
+
185
+ void markInsert(Bundle bundle) {
186
+ _entries[bundle.id] = _PresentEntry(bundle);
187
+ _changes[bundle.id] = _TrackedBundleChange(
188
+ operation: BundleChangeOperation.insert,
189
+ data: bundle,
190
+ before: null,
191
+ );
192
+ }
193
+
194
+ void markUpdate(Bundle bundle) {
195
+ final prev = _changes[bundle.id];
196
+ final prevEntry = _entries[bundle.id];
197
+ final op = prev?.operation ?? BundleChangeOperation.update;
198
+ _entries[bundle.id] = _PresentEntry(bundle);
199
+ _changes[bundle.id] = _TrackedBundleChange(
200
+ operation: op,
201
+ data: bundle,
202
+ before: prev?.before ??
203
+ (prevEntry is _PresentEntry ? prevEntry.bundle : null),
204
+ );
205
+ }
206
+
207
+ void markDelete(Bundle bundle) {
208
+ final prev = _changes[bundle.id];
209
+ final prevEntry = _entries[bundle.id];
210
+ _entries[bundle.id] = _DeletedEntry(bundle);
211
+ _changes[bundle.id] = _TrackedBundleChange(
212
+ operation: BundleChangeOperation.delete,
213
+ data: bundle,
214
+ before: prev?.before ??
215
+ (prevEntry is _PresentEntry ? prevEntry.bundle : bundle),
216
+ );
217
+ }
218
+
219
+ List<BundleChange> changedSets() {
220
+ return _changes.values
221
+ .map((c) => BundleChange(operation: c.operation, data: c.data))
222
+ .toList();
223
+ }
224
+
225
+ bool hasChanges() => _changes.isNotEmpty;
226
+
227
+ int listFetchExtraCount() {
228
+ return _changes.values
229
+ .where((c) =>
230
+ c.operation == BundleChangeOperation.update ||
231
+ c.operation == BundleChangeOperation.delete)
232
+ .length;
233
+ }
234
+
235
+ int totalDelta(DatabaseBundleQueryWhere? where) {
236
+ var total = 0;
237
+ for (final change in _changes.values) {
238
+ if (change.operation == BundleChangeOperation.insert) continue;
239
+ final matchedBefore =
240
+ change.before != null && bundleMatchesQueryWhere(change.before!, where);
241
+ final matchesAfter =
242
+ change.operation == BundleChangeOperation.update &&
243
+ bundleMatchesQueryWhere(change.data, where);
244
+ if (matchedBefore && !matchesAfter) {
245
+ total -= 1;
246
+ } else if (!matchedBefore && matchesAfter) {
247
+ total += 1;
248
+ }
249
+ }
250
+ return total;
251
+ }
252
+
253
+ void clear() {
254
+ _entries.clear();
255
+ _pendingLoads.clear();
256
+ _changes.clear();
257
+ _seededIds.clear();
258
+ }
259
+ }
@@ -0,0 +1,20 @@
1
+ import 'bundle_unit_of_work.dart';
2
+
3
+ /// Per-context Unit of Work store.
4
+ ///
5
+ /// Uses a plain Map instead of WeakMap (Dart has no WeakMap).
6
+ /// Context objects are long-lived request maps so normal Map is fine.
7
+ final Map<Object, BundleUnitOfWork> _requestUnitOfWorks = {};
8
+
9
+ /// Check if a context object is eligible for UoW tracking.
10
+ bool isUnitOfWorkContext(Object? value) =>
11
+ value is Object && value is Map<String, Object?>;
12
+
13
+ /// Retrieve or lazily create a BundleUnitOfWork for the given context.
14
+ BundleUnitOfWork? getRequestBundleUnitOfWork(Object? context) {
15
+ if (!isUnitOfWorkContext(context)) return null;
16
+ return _requestUnitOfWorks.putIfAbsent(context!, () => BundleUnitOfWork());
17
+ }
18
+
19
+ /// Remove all UoW entries (for testing/cleanup).
20
+ void clearUnitOfWorkStore() => _requestUnitOfWorks.clear();
@@ -0,0 +1,29 @@
1
+ import 'types.dart';
2
+
3
+ /// Calculate pagination metadata from total count, limit, and offset.
4
+ ///
5
+ /// Faithful port of hot-updater `calculatePagination.ts`.
6
+ PaginationInfo calculatePagination(int total, {required int limit, required int offset}) {
7
+ if (total == 0) {
8
+ return const PaginationInfo(
9
+ total: 0,
10
+ hasNextPage: false,
11
+ hasPreviousPage: false,
12
+ currentPage: 1,
13
+ totalPages: 0,
14
+ );
15
+ }
16
+
17
+ final currentPage = (offset / limit).floor() + 1;
18
+ final totalPages = (total / limit).ceil();
19
+ final hasNextPage = offset + limit < total;
20
+ final hasPreviousPage = offset > 0;
21
+
22
+ return PaginationInfo(
23
+ total: total,
24
+ hasNextPage: hasNextPage,
25
+ hasPreviousPage: hasPreviousPage,
26
+ currentPage: currentPage,
27
+ totalPages: totalPages,
28
+ );
29
+ }
@@ -0,0 +1,63 @@
1
+ import 'package:mime/mime.dart' as mime_pkg;
2
+
3
+ /// Supported compression formats for bundle deployment.
4
+ enum CompressionFormat { zip, tarBr, tarGz }
5
+
6
+ class CompressionFormatInfo {
7
+ const CompressionFormatInfo({
8
+ required this.format,
9
+ required this.fileExtension,
10
+ this.mimeType,
11
+ });
12
+
13
+ final CompressionFormat format;
14
+ final String fileExtension;
15
+ final String? mimeType;
16
+ }
17
+
18
+ const Map<CompressionFormat, CompressionFormatInfo> compressionFormats = {
19
+ CompressionFormat.zip: CompressionFormatInfo(
20
+ format: CompressionFormat.zip,
21
+ fileExtension: '.zip',
22
+ mimeType: 'application/zip',
23
+ ),
24
+ CompressionFormat.tarBr: CompressionFormatInfo(
25
+ format: CompressionFormat.tarBr,
26
+ fileExtension: '.tar.br',
27
+ mimeType: 'application/x-tar',
28
+ ),
29
+ CompressionFormat.tarGz: CompressionFormatInfo(
30
+ format: CompressionFormat.tarGz,
31
+ fileExtension: '.tar.gz',
32
+ mimeType: 'application/x-tar',
33
+ ),
34
+ };
35
+
36
+ /// Detect compression format from filename.
37
+ CompressionFormatInfo detectCompressionFormat(String filename) {
38
+ for (final info in compressionFormats.values) {
39
+ if (filename.endsWith(info.fileExtension)) {
40
+ return info;
41
+ }
42
+ }
43
+ return compressionFormats[CompressionFormat.zip]!;
44
+ }
45
+
46
+ /// Get MIME type for a filename.
47
+ String? getCompressionMimeType(String filename) {
48
+ return detectCompressionFormat(filename).mimeType;
49
+ }
50
+
51
+ /// Get Content-Type for a bundle file with 3-tier fallback.
52
+ ///
53
+ /// Faithful port of hot-updater `compressionFormat.ts`.
54
+ String getContentType(String bundlePath) {
55
+ final filename = bundlePath
56
+ .replaceAll(RegExp(r'[\\/]+$'), '')
57
+ .split(RegExp(r'[\\/]'))
58
+ .last;
59
+
60
+ return mime_pkg.lookupMimeType(bundlePath) ??
61
+ getCompressionMimeType(filename) ??
62
+ 'application/octet-stream';
63
+ }
@@ -0,0 +1,22 @@
1
+ const String contentAddressedAssetPrefix = 'assets';
2
+
3
+ /// Build a content-addressed storage path for an asset.
4
+ ///
5
+ /// Format: `sha256/<hash[:2]>/<hash>.<ext>`
6
+ /// Preserves `.br` brotli extension from the original asset path.
7
+ ///
8
+ /// Faithful port of hot-updater `contentAddressedAssets.ts`.
9
+ String getContentAddressedAssetStoragePath({
10
+ required String assetPath,
11
+ required String fileHash,
12
+ }) {
13
+ String extension;
14
+ if (assetPath.endsWith('.br')) {
15
+ extension = '.br';
16
+ } else if (assetPath.contains('.')) {
17
+ extension = '.${assetPath.split('.').last}';
18
+ } else {
19
+ extension = '';
20
+ }
21
+ return 'sha256/${fileHash.substring(0, 2)}/$fileHash$extension';
22
+ }