@_nazmiforreal/flutter-ota 0.1.0 → 0.1.1
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 +37 -39
- package/dart-src/packages/cli-tools/.dart_tool/package_config.json +496 -0
- package/dart-src/packages/cli-tools/.dart_tool/package_graph.json +743 -0
- 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.lock +638 -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.lock +381 -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.lock +435 -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.lock +411 -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.lock +396 -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.lock +427 -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.lock +411 -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.lock +515 -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 +32 -23
- /package/bin/{flutter-patcher-linux → flutter-patcher-linux-x64} +0 -0
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
/// Faithful port of hot-updater `plugins/postgres/src/postgres.ts`.
|
|
2
|
+
library;
|
|
3
|
+
|
|
4
|
+
import 'package:flutter_patcher_core/flutter_patcher_core.dart'
|
|
5
|
+
show Bundle, UpdateInfo;
|
|
6
|
+
import 'package:flutter_patcher_plugin_core/flutter_patcher_plugin_core.dart'
|
|
7
|
+
show
|
|
8
|
+
AbstractDatabasePlugin,
|
|
9
|
+
BundleChange,
|
|
10
|
+
BundleChangeOperation,
|
|
11
|
+
DatabaseBundleQueryOptions,
|
|
12
|
+
DatabaseBundleQueryOrder,
|
|
13
|
+
DatabaseBundleQueryWhere,
|
|
14
|
+
DatabasePlugin,
|
|
15
|
+
DatabasePluginHooks,
|
|
16
|
+
GetBundlesArgs,
|
|
17
|
+
Paginated,
|
|
18
|
+
calculatePagination,
|
|
19
|
+
createDatabasePlugin;
|
|
20
|
+
|
|
21
|
+
import 'postgres_bundle_mapper.dart';
|
|
22
|
+
import 'postgres_client.dart';
|
|
23
|
+
import 'postgres_config.dart';
|
|
24
|
+
import 'postgres_get_update_info.dart' as pg_ui;
|
|
25
|
+
import 'postgres_types.dart';
|
|
26
|
+
|
|
27
|
+
/// Postgres-backed [DatabasePlugin] factory.
|
|
28
|
+
///
|
|
29
|
+
/// Mirrors `postgres` from hot-updater: a `createDatabasePlugin` wrapper whose
|
|
30
|
+
/// inner factory builds a Postgres client and returns an [AbstractDatabasePlugin]
|
|
31
|
+
/// implementation talking to the `bundles` / `bundle_patches` tables and the
|
|
32
|
+
/// update-info RPCs.
|
|
33
|
+
DatabasePlugin Function() Function(
|
|
34
|
+
PostgresConfig config, [
|
|
35
|
+
DatabasePluginHooks? hooks,
|
|
36
|
+
]) postgresDatabase = createDatabasePlugin<PostgresConfig>(
|
|
37
|
+
name: 'postgresDatabase',
|
|
38
|
+
factory: (config) {
|
|
39
|
+
final client = config.clientFactory != null
|
|
40
|
+
? config.clientFactory!(config)
|
|
41
|
+
: PostgresClient.connect(config);
|
|
42
|
+
|
|
43
|
+
Future<Map<String, List<PostgresBundlePatchRow>>> fetchPatchMap(
|
|
44
|
+
List<String> bundleIds,
|
|
45
|
+
) async {
|
|
46
|
+
final patchMap = <String, List<PostgresBundlePatchRow>>{};
|
|
47
|
+
if (bundleIds.isEmpty) return patchMap;
|
|
48
|
+
|
|
49
|
+
final rows = await client.execute(
|
|
50
|
+
'SELECT * FROM bundle_patches '
|
|
51
|
+
'WHERE bundle_id = ANY(@bundle_ids) ORDER BY order_index ASC',
|
|
52
|
+
{'bundle_ids': bundleIds},
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
for (final raw in rows) {
|
|
56
|
+
final row = PostgresBundlePatchRow.fromJson(raw);
|
|
57
|
+
final current = patchMap[row.bundleId] ?? [];
|
|
58
|
+
current.add(row);
|
|
59
|
+
patchMap[row.bundleId] = current;
|
|
60
|
+
}
|
|
61
|
+
return patchMap;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return _PostgresDatabasePlugin(
|
|
65
|
+
client: client,
|
|
66
|
+
fetchPatchMap: fetchPatchMap,
|
|
67
|
+
);
|
|
68
|
+
},
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
/// Build a SQL `WHERE` fragment (with `@pN` placeholders) from [where].
|
|
72
|
+
(String where, Map<String, dynamic> params) _buildWhere(
|
|
73
|
+
DatabaseBundleQueryWhere? where,
|
|
74
|
+
) {
|
|
75
|
+
final conditions = <String>[];
|
|
76
|
+
final params = <String, dynamic>{};
|
|
77
|
+
if (where == null) return ('', params);
|
|
78
|
+
|
|
79
|
+
var index = 0;
|
|
80
|
+
String next() => '@p${index++}';
|
|
81
|
+
|
|
82
|
+
void add(String sql, dynamic value) {
|
|
83
|
+
final p = next();
|
|
84
|
+
conditions.add(sql.replaceAll('?', p));
|
|
85
|
+
params[p] = value;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (where.channel != null) {
|
|
89
|
+
add('channel = ?', where.channel);
|
|
90
|
+
}
|
|
91
|
+
if (where.platform != null) {
|
|
92
|
+
add('platform = ?', where.platform!.value);
|
|
93
|
+
}
|
|
94
|
+
if (where.enabled != null) {
|
|
95
|
+
add('enabled = ?', where.enabled);
|
|
96
|
+
}
|
|
97
|
+
if (where.fingerprintHash != null) {
|
|
98
|
+
if (where.fingerprintHash == null) {
|
|
99
|
+
conditions.add('fingerprint_hash IS NULL');
|
|
100
|
+
} else {
|
|
101
|
+
add('fingerprint_hash = ?', where.fingerprintHash);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (where.targetAppVersion != null) {
|
|
105
|
+
if (where.targetAppVersion == null) {
|
|
106
|
+
conditions.add('target_app_version IS NULL');
|
|
107
|
+
} else {
|
|
108
|
+
add('target_app_version = ?', where.targetAppVersion);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (where.targetAppVersionIn != null) {
|
|
112
|
+
add('target_app_version = ANY(?)', where.targetAppVersionIn);
|
|
113
|
+
}
|
|
114
|
+
if (where.targetAppVersionNotNull == true) {
|
|
115
|
+
conditions.add('target_app_version IS NOT NULL');
|
|
116
|
+
}
|
|
117
|
+
if (where.id != null) {
|
|
118
|
+
final id = where.id!;
|
|
119
|
+
if (id.eq != null) add('id = ?', id.eq);
|
|
120
|
+
if (id.gt != null) add('id > ?', id.gt);
|
|
121
|
+
if (id.gte != null) add('id >= ?', id.gte);
|
|
122
|
+
if (id.lt != null) add('id < ?', id.lt);
|
|
123
|
+
if (id.lte != null) add('id <= ?', id.lte);
|
|
124
|
+
if (id.ins != null) add('id = ANY(?)', id.ins);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
conditions.isEmpty ? '' : 'WHERE ${conditions.join(' AND ')}',
|
|
129
|
+
params,
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
class _PostgresDatabasePlugin implements AbstractDatabasePlugin {
|
|
134
|
+
final PostgresClientLike client;
|
|
135
|
+
final Future<Map<String, List<PostgresBundlePatchRow>>> Function(
|
|
136
|
+
List<String> bundleIds,
|
|
137
|
+
) fetchPatchMap;
|
|
138
|
+
|
|
139
|
+
_PostgresDatabasePlugin({
|
|
140
|
+
required this.client,
|
|
141
|
+
required this.fetchPatchMap,
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
@override
|
|
145
|
+
bool get supportsCursorPagination => false;
|
|
146
|
+
|
|
147
|
+
@override
|
|
148
|
+
Future<UpdateInfo?> getUpdateInfo(GetBundlesArgs args) =>
|
|
149
|
+
pg_ui.getUpdateInfo(client, args);
|
|
150
|
+
|
|
151
|
+
@override
|
|
152
|
+
Future<Bundle?> getBundleById(String bundleId) async {
|
|
153
|
+
final results = await Future.wait([
|
|
154
|
+
client.execute(
|
|
155
|
+
'SELECT * FROM bundles WHERE id = @p0',
|
|
156
|
+
{'p0': bundleId},
|
|
157
|
+
),
|
|
158
|
+
fetchPatchMap([bundleId]),
|
|
159
|
+
]);
|
|
160
|
+
|
|
161
|
+
final data = results[0] as List<Map<String, dynamic>>;
|
|
162
|
+
if (data.isEmpty) return null;
|
|
163
|
+
final row = PostgresBundleRow.fromJson(data.first);
|
|
164
|
+
final patches = (results[1] as Map<String, List<PostgresBundlePatchRow>>)[bundleId] ?? [];
|
|
165
|
+
return mapRowToBundle(row, patches);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
@override
|
|
169
|
+
Future<Paginated<List<Bundle>>> getBundles(
|
|
170
|
+
DatabaseBundleQueryOptions options,
|
|
171
|
+
) async {
|
|
172
|
+
final where = options.where;
|
|
173
|
+
final limit = options.limit;
|
|
174
|
+
final orderBy =
|
|
175
|
+
options.orderBy ?? const DatabaseBundleQueryOrder(direction: 'desc');
|
|
176
|
+
final offset =
|
|
177
|
+
options.offset ?? (options.page != null ? (options.page! - 1) * limit : 0);
|
|
178
|
+
|
|
179
|
+
if ((where?.targetAppVersionIn != null && where!.targetAppVersionIn!.isEmpty) ||
|
|
180
|
+
(where?.id?.ins != null && where!.id!.ins!.isEmpty)) {
|
|
181
|
+
return Paginated(
|
|
182
|
+
data: const [],
|
|
183
|
+
pagination: calculatePagination(0, limit: limit, offset: offset),
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
final (whereSql, whereParams) = _buildWhere(where);
|
|
188
|
+
|
|
189
|
+
final countRows = await client.execute(
|
|
190
|
+
'SELECT COUNT(*) AS total FROM bundles $whereSql',
|
|
191
|
+
whereParams,
|
|
192
|
+
);
|
|
193
|
+
final total = countRows.isEmpty
|
|
194
|
+
? 0
|
|
195
|
+
: (countRows.first['total'] as num?)?.toInt() ?? 0;
|
|
196
|
+
|
|
197
|
+
final selectParams = <String, dynamic>{...whereParams};
|
|
198
|
+
if (limit > 0) selectParams['@limit'] = limit;
|
|
199
|
+
if (offset > 0) selectParams['@offset'] = offset;
|
|
200
|
+
|
|
201
|
+
final dataRows = await client.execute(
|
|
202
|
+
'SELECT * FROM bundles $whereSql '
|
|
203
|
+
'ORDER BY id ${orderBy.direction == 'asc' ? 'asc' : 'desc'}'
|
|
204
|
+
'${limit > 0 ? ' LIMIT @limit' : ''}'
|
|
205
|
+
'${offset > 0 ? ' OFFSET @offset' : ''}',
|
|
206
|
+
selectParams,
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
final patchMap = await fetchPatchMap(
|
|
210
|
+
dataRows.map((row) => row['id'] as String).toList(),
|
|
211
|
+
);
|
|
212
|
+
final bundles = dataRows.map((row) {
|
|
213
|
+
final bundleRow = PostgresBundleRow.fromJson(row);
|
|
214
|
+
return mapRowToBundle(bundleRow, patchMap[bundleRow.id] ?? []);
|
|
215
|
+
}).toList();
|
|
216
|
+
|
|
217
|
+
return Paginated(
|
|
218
|
+
data: bundles,
|
|
219
|
+
pagination: calculatePagination(total, limit: limit, offset: offset),
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
@override
|
|
224
|
+
Future<List<String>> getChannels() async {
|
|
225
|
+
final rows = await client.execute(
|
|
226
|
+
'SELECT channel FROM bundles GROUP BY channel',
|
|
227
|
+
{},
|
|
228
|
+
);
|
|
229
|
+
return rows.map((row) => row['channel'] as String).toList();
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
@override
|
|
233
|
+
Future<void> commitBundle({
|
|
234
|
+
required List<BundleChange> changedSets,
|
|
235
|
+
}) async {
|
|
236
|
+
if (changedSets.isEmpty) return;
|
|
237
|
+
|
|
238
|
+
await client.runTx((tx) async {
|
|
239
|
+
for (final op in changedSets) {
|
|
240
|
+
if (op.operation == BundleChangeOperation.delete) {
|
|
241
|
+
await tx.execute(
|
|
242
|
+
'DELETE FROM bundle_patches WHERE bundle_id = @p0',
|
|
243
|
+
{'p0': op.data.id},
|
|
244
|
+
);
|
|
245
|
+
await tx.execute(
|
|
246
|
+
'DELETE FROM bundle_patches WHERE base_bundle_id = @p0',
|
|
247
|
+
{'p0': op.data.id},
|
|
248
|
+
);
|
|
249
|
+
final result = await tx.execute(
|
|
250
|
+
'DELETE FROM bundles WHERE id = @p0 RETURNING id',
|
|
251
|
+
{'p0': op.data.id},
|
|
252
|
+
);
|
|
253
|
+
if (result.isEmpty) {
|
|
254
|
+
throw StateError('Bundle with id ${op.data.id} not found');
|
|
255
|
+
}
|
|
256
|
+
} else {
|
|
257
|
+
final bundle = op.data;
|
|
258
|
+
final values = bundleToRowValues(bundle);
|
|
259
|
+
final columns = values.keys.toList();
|
|
260
|
+
final insertCols = columns.join(', ');
|
|
261
|
+
final placeholders =
|
|
262
|
+
columns.map((c) => '@${c.replaceAll('.', '_')}').join(', ');
|
|
263
|
+
final updateSets = columns
|
|
264
|
+
.where((c) => c != 'id')
|
|
265
|
+
.map((c) => '$c = EXCLUDED.$c')
|
|
266
|
+
.join(', ');
|
|
267
|
+
await tx.execute(
|
|
268
|
+
'INSERT INTO bundles ($insertCols) VALUES ($placeholders) '
|
|
269
|
+
'ON CONFLICT (id) DO UPDATE SET $updateSets',
|
|
270
|
+
{for (final c in columns) c.replaceAll('.', '_'): values[c]},
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
await tx.execute(
|
|
274
|
+
'DELETE FROM bundle_patches WHERE bundle_id = @p0',
|
|
275
|
+
{'p0': bundle.id},
|
|
276
|
+
);
|
|
277
|
+
final patchRows = bundleToPatchRows(bundle);
|
|
278
|
+
if (patchRows.isNotEmpty) {
|
|
279
|
+
for (final pr in patchRows) {
|
|
280
|
+
final patchCols = pr.keys.toList();
|
|
281
|
+
final patchInsert = patchCols.join(', ');
|
|
282
|
+
final patchPlaceholders =
|
|
283
|
+
patchCols.map((c) => '@${c.replaceAll('.', '_')}').join(', ');
|
|
284
|
+
await tx.execute(
|
|
285
|
+
'INSERT INTO bundle_patches ($patchInsert) '
|
|
286
|
+
'VALUES ($patchPlaceholders) '
|
|
287
|
+
'ON CONFLICT (id) DO UPDATE SET '
|
|
288
|
+
'${patchCols.where((c) => c != 'id').map((c) => '$c = EXCLUDED.$c').join(', ')}',
|
|
289
|
+
{for (final c in patchCols) c.replaceAll('.', '_'): pr[c]},
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
@override
|
|
299
|
+
Future<void> onUnmount() async {
|
|
300
|
+
await client.close();
|
|
301
|
+
}
|
|
302
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/// Faithful port of hot-updater `plugins/postgres/src/getUpdateInfo.ts`.
|
|
2
|
+
///
|
|
3
|
+
/// Calls the PL/pgSQL RPCs (`get_target_app_version_list`,
|
|
4
|
+
/// `get_update_info_by_app_version`, `get_update_info_by_fingerprint_hash`)
|
|
5
|
+
/// defined in `plugins/postgres/sql/*.sql`. The DB must have those functions
|
|
6
|
+
/// installed (see `supabase/migrations` parity work / Phase 5).
|
|
7
|
+
library;
|
|
8
|
+
|
|
9
|
+
import 'package:flutter_patcher_core/flutter_patcher_core.dart'
|
|
10
|
+
show
|
|
11
|
+
FingerprintGetBundlesArgs,
|
|
12
|
+
GetBundlesArgs,
|
|
13
|
+
AppVersionGetBundlesArgs,
|
|
14
|
+
UpdateInfo,
|
|
15
|
+
UpdateStatus,
|
|
16
|
+
UpdateStrategy;
|
|
17
|
+
import 'package:flutter_patcher_plugin_core/flutter_patcher_plugin_core.dart'
|
|
18
|
+
show filterCompatibleAppVersions;
|
|
19
|
+
|
|
20
|
+
import 'postgres_client.dart';
|
|
21
|
+
|
|
22
|
+
/// Row shape returned by the update-info RPCs.
|
|
23
|
+
class _UpdateInfoRow {
|
|
24
|
+
const _UpdateInfoRow({
|
|
25
|
+
required this.id,
|
|
26
|
+
required this.shouldForceUpdate,
|
|
27
|
+
required this.message,
|
|
28
|
+
required this.status,
|
|
29
|
+
required this.storageUri,
|
|
30
|
+
required this.fileHash,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
factory _UpdateInfoRow.fromJson(Map<String, dynamic> row) => _UpdateInfoRow(
|
|
34
|
+
id: row['id'] as String,
|
|
35
|
+
shouldForceUpdate: row['should_force_update'] as bool,
|
|
36
|
+
message: row['message'] as String?,
|
|
37
|
+
status: row['status'] as String,
|
|
38
|
+
storageUri: row['storage_uri'] as String?,
|
|
39
|
+
fileHash: row['file_hash'] as String?,
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
final String id;
|
|
43
|
+
final bool shouldForceUpdate;
|
|
44
|
+
final String? message;
|
|
45
|
+
final String status;
|
|
46
|
+
final String? storageUri;
|
|
47
|
+
final String? fileHash;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
UpdateInfo mapUpdateInfoRow(_UpdateInfoRow row) => UpdateInfo(
|
|
51
|
+
id: row.id,
|
|
52
|
+
shouldForceUpdate: row.shouldForceUpdate,
|
|
53
|
+
message: row.message,
|
|
54
|
+
status: row.status == 'ROLLBACK' ? UpdateStatus.rollback : UpdateStatus.update,
|
|
55
|
+
storageUri: row.storageUri,
|
|
56
|
+
fileHash: row.fileHash,
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
/// Resolve update info via the Postgres RPCs.
|
|
60
|
+
Future<UpdateInfo?> getUpdateInfo(
|
|
61
|
+
PostgresClientLike client,
|
|
62
|
+
GetBundlesArgs args,
|
|
63
|
+
) async {
|
|
64
|
+
if (args.updateStrategy == UpdateStrategy.appVersion) {
|
|
65
|
+
final appArgs = args as AppVersionGetBundlesArgs;
|
|
66
|
+
final targetRes = await client.execute(
|
|
67
|
+
'SELECT target_app_version '
|
|
68
|
+
'FROM get_target_app_version_list(@app_platform, @min_bundle_id)',
|
|
69
|
+
{
|
|
70
|
+
'app_platform': appArgs.platform.value,
|
|
71
|
+
'min_bundle_id': appArgs.minBundleId,
|
|
72
|
+
},
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
final targetAppVersionList = filterCompatibleAppVersions(
|
|
76
|
+
targetRes
|
|
77
|
+
.map((row) => row['target_app_version'] as String?)
|
|
78
|
+
.where((value) => value != null)
|
|
79
|
+
.cast<String>()
|
|
80
|
+
.toList(),
|
|
81
|
+
appArgs.appVersion,
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
final res = await client.execute(
|
|
85
|
+
'SELECT * FROM get_update_info_by_app_version('
|
|
86
|
+
'@app_platform, @app_version, @bundle_id, @min_bundle_id, '
|
|
87
|
+
'@target_channel, @target_app_version_list, @cohort)',
|
|
88
|
+
{
|
|
89
|
+
'app_platform': appArgs.platform.value,
|
|
90
|
+
'app_version': appArgs.appVersion,
|
|
91
|
+
'bundle_id': appArgs.bundleId,
|
|
92
|
+
'min_bundle_id': appArgs.minBundleId,
|
|
93
|
+
'target_channel': appArgs.channel,
|
|
94
|
+
'target_app_version_list': targetAppVersionList,
|
|
95
|
+
'cohort': appArgs.cohort,
|
|
96
|
+
},
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
if (res.isEmpty) return null;
|
|
100
|
+
return mapUpdateInfoRow(_UpdateInfoRow.fromJson(res.first));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
final fpArgs = args as FingerprintGetBundlesArgs;
|
|
104
|
+
final res = await client.execute(
|
|
105
|
+
'SELECT * FROM get_update_info_by_fingerprint_hash('
|
|
106
|
+
'@app_platform, @bundle_id, @min_bundle_id, '
|
|
107
|
+
'@target_channel, @target_fingerprint_hash, @cohort)',
|
|
108
|
+
{
|
|
109
|
+
'app_platform': fpArgs.platform.value,
|
|
110
|
+
'bundle_id': fpArgs.bundleId,
|
|
111
|
+
'min_bundle_id': fpArgs.minBundleId,
|
|
112
|
+
'target_channel': fpArgs.channel,
|
|
113
|
+
'target_fingerprint_hash': fpArgs.fingerprintHash,
|
|
114
|
+
'cohort': fpArgs.cohort,
|
|
115
|
+
},
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
if (res.isEmpty) return null;
|
|
119
|
+
return mapUpdateInfoRow(_UpdateInfoRow.fromJson(res.first));
|
|
120
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import 'dart:convert';
|
|
2
|
+
import 'dart:io';
|
|
3
|
+
import 'dart:typed_data';
|
|
4
|
+
|
|
5
|
+
import 'package:flutter_patcher_plugin_core/flutter_patcher_plugin_core.dart';
|
|
6
|
+
|
|
7
|
+
import 'postgres_client.dart';
|
|
8
|
+
import 'postgres_config.dart';
|
|
9
|
+
|
|
10
|
+
/// Configuration for the Postgres bytea storage plugin.
|
|
11
|
+
///
|
|
12
|
+
/// Reuses the same [PostgresConfig] connection as the database plugin. Blob
|
|
13
|
+
/// bytes are stored in a `flutter_patcher_storage(key text primary key, data
|
|
14
|
+
/// bytea, content_type text)` table. Because Postgres cannot serve bytes over
|
|
15
|
+
/// HTTP itself, [servingBaseUrl] may point at a thin proxy that reads from this
|
|
16
|
+
/// table so device update-checks can download over HTTP (the CLI/console supply
|
|
17
|
+
/// one; tests mount a local server).
|
|
18
|
+
class PostgresStorageConfig {
|
|
19
|
+
const PostgresStorageConfig({
|
|
20
|
+
required this.db,
|
|
21
|
+
this.basePath,
|
|
22
|
+
this.servingBaseUrl,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
final PostgresConfig db;
|
|
26
|
+
final String? basePath;
|
|
27
|
+
final String? servingBaseUrl;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/// Resolves the storage client for a config (test seam via [PostgresConfig.clientFactory]).
|
|
31
|
+
PostgresClientLike resolvePostgresStorageClient(PostgresStorageConfig config) =>
|
|
32
|
+
config.db.clientFactory?.call(config.db) ?? PostgresClient.connect(config.db);
|
|
33
|
+
|
|
34
|
+
String _storageUri(String key, String? servingBaseUrl) {
|
|
35
|
+
if (servingBaseUrl != null && servingBaseUrl.isNotEmpty) {
|
|
36
|
+
final base = servingBaseUrl.endsWith('/')
|
|
37
|
+
? servingBaseUrl.substring(0, servingBaseUrl.length - 1)
|
|
38
|
+
: servingBaseUrl;
|
|
39
|
+
return '$base/$key';
|
|
40
|
+
}
|
|
41
|
+
return 'postgres://$key';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
String _contentType(String key) {
|
|
45
|
+
if (key.endsWith('.json')) return 'application/json';
|
|
46
|
+
if (key.endsWith('.zip')) return 'application/zip';
|
|
47
|
+
return 'application/octet-stream';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
String _strip(String storageUri, String? servingBaseUrl) {
|
|
51
|
+
if (storageUri.startsWith('postgres://')) {
|
|
52
|
+
return storageUri.substring('postgres://'.length);
|
|
53
|
+
}
|
|
54
|
+
if (servingBaseUrl != null && storageUri.startsWith(servingBaseUrl)) {
|
|
55
|
+
final base =
|
|
56
|
+
servingBaseUrl.endsWith('/') ? servingBaseUrl : '$servingBaseUrl/';
|
|
57
|
+
return storageUri.startsWith(base)
|
|
58
|
+
? storageUri.substring(base.length)
|
|
59
|
+
: storageUri;
|
|
60
|
+
}
|
|
61
|
+
return storageUri;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
Uint8List _toBytes(Object? data) {
|
|
65
|
+
if (data is Uint8List) return data;
|
|
66
|
+
if (data is List) return Uint8List.fromList(data.cast<int>());
|
|
67
|
+
return Uint8List(0);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
NodeStorageProfile createPostgresNodeProfile(
|
|
71
|
+
PostgresClientLike client,
|
|
72
|
+
String? basePath,
|
|
73
|
+
String? servingBaseUrl,
|
|
74
|
+
) =>
|
|
75
|
+
_PostgresNodeProfile(client, basePath, servingBaseUrl);
|
|
76
|
+
|
|
77
|
+
class _PostgresNodeProfile implements NodeStorageProfile {
|
|
78
|
+
_PostgresNodeProfile(this._client, this._basePath, this._servingBaseUrl);
|
|
79
|
+
|
|
80
|
+
final PostgresClientLike _client;
|
|
81
|
+
final String? _basePath;
|
|
82
|
+
final String? _servingBaseUrl;
|
|
83
|
+
|
|
84
|
+
String _withBase(String key) =>
|
|
85
|
+
[_basePath, key].where((s) => s != null && s.isNotEmpty).join('/');
|
|
86
|
+
|
|
87
|
+
@override
|
|
88
|
+
Future<Map<String, String>> upload(String key, String filePath) async {
|
|
89
|
+
final fullKey = _withBase(key);
|
|
90
|
+
final bytes = await File(filePath).readAsBytes();
|
|
91
|
+
await _client.execute(
|
|
92
|
+
'INSERT INTO flutter_patcher_storage (key, data, content_type) '
|
|
93
|
+
'VALUES (@key, @data, @ct) '
|
|
94
|
+
'ON CONFLICT (key) DO UPDATE SET data = EXCLUDED.data, content_type = EXCLUDED.content_type',
|
|
95
|
+
{
|
|
96
|
+
'@key': fullKey,
|
|
97
|
+
'@data': bytes,
|
|
98
|
+
'@ct': _contentType(fullKey),
|
|
99
|
+
},
|
|
100
|
+
);
|
|
101
|
+
return {'storageUri': _storageUri(fullKey, _servingBaseUrl)};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
@override
|
|
105
|
+
Future<bool> exists(String storageUri) async {
|
|
106
|
+
final key = _strip(storageUri, _servingBaseUrl);
|
|
107
|
+
final rows = await _client.execute(
|
|
108
|
+
'SELECT 1 FROM flutter_patcher_storage WHERE key = @key',
|
|
109
|
+
{'@key': key},
|
|
110
|
+
);
|
|
111
|
+
return rows.isNotEmpty;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
@override
|
|
115
|
+
Future<void> delete(String storageUri) async {
|
|
116
|
+
await _client.execute(
|
|
117
|
+
'DELETE FROM flutter_patcher_storage WHERE key = @key',
|
|
118
|
+
{'@key': _strip(storageUri, _servingBaseUrl)},
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
@override
|
|
123
|
+
Future<void> downloadFile(String storageUri, String filePath) async {
|
|
124
|
+
final key = _strip(storageUri, _servingBaseUrl);
|
|
125
|
+
final rows = await _client.execute(
|
|
126
|
+
'SELECT data FROM flutter_patcher_storage WHERE key = @key',
|
|
127
|
+
{'@key': key},
|
|
128
|
+
);
|
|
129
|
+
if (rows.isEmpty) {
|
|
130
|
+
throw StateError('Object not found: $key');
|
|
131
|
+
}
|
|
132
|
+
await File(filePath).writeAsBytes(_toBytes(rows.first['data']));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
@override
|
|
136
|
+
Future<List<StorageObject>> listObjects([String? prefix]) async {
|
|
137
|
+
final fullPrefix = prefix == null ? null : _withBase(prefix);
|
|
138
|
+
final rows = await _client.execute(
|
|
139
|
+
'SELECT key, octet_length(data) AS size FROM flutter_patcher_storage '
|
|
140
|
+
'WHERE key LIKE @prefix',
|
|
141
|
+
{'@prefix': '${fullPrefix ?? ''}%'},
|
|
142
|
+
);
|
|
143
|
+
return rows.map((r) {
|
|
144
|
+
final key = r['key'] as String;
|
|
145
|
+
return StorageObject(
|
|
146
|
+
key: key,
|
|
147
|
+
storageUri: _storageUri(key, _servingBaseUrl),
|
|
148
|
+
size: (r['size'] as int?) ?? 0,
|
|
149
|
+
);
|
|
150
|
+
}).toList();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
@override
|
|
154
|
+
Future<void> deleteObjects(List<String> keys) async {
|
|
155
|
+
for (final k in keys) {
|
|
156
|
+
await _client.execute(
|
|
157
|
+
'DELETE FROM flutter_patcher_storage WHERE key = @key',
|
|
158
|
+
{'@key': _strip(k, _servingBaseUrl)},
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
RuntimeStorageProfile createPostgresRuntimeProfile(
|
|
165
|
+
PostgresClientLike client,
|
|
166
|
+
String? servingBaseUrl,
|
|
167
|
+
) =>
|
|
168
|
+
_PostgresRuntimeProfile(client, servingBaseUrl);
|
|
169
|
+
|
|
170
|
+
class _PostgresRuntimeProfile implements RuntimeStorageProfile {
|
|
171
|
+
_PostgresRuntimeProfile(this._client, this._servingBaseUrl);
|
|
172
|
+
|
|
173
|
+
final PostgresClientLike _client;
|
|
174
|
+
final String? _servingBaseUrl;
|
|
175
|
+
|
|
176
|
+
@override
|
|
177
|
+
Future<Map<String, String>> getDownloadUrl(String storageUri) async {
|
|
178
|
+
return {'fileUrl': _storageUri(_strip(storageUri, _servingBaseUrl), _servingBaseUrl)};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
@override
|
|
182
|
+
Future<String?> readText(String storageUri) async {
|
|
183
|
+
final key = _strip(storageUri, _servingBaseUrl);
|
|
184
|
+
final rows = await _client.execute(
|
|
185
|
+
'SELECT data FROM flutter_patcher_storage WHERE key = @key',
|
|
186
|
+
{'@key': key},
|
|
187
|
+
);
|
|
188
|
+
if (rows.isEmpty) return null;
|
|
189
|
+
return utf8.decode(_toBytes(rows.first['data']));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/// Postgres bytea storage plugin (name `postgresStorage`, protocol `postgres`).
|
|
194
|
+
StoragePlugin Function(PostgresStorageConfig) postgresStorage =
|
|
195
|
+
createUniversalStoragePlugin<PostgresStorageConfig>(
|
|
196
|
+
name: 'postgresStorage',
|
|
197
|
+
supportedProtocol: 'postgres',
|
|
198
|
+
factory: (cfg) {
|
|
199
|
+
final client = resolvePostgresStorageClient(cfg);
|
|
200
|
+
return (
|
|
201
|
+
node: createPostgresNodeProfile(client, cfg.basePath, cfg.servingBaseUrl),
|
|
202
|
+
runtime: createPostgresRuntimeProfile(client, cfg.servingBaseUrl),
|
|
203
|
+
);
|
|
204
|
+
},
|
|
205
|
+
);
|