@_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,90 @@
|
|
|
1
|
+
import 'package:postgres/postgres.dart';
|
|
2
|
+
|
|
3
|
+
import 'postgres_config.dart';
|
|
4
|
+
|
|
5
|
+
/// Minimal Postgres session contract used by the plugin.
|
|
6
|
+
///
|
|
7
|
+
/// Mirrors the surface the Dart port needs from `package:postgres`
|
|
8
|
+
/// (`Session`/`SessionExecutor`): parameterized `execute` returning column maps,
|
|
9
|
+
/// `runTx` for transactional `commitBundle`, and `close`.
|
|
10
|
+
///
|
|
11
|
+
/// A mock implementation backs the unit tests (see `test/mock_postgres_client.dart`).
|
|
12
|
+
abstract class PostgresClientLike {
|
|
13
|
+
/// Runs [sql] with named [parameters] (e.g. `@id`) and returns rows as
|
|
14
|
+
/// column-name → value maps.
|
|
15
|
+
Future<List<Map<String, dynamic>>> execute(
|
|
16
|
+
String sql,
|
|
17
|
+
Map<String, dynamic> parameters,
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
/// Runs [fn] inside a transaction. The argument is a client bound to the
|
|
21
|
+
/// transaction session (only `execute` is supported on it; nested
|
|
22
|
+
/// transactions are not supported).
|
|
23
|
+
Future<T> runTx<T>(Future<T> Function(PostgresClientLike tx) fn);
|
|
24
|
+
|
|
25
|
+
/// Closes the underlying connection.
|
|
26
|
+
Future<void> close();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/// Real [PostgresClientLike] backed by `package:postgres`.
|
|
30
|
+
///
|
|
31
|
+
/// The connection is opened lazily on first use (so `createDatabasePlugin`'s
|
|
32
|
+
/// synchronous `factory` can build this client without `await`).
|
|
33
|
+
class PostgresClient implements PostgresClientLike {
|
|
34
|
+
PostgresClient._(this._config, this._session, this._executor);
|
|
35
|
+
|
|
36
|
+
final PostgresConfig _config;
|
|
37
|
+
Session? _session;
|
|
38
|
+
SessionExecutor? _executor;
|
|
39
|
+
|
|
40
|
+
/// Creates a client that will open a real connection on first use.
|
|
41
|
+
factory PostgresClient.connect(PostgresConfig config) =>
|
|
42
|
+
PostgresClient._(config, null, null);
|
|
43
|
+
|
|
44
|
+
Future<Session> get _activeSession async {
|
|
45
|
+
if (_session != null) return _session!;
|
|
46
|
+
final endpoint = Endpoint(
|
|
47
|
+
host: _config.host,
|
|
48
|
+
port: _config.port,
|
|
49
|
+
database: _config.database,
|
|
50
|
+
username: _config.username,
|
|
51
|
+
password: _config.password,
|
|
52
|
+
);
|
|
53
|
+
final connection = await Connection.open(
|
|
54
|
+
endpoint,
|
|
55
|
+
settings: ConnectionSettings(sslMode: _config.sslMode),
|
|
56
|
+
);
|
|
57
|
+
_session = connection;
|
|
58
|
+
_executor = connection;
|
|
59
|
+
return connection;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@override
|
|
63
|
+
Future<List<Map<String, dynamic>>> execute(
|
|
64
|
+
String sql,
|
|
65
|
+
Map<String, dynamic> parameters,
|
|
66
|
+
) async {
|
|
67
|
+
final session = await _activeSession;
|
|
68
|
+
final result = await session.execute(sql, parameters: parameters);
|
|
69
|
+
return result.map((row) => row.toColumnMap()).toList();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@override
|
|
73
|
+
Future<T> runTx<T>(Future<T> Function(PostgresClientLike tx) fn) async {
|
|
74
|
+
final executor = _executor;
|
|
75
|
+
if (executor == null) {
|
|
76
|
+
throw UnsupportedError('Nested transactions are not supported');
|
|
77
|
+
}
|
|
78
|
+
return executor.runTx((tx) => fn(PostgresClient._(_config, tx, null)));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
@override
|
|
82
|
+
Future<void> close() async {
|
|
83
|
+
final executor = _executor;
|
|
84
|
+
if (executor != null) {
|
|
85
|
+
await executor.close();
|
|
86
|
+
_session = null;
|
|
87
|
+
_executor = null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import 'package:postgres/postgres.dart';
|
|
2
|
+
|
|
3
|
+
import 'postgres_client.dart';
|
|
4
|
+
|
|
5
|
+
/// Connection configuration for the Postgres backend.
|
|
6
|
+
///
|
|
7
|
+
/// Faithful port of hot-updater `PostgresConfig extends PoolConfig` (the `pg`
|
|
8
|
+
/// connection options). Mirrors the Supabase plugin's `clientFactory` seam:
|
|
9
|
+
/// when [clientFactory] is provided, the plugin uses the injected client instead
|
|
10
|
+
/// of opening a real connection — this is what makes the plugin unit-testable
|
|
11
|
+
/// without a live database.
|
|
12
|
+
class PostgresConfig {
|
|
13
|
+
const PostgresConfig({
|
|
14
|
+
required this.host,
|
|
15
|
+
this.port = 5432,
|
|
16
|
+
required this.database,
|
|
17
|
+
this.username,
|
|
18
|
+
this.password,
|
|
19
|
+
this.sslMode,
|
|
20
|
+
this.clientFactory,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
final String host;
|
|
24
|
+
final int port;
|
|
25
|
+
final String database;
|
|
26
|
+
final String? username;
|
|
27
|
+
final String? password;
|
|
28
|
+
final SslMode? sslMode;
|
|
29
|
+
|
|
30
|
+
/// Optional factory for injecting a [PostgresClientLike] (tests / custom pools).
|
|
31
|
+
final PostgresClientFactory? clientFactory;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/// Builds a [PostgresClientLike] from a [PostgresConfig].
|
|
35
|
+
typedef PostgresClientFactory = PostgresClientLike Function(PostgresConfig config);
|
|
@@ -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
|
+
}
|