@_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.
- package/bin/flutter-patcher.js +48 -39
- package/dart-src/packages/cli-tools/.gitkeep +0 -0
- package/dart-src/packages/cli-tools/bin/flutter_patcher.dart +9 -0
- package/dart-src/packages/cli-tools/lib/flutter_patcher_cli.dart +73 -0
- package/dart-src/packages/cli-tools/lib/src/backend.dart +162 -0
- package/dart-src/packages/cli-tools/lib/src/cli_base.dart +56 -0
- package/dart-src/packages/cli-tools/lib/src/commands/build.dart +99 -0
- package/dart-src/packages/cli-tools/lib/src/commands/bundle.dart +151 -0
- package/dart-src/packages/cli-tools/lib/src/commands/channel.dart +140 -0
- package/dart-src/packages/cli-tools/lib/src/commands/config_command.dart +113 -0
- package/dart-src/packages/cli-tools/lib/src/commands/console.dart +55 -0
- package/dart-src/packages/cli-tools/lib/src/commands/deploy.dart +95 -0
- package/dart-src/packages/cli-tools/lib/src/commands/doctor.dart +48 -0
- package/dart-src/packages/cli-tools/lib/src/commands/fingerprint.dart +29 -0
- package/dart-src/packages/cli-tools/lib/src/commands/init.dart +82 -0
- package/dart-src/packages/cli-tools/lib/src/commands/keys.dart +55 -0
- package/dart-src/packages/cli-tools/lib/src/commands/migrate.dart +132 -0
- package/dart-src/packages/cli-tools/lib/src/commands/rollback.dart +41 -0
- package/dart-src/packages/cli-tools/lib/src/config.dart +715 -0
- package/dart-src/packages/cli-tools/lib/src/operations.dart +206 -0
- package/dart-src/packages/cli-tools/lib/src/pack.dart +417 -0
- package/dart-src/packages/cli-tools/lib/src/sign.dart +60 -0
- package/dart-src/packages/cli-tools/lib/src/util.dart +86 -0
- package/dart-src/packages/cli-tools/migrations/cloudflare/0001_hot-updater_init.sql +16 -0
- package/dart-src/packages/cli-tools/migrations/cloudflare/0002_hot-updater_0.13.0.sql +8 -0
- package/dart-src/packages/cli-tools/migrations/cloudflare/0003_hot-updater_0.18.0.sql +38 -0
- package/dart-src/packages/cli-tools/migrations/cloudflare/0004_hot-updater_0.29.0.sql +11 -0
- package/dart-src/packages/cli-tools/migrations/cloudflare/0005_hot-updater_0.31.0.sql +24 -0
- package/dart-src/packages/cli-tools/migrations/cloudflare/bundles.sql +42 -0
- package/dart-src/packages/cli-tools/migrations/supabase/20250103114225_init.sql +228 -0
- package/dart-src/packages/cli-tools/migrations/supabase/20250314000000_hot-updater_0.13.0.sql +134 -0
- package/dart-src/packages/cli-tools/migrations/supabase/20250516000000_hot-updater_0.18.0.sql +194 -0
- package/dart-src/packages/cli-tools/migrations/supabase/20251014000000_hot-updater_0.21.0.sql +184 -0
- package/dart-src/packages/cli-tools/migrations/supabase/20260401000000_hot-updater_0.29.0.sql +503 -0
- package/dart-src/packages/cli-tools/migrations/supabase/20260414000000_hot-updater_0.30.0.sql +50 -0
- package/dart-src/packages/cli-tools/migrations/supabase/20260422000000_hot-updater_0.31.0.sql +20 -0
- package/dart-src/packages/cli-tools/migrations/supabase/20260520014100_hot-updater_rls.sql +48 -0
- package/dart-src/packages/cli-tools/pubspec.yaml +37 -0
- package/dart-src/packages/core/lib/flutter_patcher_core.dart +29 -0
- package/dart-src/packages/core/lib/src/app_update_info.dart +111 -0
- package/dart-src/packages/core/lib/src/bundle.dart +191 -0
- package/dart-src/packages/core/lib/src/bundle_artifacts.dart +39 -0
- package/dart-src/packages/core/lib/src/bundle_patch_artifact.dart +47 -0
- package/dart-src/packages/core/lib/src/changed_asset.dart +77 -0
- package/dart-src/packages/core/lib/src/get_bundles_args.dart +78 -0
- package/dart-src/packages/core/lib/src/metadata.dart +31 -0
- package/dart-src/packages/core/lib/src/patch_info.dart +284 -0
- package/dart-src/packages/core/lib/src/platform.dart +15 -0
- package/dart-src/packages/core/lib/src/rollout.dart +209 -0
- package/dart-src/packages/core/lib/src/semver.dart +70 -0
- package/dart-src/packages/core/lib/src/semver_range.dart +358 -0
- package/dart-src/packages/core/lib/src/semver_version.dart +94 -0
- package/dart-src/packages/core/lib/src/status.dart +24 -0
- package/dart-src/packages/core/lib/src/strategy.dart +10 -0
- package/dart-src/packages/core/lib/src/update_bundle_params.dart +30 -0
- package/dart-src/packages/core/lib/src/uuid.dart +45 -0
- package/dart-src/packages/core/pubspec.yaml +9 -0
- package/dart-src/packages/core/tool/dbg.dart +6 -0
- package/dart-src/plugins/aws/.gitkeep +0 -0
- package/dart-src/plugins/aws/lib/flutter_patcher_aws.dart +12 -0
- package/dart-src/plugins/aws/lib/src/aws_cloudfront_client.dart +207 -0
- package/dart-src/plugins/aws/lib/src/aws_cloudfront_signer.dart +130 -0
- package/dart-src/plugins/aws/lib/src/aws_config.dart +59 -0
- package/dart-src/plugins/aws/lib/src/aws_database.dart +198 -0
- package/dart-src/plugins/aws/lib/src/aws_lambda_edge_storage.dart +72 -0
- package/dart-src/plugins/aws/lib/src/aws_s3_client.dart +312 -0
- package/dart-src/plugins/aws/lib/src/aws_ssm_client.dart +125 -0
- package/dart-src/plugins/aws/lib/src/aws_storage.dart +20 -0
- package/dart-src/plugins/aws/lib/src/aws_storage_profile.dart +123 -0
- package/dart-src/plugins/aws/lib/src/with_cloudfront_signed_url.dart +136 -0
- package/dart-src/plugins/aws/pubspec.yaml +22 -0
- package/dart-src/plugins/cloudflare/.gitkeep +0 -0
- package/dart-src/plugins/cloudflare/lib/flutter_patcher_cloudflare.dart +18 -0
- package/dart-src/plugins/cloudflare/lib/src/cloudflare_worker_database.dart +29 -0
- package/dart-src/plugins/cloudflare/lib/src/d1_build_where.dart +93 -0
- package/dart-src/plugins/cloudflare/lib/src/d1_bundle_mapper.dart +131 -0
- package/dart-src/plugins/cloudflare/lib/src/d1_client.dart +66 -0
- package/dart-src/plugins/cloudflare/lib/src/d1_config.dart +30 -0
- package/dart-src/plugins/cloudflare/lib/src/d1_database.dart +16 -0
- package/dart-src/plugins/cloudflare/lib/src/d1_database_plugin.dart +314 -0
- package/dart-src/plugins/cloudflare/lib/src/r2_config.dart +50 -0
- package/dart-src/plugins/cloudflare/lib/src/r2_s3_client.dart +287 -0
- package/dart-src/plugins/cloudflare/lib/src/r2_storage.dart +23 -0
- package/dart-src/plugins/cloudflare/lib/src/r2_storage_profile.dart +110 -0
- package/dart-src/plugins/cloudflare/migrations/0001_hot-updater_init.sql +16 -0
- package/dart-src/plugins/cloudflare/migrations/0002_hot-updater_0.13.0.sql +8 -0
- package/dart-src/plugins/cloudflare/migrations/0003_hot-updater_0.18.0.sql +38 -0
- package/dart-src/plugins/cloudflare/migrations/0004_hot-updater_0.29.0.sql +11 -0
- package/dart-src/plugins/cloudflare/migrations/0005_hot-updater_0.31.0.sql +24 -0
- package/dart-src/plugins/cloudflare/pubspec.yaml +20 -0
- package/dart-src/plugins/cloudflare/sql/bundles.sql +42 -0
- package/dart-src/plugins/plugin-core/lib/flutter_patcher_plugin_core.dart +42 -0
- package/dart-src/plugins/plugin-core/lib/src/asset_storage_layout.dart +94 -0
- package/dart-src/plugins/plugin-core/lib/src/bundle_storage_layout.dart +44 -0
- package/dart-src/plugins/plugin-core/lib/src/bundle_unit_of_work.dart +259 -0
- package/dart-src/plugins/plugin-core/lib/src/bundle_unit_of_work_store.dart +20 -0
- package/dart-src/plugins/plugin-core/lib/src/calculate_pagination.dart +29 -0
- package/dart-src/plugins/plugin-core/lib/src/compression_format.dart +63 -0
- package/dart-src/plugins/plugin-core/lib/src/content_addressed_assets.dart +22 -0
- package/dart-src/plugins/plugin-core/lib/src/create_blob_database_plugin.dart +853 -0
- package/dart-src/plugins/plugin-core/lib/src/create_database_plugin.dart +525 -0
- package/dart-src/plugins/plugin-core/lib/src/create_storage_key_builder.dart +15 -0
- package/dart-src/plugins/plugin-core/lib/src/create_storage_plugin.dart +130 -0
- package/dart-src/plugins/plugin-core/lib/src/filter_compatible_app_versions.dart +18 -0
- package/dart-src/plugins/plugin-core/lib/src/generate_min_bundle_id.dart +19 -0
- package/dart-src/plugins/plugin-core/lib/src/get_update_info.dart +241 -0
- package/dart-src/plugins/plugin-core/lib/src/is_object.dart +5 -0
- package/dart-src/plugins/plugin-core/lib/src/legacy_asset_storage_layout.dart +9 -0
- package/dart-src/plugins/plugin-core/lib/src/paginate_bundles.dart +112 -0
- package/dart-src/plugins/plugin-core/lib/src/parse_storage_uri.dart +44 -0
- package/dart-src/plugins/plugin-core/lib/src/query_bundles.dart +70 -0
- package/dart-src/plugins/plugin-core/lib/src/request_update_bundle_state.dart +62 -0
- package/dart-src/plugins/plugin-core/lib/src/resolve_update_info_from_bundles.dart +43 -0
- package/dart-src/plugins/plugin-core/lib/src/storage_profile.dart +27 -0
- package/dart-src/plugins/plugin-core/lib/src/types.dart +454 -0
- package/dart-src/plugins/plugin-core/lib/src/uuidv7.dart +41 -0
- package/dart-src/plugins/plugin-core/pubspec.yaml +15 -0
- package/dart-src/plugins/postgres/.gitkeep +0 -0
- package/dart-src/plugins/postgres/lib/flutter_patcher_postgres.dart +17 -0
- package/dart-src/plugins/postgres/lib/src/postgres_bundle_mapper.dart +130 -0
- package/dart-src/plugins/postgres/lib/src/postgres_client.dart +90 -0
- package/dart-src/plugins/postgres/lib/src/postgres_config.dart +35 -0
- package/dart-src/plugins/postgres/lib/src/postgres_database.dart +302 -0
- package/dart-src/plugins/postgres/lib/src/postgres_get_update_info.dart +120 -0
- package/dart-src/plugins/postgres/lib/src/postgres_storage.dart +205 -0
- package/dart-src/plugins/postgres/lib/src/postgres_types.dart +132 -0
- package/dart-src/plugins/postgres/pubspec.yaml +18 -0
- package/dart-src/plugins/postgres/sql/bundles.sql +45 -0
- package/dart-src/plugins/postgres/sql/get_target_app_version_list.sql +21 -0
- package/dart-src/plugins/postgres/sql/get_update_info.spec.ts +171 -0
- package/dart-src/plugins/postgres/sql/get_update_info_by_app_version.sql +126 -0
- package/dart-src/plugins/postgres/sql/get_update_info_by_fingerprint_hash.sql +125 -0
- package/dart-src/plugins/postgres/sql/hash_user_id.sql +30 -0
- package/dart-src/plugins/postgres/sql/is_device_eligible.sql +237 -0
- package/dart-src/plugins/postgres/sql/prepareSql.ts +11 -0
- package/dart-src/plugins/standalone/.gitkeep +0 -0
- package/dart-src/plugins/standalone/lib/flutter_patcher_standalone.dart +12 -0
- package/dart-src/plugins/standalone/lib/src/standalone_config.dart +107 -0
- package/dart-src/plugins/standalone/lib/src/standalone_database.dart +233 -0
- package/dart-src/plugins/standalone/lib/src/standalone_storage.dart +175 -0
- package/dart-src/plugins/standalone/pubspec.yaml +18 -0
- package/dart-src/plugins/supabase/lib/edge.dart +10 -0
- package/dart-src/plugins/supabase/lib/flutter_patcher_supabase.dart +21 -0
- package/dart-src/plugins/supabase/lib/src/supabase_bundle_mapper.dart +129 -0
- package/dart-src/plugins/supabase/lib/src/supabase_client_adapter.dart +154 -0
- package/dart-src/plugins/supabase/lib/src/supabase_client_http.dart +516 -0
- package/dart-src/plugins/supabase/lib/src/supabase_config.dart +34 -0
- package/dart-src/plugins/supabase/lib/src/supabase_database.dart +422 -0
- package/dart-src/plugins/supabase/lib/src/supabase_edge_function_database.dart +32 -0
- package/dart-src/plugins/supabase/lib/src/supabase_edge_function_storage.dart +99 -0
- package/dart-src/plugins/supabase/lib/src/supabase_signed_url_batcher.dart +145 -0
- package/dart-src/plugins/supabase/lib/src/supabase_storage.dart +298 -0
- package/dart-src/plugins/supabase/lib/src/types.dart +130 -0
- package/dart-src/plugins/supabase/pubspec.yaml +19 -0
- package/dart-src/plugins/supabase/supabase/edge-functions/index.ts +44 -0
- package/dart-src/plugins/supabase/supabase/edge-functions/runtime.docker.integration.spec.ts +968 -0
- package/dart-src/plugins/supabase/supabase/migrations/20250103114225_init.sql +228 -0
- package/dart-src/plugins/supabase/supabase/migrations/20250314000000_hot-updater_0.13.0.sql +134 -0
- package/dart-src/plugins/supabase/supabase/migrations/20250516000000_hot-updater_0.18.0.sql +194 -0
- package/dart-src/plugins/supabase/supabase/migrations/20251014000000_hot-updater_0.21.0.sql +184 -0
- package/dart-src/plugins/supabase/supabase/migrations/20260401000000_hot-updater_0.29.0.sql +503 -0
- package/dart-src/plugins/supabase/supabase/migrations/20260414000000_hot-updater_0.30.0.sql +50 -0
- package/dart-src/plugins/supabase/supabase/migrations/20260422000000_hot-updater_0.31.0.sql +20 -0
- package/dart-src/plugins/supabase/supabase/migrations/20260520014100_hot-updater_rls.sql +48 -0
- package/package.json +2 -1
- package/scripts/postinstall.js +53 -25
- /package/bin/{flutter-patcher-linux → flutter-patcher-linux-x64} +0 -0
|
@@ -0,0 +1,525 @@
|
|
|
1
|
+
import 'bundle_unit_of_work.dart';
|
|
2
|
+
import 'bundle_unit_of_work_store.dart';
|
|
3
|
+
import 'calculate_pagination.dart';
|
|
4
|
+
import 'types.dart';
|
|
5
|
+
|
|
6
|
+
const List<String> _replaceOnUpdateKeys = ['patches', 'targetCohorts'];
|
|
7
|
+
const DatabaseBundleQueryOrder _defaultDescOrder =
|
|
8
|
+
DatabaseBundleQueryOrder(field: 'id', direction: 'desc');
|
|
9
|
+
|
|
10
|
+
int? _normalizePage(int? value) {
|
|
11
|
+
if (value == null || value < 1) return null;
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/// Deep-merge a bundle patch onto a base bundle, replacing array fields
|
|
16
|
+
/// specified by [replaceKeys] rather than concatenating them.
|
|
17
|
+
Bundle mergeBundleUpdate(Bundle base, Map<String, Object?> patch) {
|
|
18
|
+
final baseMap = base.toJson();
|
|
19
|
+
for (final key in patch.keys) {
|
|
20
|
+
final srcValue = patch[key];
|
|
21
|
+
if (_replaceOnUpdateKeys.contains(key)) {
|
|
22
|
+
baseMap[key] = srcValue;
|
|
23
|
+
} else if (srcValue != null) {
|
|
24
|
+
baseMap[key] = srcValue;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return Bundle.fromJson(baseMap);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
DatabaseBundleIdFilter _mergeIdFilter(
|
|
31
|
+
DatabaseBundleIdFilter? base,
|
|
32
|
+
DatabaseBundleIdFilter patch,
|
|
33
|
+
) {
|
|
34
|
+
return DatabaseBundleIdFilter(
|
|
35
|
+
eq: patch.eq ?? base?.eq,
|
|
36
|
+
gt: patch.gt ?? base?.gt,
|
|
37
|
+
gte: patch.gte ?? base?.gte,
|
|
38
|
+
lt: patch.lt ?? base?.lt,
|
|
39
|
+
lte: patch.lte ?? base?.lte,
|
|
40
|
+
ins: patch.ins ?? base?.ins,
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
DatabaseBundleQueryWhere _mergeWhereWithIdFilter(
|
|
45
|
+
DatabaseBundleQueryWhere? where,
|
|
46
|
+
DatabaseBundleIdFilter idFilter,
|
|
47
|
+
) {
|
|
48
|
+
return DatabaseBundleQueryWhere(
|
|
49
|
+
channel: where?.channel,
|
|
50
|
+
platform: where?.platform,
|
|
51
|
+
enabled: where?.enabled,
|
|
52
|
+
id: _mergeIdFilter(where?.id, idFilter),
|
|
53
|
+
targetAppVersion: where?.targetAppVersion,
|
|
54
|
+
targetAppVersionIn: where?.targetAppVersionIn,
|
|
55
|
+
targetAppVersionNotNull: where?.targetAppVersionNotNull,
|
|
56
|
+
fingerprintHash: where?.fingerprintHash,
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
class _CursorPageQuery {
|
|
61
|
+
const _CursorPageQuery({
|
|
62
|
+
required this.reverseData,
|
|
63
|
+
required this.where,
|
|
64
|
+
required this.orderBy,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
final bool reverseData;
|
|
68
|
+
final DatabaseBundleQueryWhere where;
|
|
69
|
+
final DatabaseBundleQueryOrder orderBy;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
_CursorPageQuery _buildCursorPageQuery(
|
|
73
|
+
DatabaseBundleQueryWhere? where,
|
|
74
|
+
DatabaseBundleCursor cursor,
|
|
75
|
+
DatabaseBundleQueryOrder orderBy,
|
|
76
|
+
) {
|
|
77
|
+
final direction = orderBy.direction;
|
|
78
|
+
|
|
79
|
+
if (cursor.after != null) {
|
|
80
|
+
return _CursorPageQuery(
|
|
81
|
+
reverseData: false,
|
|
82
|
+
where: _mergeWhereWithIdFilter(
|
|
83
|
+
where,
|
|
84
|
+
DatabaseBundleIdFilter(
|
|
85
|
+
lt: direction == 'desc' ? cursor.after : null,
|
|
86
|
+
gt: direction == 'asc' ? cursor.after : null,
|
|
87
|
+
),
|
|
88
|
+
),
|
|
89
|
+
orderBy: orderBy,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (cursor.before != null) {
|
|
94
|
+
return _CursorPageQuery(
|
|
95
|
+
reverseData: true,
|
|
96
|
+
where: _mergeWhereWithIdFilter(
|
|
97
|
+
where,
|
|
98
|
+
DatabaseBundleIdFilter(
|
|
99
|
+
gt: direction == 'desc' ? cursor.before : null,
|
|
100
|
+
lt: direction == 'asc' ? cursor.before : null,
|
|
101
|
+
),
|
|
102
|
+
),
|
|
103
|
+
orderBy: DatabaseBundleQueryOrder(
|
|
104
|
+
field: orderBy.field,
|
|
105
|
+
direction: direction == 'desc' ? 'asc' : 'desc',
|
|
106
|
+
),
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return _CursorPageQuery(
|
|
111
|
+
reverseData: false,
|
|
112
|
+
where: where ?? const DatabaseBundleQueryWhere(),
|
|
113
|
+
orderBy: orderBy,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
DatabaseBundleQueryWhere _buildCountBeforeWhere(
|
|
118
|
+
DatabaseBundleQueryWhere? where,
|
|
119
|
+
String firstBundleId,
|
|
120
|
+
DatabaseBundleQueryOrder orderBy,
|
|
121
|
+
) {
|
|
122
|
+
return _mergeWhereWithIdFilter(
|
|
123
|
+
where,
|
|
124
|
+
DatabaseBundleIdFilter(
|
|
125
|
+
gt: orderBy.direction == 'desc' ? firstBundleId : null,
|
|
126
|
+
lt: orderBy.direction == 'asc' ? firstBundleId : null,
|
|
127
|
+
),
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
Paginated<List<Bundle>> _createPaginatedResult(
|
|
132
|
+
int total,
|
|
133
|
+
int limit,
|
|
134
|
+
int startIndex,
|
|
135
|
+
List<Bundle> data,
|
|
136
|
+
) {
|
|
137
|
+
final pagination = calculatePagination(total, limit: limit, offset: startIndex);
|
|
138
|
+
final nextCursor =
|
|
139
|
+
data.isNotEmpty && startIndex + data.length < total ? data.last.id : null;
|
|
140
|
+
final previousCursor =
|
|
141
|
+
data.isNotEmpty && startIndex > 0 ? data.first.id : null;
|
|
142
|
+
|
|
143
|
+
return Paginated(
|
|
144
|
+
data: data,
|
|
145
|
+
pagination: PaginationInfo(
|
|
146
|
+
total: pagination.total,
|
|
147
|
+
hasNextPage: pagination.hasNextPage,
|
|
148
|
+
hasPreviousPage: pagination.hasPreviousPage,
|
|
149
|
+
currentPage: pagination.currentPage,
|
|
150
|
+
totalPages: pagination.totalPages,
|
|
151
|
+
nextCursor: nextCursor,
|
|
152
|
+
previousCursor: previousCursor,
|
|
153
|
+
),
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
DatabaseBundleQueryOptions _expandLimitForUnitOfWork(
|
|
158
|
+
DatabaseBundleQueryOptions options,
|
|
159
|
+
BundleUnitOfWork unitOfWork,
|
|
160
|
+
) {
|
|
161
|
+
final extra = unitOfWork.listFetchExtraCount();
|
|
162
|
+
if (extra == 0) return options;
|
|
163
|
+
return DatabaseBundleQueryOptions(
|
|
164
|
+
where: options.where,
|
|
165
|
+
limit: options.limit + extra,
|
|
166
|
+
page: options.page,
|
|
167
|
+
offset: options.offset,
|
|
168
|
+
cursor: options.cursor,
|
|
169
|
+
orderBy: options.orderBy,
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
PaginationInfo _adjustPaginationTotal(
|
|
174
|
+
PaginationInfo pagination, {
|
|
175
|
+
required int limit,
|
|
176
|
+
required int totalDelta,
|
|
177
|
+
}) {
|
|
178
|
+
if (totalDelta == 0) return pagination;
|
|
179
|
+
final total = (pagination.total + totalDelta).clamp(0, 0x7FFFFFFF);
|
|
180
|
+
final hasPreviousPage = pagination.currentPage > 1;
|
|
181
|
+
final hasNextPage = pagination.currentPage * limit < total;
|
|
182
|
+
return PaginationInfo(
|
|
183
|
+
total: total,
|
|
184
|
+
hasNextPage: hasNextPage,
|
|
185
|
+
hasPreviousPage: hasPreviousPage,
|
|
186
|
+
currentPage: pagination.currentPage,
|
|
187
|
+
totalPages: total == 0 ? 0 : (total / limit).ceil(),
|
|
188
|
+
nextCursor: pagination.nextCursor,
|
|
189
|
+
previousCursor: pagination.previousCursor,
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/// The underlying database methods a plugin must implement.
|
|
194
|
+
abstract class AbstractDatabasePlugin {
|
|
195
|
+
bool get supportsCursorPagination => false;
|
|
196
|
+
|
|
197
|
+
Future<Bundle?> getBundleById(String bundleId);
|
|
198
|
+
|
|
199
|
+
Future<UpdateInfo?> getUpdateInfo(GetBundlesArgs args);
|
|
200
|
+
|
|
201
|
+
Future<Paginated<List<Bundle>>> getBundles(
|
|
202
|
+
DatabaseBundleQueryOptions options,
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
Future<List<String>> getChannels();
|
|
206
|
+
|
|
207
|
+
Future<void> commitBundle({
|
|
208
|
+
required List<BundleChange> changedSets,
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
Future<void> onUnmount();
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/// Configuration for [createDatabasePlugin].
|
|
215
|
+
class CreateDatabasePluginOptions<TConfig> {
|
|
216
|
+
const CreateDatabasePluginOptions({
|
|
217
|
+
required this.name,
|
|
218
|
+
required this.factory,
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
final String name;
|
|
222
|
+
final AbstractDatabasePlugin Function(TConfig config) factory;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/// Double-curried factory that creates a [DatabasePlugin] with lazy
|
|
226
|
+
/// initialization, UnitOfWork integration, and hook support.
|
|
227
|
+
///
|
|
228
|
+
/// Usage:
|
|
229
|
+
/// ```dart
|
|
230
|
+
/// final supabaseDatabase = createDatabasePlugin<SupabaseConfig>(
|
|
231
|
+
/// name: 'supabaseDatabase',
|
|
232
|
+
/// factory: (config) => SupabaseDatabaseImpl(config),
|
|
233
|
+
/// );
|
|
234
|
+
/// // First curry: config + hooks → factory
|
|
235
|
+
/// final createDb = supabaseDatabase(config, hooks);
|
|
236
|
+
/// // Second curry: new plugin instance
|
|
237
|
+
/// final db = createDb();
|
|
238
|
+
/// ```
|
|
239
|
+
DatabasePlugin Function() Function(TConfig config, [DatabasePluginHooks? hooks])
|
|
240
|
+
createDatabasePlugin<TConfig>({
|
|
241
|
+
required String name,
|
|
242
|
+
required AbstractDatabasePlugin Function(TConfig config) factory,
|
|
243
|
+
}) {
|
|
244
|
+
return (TConfig config, [DatabasePluginHooks? hooks]) {
|
|
245
|
+
AbstractDatabasePlugin? cachedMethods;
|
|
246
|
+
AbstractDatabasePlugin getMethods() {
|
|
247
|
+
cachedMethods ??= factory(config);
|
|
248
|
+
return cachedMethods!;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return () {
|
|
252
|
+
final instanceUnitOfWork = BundleUnitOfWork();
|
|
253
|
+
|
|
254
|
+
BundleUnitOfWork getMutationUnitOfWork(
|
|
255
|
+
Map<String, Object?>? context) =>
|
|
256
|
+
getRequestBundleUnitOfWork(context) ?? instanceUnitOfWork;
|
|
257
|
+
|
|
258
|
+
Future<Paginated<List<Bundle>>> runGetBundles(
|
|
259
|
+
DatabaseBundleQueryOptions options,
|
|
260
|
+
) =>
|
|
261
|
+
getMethods().getBundles(options);
|
|
262
|
+
|
|
263
|
+
Future<Paginated<List<Bundle>>> getBundlesWithLegacyCursorFallback(
|
|
264
|
+
DatabaseBundleQueryOptions options,
|
|
265
|
+
) async {
|
|
266
|
+
final orderBy = options.orderBy ?? _defaultDescOrder;
|
|
267
|
+
final baseWhere = options.where;
|
|
268
|
+
|
|
269
|
+
final totalResult = await runGetBundles(
|
|
270
|
+
DatabaseBundleQueryOptions(
|
|
271
|
+
where: baseWhere,
|
|
272
|
+
limit: 1,
|
|
273
|
+
offset: 0,
|
|
274
|
+
orderBy: orderBy,
|
|
275
|
+
),
|
|
276
|
+
);
|
|
277
|
+
final total = totalResult.pagination.total;
|
|
278
|
+
|
|
279
|
+
if (options.cursor?.after == null && options.cursor?.before == null) {
|
|
280
|
+
final firstPage = await runGetBundles(
|
|
281
|
+
DatabaseBundleQueryOptions(
|
|
282
|
+
where: baseWhere,
|
|
283
|
+
limit: options.limit,
|
|
284
|
+
offset: 0,
|
|
285
|
+
orderBy: orderBy,
|
|
286
|
+
),
|
|
287
|
+
);
|
|
288
|
+
return _createPaginatedResult(total, options.limit, 0, firstPage.data);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
final cursorQuery = _buildCursorPageQuery(
|
|
292
|
+
baseWhere,
|
|
293
|
+
options.cursor!,
|
|
294
|
+
orderBy,
|
|
295
|
+
);
|
|
296
|
+
final cursorPage = await runGetBundles(
|
|
297
|
+
DatabaseBundleQueryOptions(
|
|
298
|
+
where: cursorQuery.where,
|
|
299
|
+
limit: options.limit,
|
|
300
|
+
offset: 0,
|
|
301
|
+
orderBy: cursorQuery.orderBy,
|
|
302
|
+
),
|
|
303
|
+
);
|
|
304
|
+
final data = cursorQuery.reverseData
|
|
305
|
+
? cursorPage.data.reversed.toList()
|
|
306
|
+
: cursorPage.data;
|
|
307
|
+
|
|
308
|
+
if (data.isEmpty) {
|
|
309
|
+
return Paginated(
|
|
310
|
+
data: data,
|
|
311
|
+
pagination: PaginationInfo(
|
|
312
|
+
total: total,
|
|
313
|
+
hasNextPage: false,
|
|
314
|
+
hasPreviousPage: false,
|
|
315
|
+
currentPage: 1,
|
|
316
|
+
totalPages: 0,
|
|
317
|
+
nextCursor: options.cursor?.before,
|
|
318
|
+
previousCursor: options.cursor?.after,
|
|
319
|
+
),
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
final firstBundleId = data.first.id;
|
|
324
|
+
final countBeforeResult = await runGetBundles(
|
|
325
|
+
DatabaseBundleQueryOptions(
|
|
326
|
+
where: _buildCountBeforeWhere(baseWhere, firstBundleId, orderBy),
|
|
327
|
+
limit: 1,
|
|
328
|
+
offset: 0,
|
|
329
|
+
orderBy: orderBy,
|
|
330
|
+
),
|
|
331
|
+
);
|
|
332
|
+
|
|
333
|
+
return _createPaginatedResult(
|
|
334
|
+
total,
|
|
335
|
+
options.limit,
|
|
336
|
+
countBeforeResult.pagination.total,
|
|
337
|
+
data,
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Build the plugin methods.
|
|
342
|
+
return _DatabasePluginImpl(
|
|
343
|
+
name: name,
|
|
344
|
+
getMethods: getMethods,
|
|
345
|
+
getMutationUnitOfWork: getMutationUnitOfWork,
|
|
346
|
+
instanceUnitOfWork: instanceUnitOfWork,
|
|
347
|
+
runGetBundles: runGetBundles,
|
|
348
|
+
getBundlesWithLegacyCursorFallback: getBundlesWithLegacyCursorFallback,
|
|
349
|
+
hooks: hooks,
|
|
350
|
+
) as DatabasePlugin;
|
|
351
|
+
};
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/// Internal implementation of DatabasePlugin.
|
|
356
|
+
class _DatabasePluginImpl implements DatabasePlugin {
|
|
357
|
+
_DatabasePluginImpl({
|
|
358
|
+
required this.name,
|
|
359
|
+
required this.getMethods,
|
|
360
|
+
required this.getMutationUnitOfWork,
|
|
361
|
+
required this.instanceUnitOfWork,
|
|
362
|
+
required this.runGetBundles,
|
|
363
|
+
required this.getBundlesWithLegacyCursorFallback,
|
|
364
|
+
this.hooks,
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
@override
|
|
368
|
+
final String name;
|
|
369
|
+
final AbstractDatabasePlugin Function() getMethods;
|
|
370
|
+
final BundleUnitOfWork Function(Map<String, Object?>?) getMutationUnitOfWork;
|
|
371
|
+
final BundleUnitOfWork instanceUnitOfWork;
|
|
372
|
+
final Future<Paginated<List<Bundle>>> Function(DatabaseBundleQueryOptions)
|
|
373
|
+
runGetBundles;
|
|
374
|
+
final Future<Paginated<List<Bundle>>> Function(DatabaseBundleQueryOptions)
|
|
375
|
+
getBundlesWithLegacyCursorFallback;
|
|
376
|
+
final DatabasePluginHooks? hooks;
|
|
377
|
+
|
|
378
|
+
@override
|
|
379
|
+
Future<void> onUnmount() => getMethods().onUnmount();
|
|
380
|
+
|
|
381
|
+
@override
|
|
382
|
+
Future<List<String>> getChannels() => getMethods().getChannels();
|
|
383
|
+
|
|
384
|
+
@override
|
|
385
|
+
Future<Bundle?> getBundleById(String bundleId) async {
|
|
386
|
+
final requestUoW =
|
|
387
|
+
getRequestBundleUnitOfWork(null); // context not passed through here
|
|
388
|
+
if (requestUoW != null) {
|
|
389
|
+
return requestUoW.getById(bundleId, () =>
|
|
390
|
+
getMethods().getBundleById(bundleId));
|
|
391
|
+
}
|
|
392
|
+
final pending = instanceUnitOfWork.peekChanged(bundleId);
|
|
393
|
+
if (pending is TrackedBundleFound) return pending.value;
|
|
394
|
+
return getMethods().getBundleById(bundleId);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
@override
|
|
398
|
+
Future<UpdateInfo?> getUpdateInfo(GetBundlesArgs args) async {
|
|
399
|
+
final methods = getMethods();
|
|
400
|
+
return methods.getUpdateInfo(args);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
@override
|
|
404
|
+
Future<Paginated<List<Bundle>>> getBundles(
|
|
405
|
+
DatabaseBundleQueryOptions options,
|
|
406
|
+
) async {
|
|
407
|
+
final methods = getMethods();
|
|
408
|
+
final unitOfWork = getMutationUnitOfWork(null);
|
|
409
|
+
final shouldOverlay = instanceUnitOfWork.hasChanges();
|
|
410
|
+
final normalizedOptions = DatabaseBundleQueryOptions(
|
|
411
|
+
where: options.where,
|
|
412
|
+
limit: options.limit,
|
|
413
|
+
page: _normalizePage(options.page),
|
|
414
|
+
cursor: options.cursor,
|
|
415
|
+
orderBy: options.orderBy ?? _defaultDescOrder,
|
|
416
|
+
);
|
|
417
|
+
|
|
418
|
+
Paginated<List<Bundle>> overlayResult(Paginated<List<Bundle>> result) {
|
|
419
|
+
return Paginated(
|
|
420
|
+
data: unitOfWork.overlayList(
|
|
421
|
+
result.data,
|
|
422
|
+
limit: normalizedOptions.limit,
|
|
423
|
+
orderBy: normalizedOptions.orderBy,
|
|
424
|
+
where: normalizedOptions.where,
|
|
425
|
+
),
|
|
426
|
+
pagination: _adjustPaginationTotal(
|
|
427
|
+
result.pagination,
|
|
428
|
+
limit: normalizedOptions.limit,
|
|
429
|
+
totalDelta: unitOfWork.totalDelta(normalizedOptions.where),
|
|
430
|
+
),
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if (normalizedOptions.page != null) {
|
|
435
|
+
final page = normalizedOptions.page!;
|
|
436
|
+
final requestedOffset = (page - 1) * normalizedOptions.limit;
|
|
437
|
+
final fetchOptions = _expandLimitForUnitOfWork(normalizedOptions, unitOfWork);
|
|
438
|
+
var pageResult = await runGetBundles(
|
|
439
|
+
DatabaseBundleQueryOptions(
|
|
440
|
+
where: fetchOptions.where,
|
|
441
|
+
limit: fetchOptions.limit,
|
|
442
|
+
offset: requestedOffset,
|
|
443
|
+
orderBy: fetchOptions.orderBy,
|
|
444
|
+
),
|
|
445
|
+
);
|
|
446
|
+
|
|
447
|
+
final total = pageResult.pagination.total;
|
|
448
|
+
final totalPages =
|
|
449
|
+
total == 0 ? 0 : (total / normalizedOptions.limit).ceil();
|
|
450
|
+
final maxOffset =
|
|
451
|
+
totalPages == 0 ? 0 : (totalPages - 1) * normalizedOptions.limit;
|
|
452
|
+
final resolvedOffset =
|
|
453
|
+
requestedOffset < maxOffset ? requestedOffset : maxOffset;
|
|
454
|
+
|
|
455
|
+
if (resolvedOffset != requestedOffset) {
|
|
456
|
+
pageResult = await runGetBundles(
|
|
457
|
+
DatabaseBundleQueryOptions(
|
|
458
|
+
where: fetchOptions.where,
|
|
459
|
+
limit: fetchOptions.limit,
|
|
460
|
+
offset: resolvedOffset,
|
|
461
|
+
orderBy: fetchOptions.orderBy,
|
|
462
|
+
),
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
final result = _createPaginatedResult(
|
|
467
|
+
total,
|
|
468
|
+
normalizedOptions.limit,
|
|
469
|
+
resolvedOffset,
|
|
470
|
+
pageResult.data,
|
|
471
|
+
);
|
|
472
|
+
return shouldOverlay ? overlayResult(result) : result;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
if (methods.supportsCursorPagination) {
|
|
476
|
+
final fetchOptions = _expandLimitForUnitOfWork(normalizedOptions, unitOfWork);
|
|
477
|
+
final result = await runGetBundles(fetchOptions);
|
|
478
|
+
return shouldOverlay ? overlayResult(result) : result;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
final result = await getBundlesWithLegacyCursorFallback(
|
|
482
|
+
shouldOverlay
|
|
483
|
+
? _expandLimitForUnitOfWork(normalizedOptions, unitOfWork)
|
|
484
|
+
: normalizedOptions,
|
|
485
|
+
);
|
|
486
|
+
return shouldOverlay ? overlayResult(result) : result;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
@override
|
|
490
|
+
Future<void> commitBundle() async {
|
|
491
|
+
final methods = getMethods();
|
|
492
|
+
final unitOfWork = getMutationUnitOfWork(null);
|
|
493
|
+
await methods.commitBundle(
|
|
494
|
+
changedSets: unitOfWork.changedSets(),
|
|
495
|
+
);
|
|
496
|
+
unitOfWork.clear();
|
|
497
|
+
final cb = hooks?.onDatabaseUpdated;
|
|
498
|
+
if (cb != null) await cb();
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
@override
|
|
502
|
+
Future<void> updateBundle(
|
|
503
|
+
String targetBundleId,
|
|
504
|
+
Map<String, Object?> newBundle,
|
|
505
|
+
) async {
|
|
506
|
+
final unitOfWork = getMutationUnitOfWork(null);
|
|
507
|
+
final current = await unitOfWork.getById(targetBundleId, () =>
|
|
508
|
+
getMethods().getBundleById(targetBundleId));
|
|
509
|
+
if (current == null) {
|
|
510
|
+
throw StateError('targetBundleId not found');
|
|
511
|
+
}
|
|
512
|
+
final updated = mergeBundleUpdate(current, newBundle);
|
|
513
|
+
unitOfWork.markUpdate(updated);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
@override
|
|
517
|
+
Future<void> appendBundle(Bundle insertBundle) async {
|
|
518
|
+
getMutationUnitOfWork(null).markInsert(insertBundle);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
@override
|
|
522
|
+
Future<void> deleteBundle(Bundle deleteBundle) async {
|
|
523
|
+
getMutationUnitOfWork(null).markDelete(deleteBundle);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/// Create a curried storage key builder that prepends an optional base path.
|
|
2
|
+
///
|
|
3
|
+
/// ```dart
|
|
4
|
+
/// final getKey = createStorageKeyBuilder('bundles');
|
|
5
|
+
/// getKey('v1', 'patch.zip'); // → 'bundles/v1/patch.zip'
|
|
6
|
+
/// ```
|
|
7
|
+
///
|
|
8
|
+
/// Faithful port of hot-updater `createStorageKeyBuilder.ts`.
|
|
9
|
+
String Function(String, [String, String, String]) createStorageKeyBuilder(
|
|
10
|
+
String? basePath,
|
|
11
|
+
) {
|
|
12
|
+
return (String a, [String b = '', String c = '', String d = '']) {
|
|
13
|
+
return [basePath ?? '', a, b, c, d].where((s) => s.isNotEmpty).join('/');
|
|
14
|
+
};
|
|
15
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import 'types.dart';
|
|
2
|
+
|
|
3
|
+
/// Create a deploy/CLI/console storage plugin (node profile only).
|
|
4
|
+
StoragePlugin Function(TConfig config, [StoragePluginHooks? hooks])
|
|
5
|
+
createNodeStoragePlugin<TConfig>({
|
|
6
|
+
required String name,
|
|
7
|
+
required String supportedProtocol,
|
|
8
|
+
required NodeStorageProfile Function(TConfig config) factory,
|
|
9
|
+
}) {
|
|
10
|
+
return (TConfig config, [StoragePluginHooks? hooks]) {
|
|
11
|
+
NodeStorageProfile? cached;
|
|
12
|
+
NodeStorageProfile getProfile() {
|
|
13
|
+
cached ??= factory(config);
|
|
14
|
+
return cached!;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return _ProfiledStoragePlugin(
|
|
18
|
+
name: name,
|
|
19
|
+
supportedProtocol: supportedProtocol,
|
|
20
|
+
nodeProfile: _WrappedNodeProfile(getProfile, hooks),
|
|
21
|
+
);
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/// Create an update-check runtime storage plugin.
|
|
26
|
+
StoragePlugin Function(TConfig config, [StoragePluginHooks? hooks])
|
|
27
|
+
createRuntimeStoragePlugin<TConfig>({
|
|
28
|
+
required String name,
|
|
29
|
+
required String supportedProtocol,
|
|
30
|
+
required RuntimeStorageProfile Function(TConfig config) factory,
|
|
31
|
+
}) {
|
|
32
|
+
return (TConfig config, [StoragePluginHooks? hooks]) {
|
|
33
|
+
RuntimeStorageProfile? cached;
|
|
34
|
+
RuntimeStorageProfile getProfile() {
|
|
35
|
+
cached ??= factory(config);
|
|
36
|
+
return cached!;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return _ProfiledStoragePlugin(
|
|
40
|
+
name: name,
|
|
41
|
+
supportedProtocol: supportedProtocol,
|
|
42
|
+
runtimeProfile: getProfile,
|
|
43
|
+
);
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/// Create a storage plugin supporting both node and runtime profiles.
|
|
48
|
+
StoragePlugin Function(TConfig config, [StoragePluginHooks? hooks])
|
|
49
|
+
createUniversalStoragePlugin<TConfig>({
|
|
50
|
+
required String name,
|
|
51
|
+
required String supportedProtocol,
|
|
52
|
+
required ({
|
|
53
|
+
NodeStorageProfile node,
|
|
54
|
+
RuntimeStorageProfile runtime,
|
|
55
|
+
}) Function(TConfig config) factory,
|
|
56
|
+
}) {
|
|
57
|
+
return (TConfig config, [StoragePluginHooks? hooks]) {
|
|
58
|
+
({NodeStorageProfile node, RuntimeStorageProfile runtime})? cached;
|
|
59
|
+
({NodeStorageProfile node, RuntimeStorageProfile runtime}) getProfiles() {
|
|
60
|
+
cached ??= factory(config);
|
|
61
|
+
return cached!;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return _ProfiledStoragePlugin(
|
|
65
|
+
name: name,
|
|
66
|
+
supportedProtocol: supportedProtocol,
|
|
67
|
+
nodeProfile: _WrappedNodeProfile(() => getProfiles().node, hooks),
|
|
68
|
+
runtimeProfile: () => getProfiles().runtime,
|
|
69
|
+
);
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/// Wraps a node profile's upload with hook callback.
|
|
74
|
+
class _WrappedNodeProfile implements NodeStorageProfile {
|
|
75
|
+
_WrappedNodeProfile(this._delegate, this._hooks);
|
|
76
|
+
|
|
77
|
+
final NodeStorageProfile Function() _delegate;
|
|
78
|
+
final StoragePluginHooks? _hooks;
|
|
79
|
+
|
|
80
|
+
NodeStorageProfile get _node => _delegate();
|
|
81
|
+
|
|
82
|
+
@override
|
|
83
|
+
Future<Map<String, String>> upload(String key, String filePath) async {
|
|
84
|
+
final result = await _node.upload(key, filePath);
|
|
85
|
+
final cb = _hooks?.onStorageUploaded;
|
|
86
|
+
if (cb != null) await cb();
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@override
|
|
91
|
+
Future<bool> exists(String storageUri) => _node.exists(storageUri);
|
|
92
|
+
|
|
93
|
+
@override
|
|
94
|
+
Future<void> delete(String storageUri) => _node.delete(storageUri);
|
|
95
|
+
|
|
96
|
+
@override
|
|
97
|
+
Future<void> downloadFile(String storageUri, String filePath) =>
|
|
98
|
+
_node.downloadFile(storageUri, filePath);
|
|
99
|
+
|
|
100
|
+
@override
|
|
101
|
+
Future<List<StorageObject>> listObjects([String? prefix]) =>
|
|
102
|
+
_node.listObjects(prefix);
|
|
103
|
+
|
|
104
|
+
@override
|
|
105
|
+
Future<void> deleteObjects(List<String> keys) =>
|
|
106
|
+
_node.deleteObjects(keys);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/// Storage plugin with lazy profile resolution.
|
|
110
|
+
class _ProfiledStoragePlugin implements StoragePlugin {
|
|
111
|
+
_ProfiledStoragePlugin({
|
|
112
|
+
required this.name,
|
|
113
|
+
required this.supportedProtocol,
|
|
114
|
+
this.nodeProfile,
|
|
115
|
+
this.runtimeProfile,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
@override
|
|
119
|
+
final String name;
|
|
120
|
+
@override
|
|
121
|
+
final String supportedProtocol;
|
|
122
|
+
final NodeStorageProfile? nodeProfile;
|
|
123
|
+
final RuntimeStorageProfile Function()? runtimeProfile;
|
|
124
|
+
|
|
125
|
+
@override
|
|
126
|
+
StoragePluginProfiles get profiles => StoragePluginProfiles(
|
|
127
|
+
node: nodeProfile,
|
|
128
|
+
runtime: runtimeProfile?.call(),
|
|
129
|
+
);
|
|
130
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import 'package:flutter_patcher_core/flutter_patcher_core.dart'
|
|
2
|
+
show semverSatisfies;
|
|
3
|
+
|
|
4
|
+
/// Filter target app versions that are compatible with the current app version.
|
|
5
|
+
///
|
|
6
|
+
/// Returns only versions compatible per semver, sorted descending (newest first).
|
|
7
|
+
///
|
|
8
|
+
/// Faithful port of hot-updater `filterCompatibleAppVersions.ts`.
|
|
9
|
+
List<String> filterCompatibleAppVersions(
|
|
10
|
+
List<String> targetAppVersionList,
|
|
11
|
+
String currentVersion,
|
|
12
|
+
) {
|
|
13
|
+
final compatible = targetAppVersionList
|
|
14
|
+
.where((version) => semverSatisfies(version, currentVersion))
|
|
15
|
+
.toList();
|
|
16
|
+
compatible.sort((a, b) => b.compareTo(a));
|
|
17
|
+
return compatible;
|
|
18
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/// Generate a minimum-priority bundle ID: UUIDv7 with zeroed random bits.
|
|
2
|
+
///
|
|
3
|
+
/// Used by the DB plugin to create bundle IDs that sort before any
|
|
4
|
+
/// random-bit bundle created at the same millisecond.
|
|
5
|
+
///
|
|
6
|
+
/// Faithful port of hot-updater `generateMinBundleId.ts`.
|
|
7
|
+
String generateMinBundleId() {
|
|
8
|
+
final timestamp = DateTime.now().millisecondsSinceEpoch;
|
|
9
|
+
final tsHex = timestamp.toRadixString(16).padLeft(12, '0');
|
|
10
|
+
|
|
11
|
+
// UUIDv7 layout: 48-bit ts | ver(0111)+12 rand_a(0) | var(10)+62 rand_b(0)
|
|
12
|
+
final timeHigh = tsHex.substring(0, 8);
|
|
13
|
+
final timeLow = tsHex.substring(8, 12);
|
|
14
|
+
const versionAndRandom = '7000'; // version 7, rand_a = 0
|
|
15
|
+
const variantAndRandom = '8000'; // variant 10, rand_b = 0
|
|
16
|
+
const node = '000000000000';
|
|
17
|
+
|
|
18
|
+
return '$timeHigh-$timeLow-$versionAndRandom-$variantAndRandom-$node';
|
|
19
|
+
}
|