@_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.
Files changed (177) hide show
  1. package/bin/flutter-patcher.js +37 -39
  2. package/dart-src/packages/cli-tools/.dart_tool/package_config.json +496 -0
  3. package/dart-src/packages/cli-tools/.dart_tool/package_graph.json +743 -0
  4. package/dart-src/packages/cli-tools/.gitkeep +0 -0
  5. package/dart-src/packages/cli-tools/bin/flutter_patcher.dart +9 -0
  6. package/dart-src/packages/cli-tools/lib/flutter_patcher_cli.dart +73 -0
  7. package/dart-src/packages/cli-tools/lib/src/backend.dart +162 -0
  8. package/dart-src/packages/cli-tools/lib/src/cli_base.dart +56 -0
  9. package/dart-src/packages/cli-tools/lib/src/commands/build.dart +99 -0
  10. package/dart-src/packages/cli-tools/lib/src/commands/bundle.dart +151 -0
  11. package/dart-src/packages/cli-tools/lib/src/commands/channel.dart +140 -0
  12. package/dart-src/packages/cli-tools/lib/src/commands/config_command.dart +113 -0
  13. package/dart-src/packages/cli-tools/lib/src/commands/console.dart +55 -0
  14. package/dart-src/packages/cli-tools/lib/src/commands/deploy.dart +95 -0
  15. package/dart-src/packages/cli-tools/lib/src/commands/doctor.dart +48 -0
  16. package/dart-src/packages/cli-tools/lib/src/commands/fingerprint.dart +29 -0
  17. package/dart-src/packages/cli-tools/lib/src/commands/init.dart +82 -0
  18. package/dart-src/packages/cli-tools/lib/src/commands/keys.dart +55 -0
  19. package/dart-src/packages/cli-tools/lib/src/commands/migrate.dart +132 -0
  20. package/dart-src/packages/cli-tools/lib/src/commands/rollback.dart +41 -0
  21. package/dart-src/packages/cli-tools/lib/src/config.dart +715 -0
  22. package/dart-src/packages/cli-tools/lib/src/operations.dart +206 -0
  23. package/dart-src/packages/cli-tools/lib/src/pack.dart +417 -0
  24. package/dart-src/packages/cli-tools/lib/src/sign.dart +60 -0
  25. package/dart-src/packages/cli-tools/lib/src/util.dart +86 -0
  26. package/dart-src/packages/cli-tools/migrations/cloudflare/0001_hot-updater_init.sql +16 -0
  27. package/dart-src/packages/cli-tools/migrations/cloudflare/0002_hot-updater_0.13.0.sql +8 -0
  28. package/dart-src/packages/cli-tools/migrations/cloudflare/0003_hot-updater_0.18.0.sql +38 -0
  29. package/dart-src/packages/cli-tools/migrations/cloudflare/0004_hot-updater_0.29.0.sql +11 -0
  30. package/dart-src/packages/cli-tools/migrations/cloudflare/0005_hot-updater_0.31.0.sql +24 -0
  31. package/dart-src/packages/cli-tools/migrations/cloudflare/bundles.sql +42 -0
  32. package/dart-src/packages/cli-tools/migrations/supabase/20250103114225_init.sql +228 -0
  33. package/dart-src/packages/cli-tools/migrations/supabase/20250314000000_hot-updater_0.13.0.sql +134 -0
  34. package/dart-src/packages/cli-tools/migrations/supabase/20250516000000_hot-updater_0.18.0.sql +194 -0
  35. package/dart-src/packages/cli-tools/migrations/supabase/20251014000000_hot-updater_0.21.0.sql +184 -0
  36. package/dart-src/packages/cli-tools/migrations/supabase/20260401000000_hot-updater_0.29.0.sql +503 -0
  37. package/dart-src/packages/cli-tools/migrations/supabase/20260414000000_hot-updater_0.30.0.sql +50 -0
  38. package/dart-src/packages/cli-tools/migrations/supabase/20260422000000_hot-updater_0.31.0.sql +20 -0
  39. package/dart-src/packages/cli-tools/migrations/supabase/20260520014100_hot-updater_rls.sql +48 -0
  40. package/dart-src/packages/cli-tools/pubspec.lock +638 -0
  41. package/dart-src/packages/cli-tools/pubspec.yaml +37 -0
  42. package/dart-src/packages/core/lib/flutter_patcher_core.dart +29 -0
  43. package/dart-src/packages/core/lib/src/app_update_info.dart +111 -0
  44. package/dart-src/packages/core/lib/src/bundle.dart +191 -0
  45. package/dart-src/packages/core/lib/src/bundle_artifacts.dart +39 -0
  46. package/dart-src/packages/core/lib/src/bundle_patch_artifact.dart +47 -0
  47. package/dart-src/packages/core/lib/src/changed_asset.dart +77 -0
  48. package/dart-src/packages/core/lib/src/get_bundles_args.dart +78 -0
  49. package/dart-src/packages/core/lib/src/metadata.dart +31 -0
  50. package/dart-src/packages/core/lib/src/patch_info.dart +284 -0
  51. package/dart-src/packages/core/lib/src/platform.dart +15 -0
  52. package/dart-src/packages/core/lib/src/rollout.dart +209 -0
  53. package/dart-src/packages/core/lib/src/semver.dart +70 -0
  54. package/dart-src/packages/core/lib/src/semver_range.dart +358 -0
  55. package/dart-src/packages/core/lib/src/semver_version.dart +94 -0
  56. package/dart-src/packages/core/lib/src/status.dart +24 -0
  57. package/dart-src/packages/core/lib/src/strategy.dart +10 -0
  58. package/dart-src/packages/core/lib/src/update_bundle_params.dart +30 -0
  59. package/dart-src/packages/core/lib/src/uuid.dart +45 -0
  60. package/dart-src/packages/core/pubspec.lock +381 -0
  61. package/dart-src/packages/core/pubspec.yaml +9 -0
  62. package/dart-src/packages/core/tool/dbg.dart +6 -0
  63. package/dart-src/plugins/aws/.gitkeep +0 -0
  64. package/dart-src/plugins/aws/lib/flutter_patcher_aws.dart +12 -0
  65. package/dart-src/plugins/aws/lib/src/aws_cloudfront_client.dart +207 -0
  66. package/dart-src/plugins/aws/lib/src/aws_cloudfront_signer.dart +130 -0
  67. package/dart-src/plugins/aws/lib/src/aws_config.dart +59 -0
  68. package/dart-src/plugins/aws/lib/src/aws_database.dart +198 -0
  69. package/dart-src/plugins/aws/lib/src/aws_lambda_edge_storage.dart +72 -0
  70. package/dart-src/plugins/aws/lib/src/aws_s3_client.dart +312 -0
  71. package/dart-src/plugins/aws/lib/src/aws_ssm_client.dart +125 -0
  72. package/dart-src/plugins/aws/lib/src/aws_storage.dart +20 -0
  73. package/dart-src/plugins/aws/lib/src/aws_storage_profile.dart +123 -0
  74. package/dart-src/plugins/aws/lib/src/with_cloudfront_signed_url.dart +136 -0
  75. package/dart-src/plugins/aws/pubspec.lock +435 -0
  76. package/dart-src/plugins/aws/pubspec.yaml +22 -0
  77. package/dart-src/plugins/cloudflare/.gitkeep +0 -0
  78. package/dart-src/plugins/cloudflare/lib/flutter_patcher_cloudflare.dart +18 -0
  79. package/dart-src/plugins/cloudflare/lib/src/cloudflare_worker_database.dart +29 -0
  80. package/dart-src/plugins/cloudflare/lib/src/d1_build_where.dart +93 -0
  81. package/dart-src/plugins/cloudflare/lib/src/d1_bundle_mapper.dart +131 -0
  82. package/dart-src/plugins/cloudflare/lib/src/d1_client.dart +66 -0
  83. package/dart-src/plugins/cloudflare/lib/src/d1_config.dart +30 -0
  84. package/dart-src/plugins/cloudflare/lib/src/d1_database.dart +16 -0
  85. package/dart-src/plugins/cloudflare/lib/src/d1_database_plugin.dart +314 -0
  86. package/dart-src/plugins/cloudflare/lib/src/r2_config.dart +50 -0
  87. package/dart-src/plugins/cloudflare/lib/src/r2_s3_client.dart +287 -0
  88. package/dart-src/plugins/cloudflare/lib/src/r2_storage.dart +23 -0
  89. package/dart-src/plugins/cloudflare/lib/src/r2_storage_profile.dart +110 -0
  90. package/dart-src/plugins/cloudflare/migrations/0001_hot-updater_init.sql +16 -0
  91. package/dart-src/plugins/cloudflare/migrations/0002_hot-updater_0.13.0.sql +8 -0
  92. package/dart-src/plugins/cloudflare/migrations/0003_hot-updater_0.18.0.sql +38 -0
  93. package/dart-src/plugins/cloudflare/migrations/0004_hot-updater_0.29.0.sql +11 -0
  94. package/dart-src/plugins/cloudflare/migrations/0005_hot-updater_0.31.0.sql +24 -0
  95. package/dart-src/plugins/cloudflare/pubspec.lock +411 -0
  96. package/dart-src/plugins/cloudflare/pubspec.yaml +20 -0
  97. package/dart-src/plugins/cloudflare/sql/bundles.sql +42 -0
  98. package/dart-src/plugins/plugin-core/lib/flutter_patcher_plugin_core.dart +42 -0
  99. package/dart-src/plugins/plugin-core/lib/src/asset_storage_layout.dart +94 -0
  100. package/dart-src/plugins/plugin-core/lib/src/bundle_storage_layout.dart +44 -0
  101. package/dart-src/plugins/plugin-core/lib/src/bundle_unit_of_work.dart +259 -0
  102. package/dart-src/plugins/plugin-core/lib/src/bundle_unit_of_work_store.dart +20 -0
  103. package/dart-src/plugins/plugin-core/lib/src/calculate_pagination.dart +29 -0
  104. package/dart-src/plugins/plugin-core/lib/src/compression_format.dart +63 -0
  105. package/dart-src/plugins/plugin-core/lib/src/content_addressed_assets.dart +22 -0
  106. package/dart-src/plugins/plugin-core/lib/src/create_blob_database_plugin.dart +853 -0
  107. package/dart-src/plugins/plugin-core/lib/src/create_database_plugin.dart +525 -0
  108. package/dart-src/plugins/plugin-core/lib/src/create_storage_key_builder.dart +15 -0
  109. package/dart-src/plugins/plugin-core/lib/src/create_storage_plugin.dart +130 -0
  110. package/dart-src/plugins/plugin-core/lib/src/filter_compatible_app_versions.dart +18 -0
  111. package/dart-src/plugins/plugin-core/lib/src/generate_min_bundle_id.dart +19 -0
  112. package/dart-src/plugins/plugin-core/lib/src/get_update_info.dart +241 -0
  113. package/dart-src/plugins/plugin-core/lib/src/is_object.dart +5 -0
  114. package/dart-src/plugins/plugin-core/lib/src/legacy_asset_storage_layout.dart +9 -0
  115. package/dart-src/plugins/plugin-core/lib/src/paginate_bundles.dart +112 -0
  116. package/dart-src/plugins/plugin-core/lib/src/parse_storage_uri.dart +44 -0
  117. package/dart-src/plugins/plugin-core/lib/src/query_bundles.dart +70 -0
  118. package/dart-src/plugins/plugin-core/lib/src/request_update_bundle_state.dart +62 -0
  119. package/dart-src/plugins/plugin-core/lib/src/resolve_update_info_from_bundles.dart +43 -0
  120. package/dart-src/plugins/plugin-core/lib/src/storage_profile.dart +27 -0
  121. package/dart-src/plugins/plugin-core/lib/src/types.dart +454 -0
  122. package/dart-src/plugins/plugin-core/lib/src/uuidv7.dart +41 -0
  123. package/dart-src/plugins/plugin-core/pubspec.lock +396 -0
  124. package/dart-src/plugins/plugin-core/pubspec.yaml +15 -0
  125. package/dart-src/plugins/postgres/.gitkeep +0 -0
  126. package/dart-src/plugins/postgres/lib/flutter_patcher_postgres.dart +17 -0
  127. package/dart-src/plugins/postgres/lib/src/postgres_bundle_mapper.dart +130 -0
  128. package/dart-src/plugins/postgres/lib/src/postgres_client.dart +90 -0
  129. package/dart-src/plugins/postgres/lib/src/postgres_config.dart +35 -0
  130. package/dart-src/plugins/postgres/lib/src/postgres_database.dart +302 -0
  131. package/dart-src/plugins/postgres/lib/src/postgres_get_update_info.dart +120 -0
  132. package/dart-src/plugins/postgres/lib/src/postgres_storage.dart +205 -0
  133. package/dart-src/plugins/postgres/lib/src/postgres_types.dart +132 -0
  134. package/dart-src/plugins/postgres/pubspec.lock +427 -0
  135. package/dart-src/plugins/postgres/pubspec.yaml +18 -0
  136. package/dart-src/plugins/postgres/sql/bundles.sql +45 -0
  137. package/dart-src/plugins/postgres/sql/get_target_app_version_list.sql +21 -0
  138. package/dart-src/plugins/postgres/sql/get_update_info.spec.ts +171 -0
  139. package/dart-src/plugins/postgres/sql/get_update_info_by_app_version.sql +126 -0
  140. package/dart-src/plugins/postgres/sql/get_update_info_by_fingerprint_hash.sql +125 -0
  141. package/dart-src/plugins/postgres/sql/hash_user_id.sql +30 -0
  142. package/dart-src/plugins/postgres/sql/is_device_eligible.sql +237 -0
  143. package/dart-src/plugins/postgres/sql/prepareSql.ts +11 -0
  144. package/dart-src/plugins/standalone/.gitkeep +0 -0
  145. package/dart-src/plugins/standalone/lib/flutter_patcher_standalone.dart +12 -0
  146. package/dart-src/plugins/standalone/lib/src/standalone_config.dart +107 -0
  147. package/dart-src/plugins/standalone/lib/src/standalone_database.dart +233 -0
  148. package/dart-src/plugins/standalone/lib/src/standalone_storage.dart +175 -0
  149. package/dart-src/plugins/standalone/pubspec.lock +411 -0
  150. package/dart-src/plugins/standalone/pubspec.yaml +18 -0
  151. package/dart-src/plugins/supabase/lib/edge.dart +10 -0
  152. package/dart-src/plugins/supabase/lib/flutter_patcher_supabase.dart +21 -0
  153. package/dart-src/plugins/supabase/lib/src/supabase_bundle_mapper.dart +129 -0
  154. package/dart-src/plugins/supabase/lib/src/supabase_client_adapter.dart +154 -0
  155. package/dart-src/plugins/supabase/lib/src/supabase_client_http.dart +516 -0
  156. package/dart-src/plugins/supabase/lib/src/supabase_config.dart +34 -0
  157. package/dart-src/plugins/supabase/lib/src/supabase_database.dart +422 -0
  158. package/dart-src/plugins/supabase/lib/src/supabase_edge_function_database.dart +32 -0
  159. package/dart-src/plugins/supabase/lib/src/supabase_edge_function_storage.dart +99 -0
  160. package/dart-src/plugins/supabase/lib/src/supabase_signed_url_batcher.dart +145 -0
  161. package/dart-src/plugins/supabase/lib/src/supabase_storage.dart +298 -0
  162. package/dart-src/plugins/supabase/lib/src/types.dart +130 -0
  163. package/dart-src/plugins/supabase/pubspec.lock +515 -0
  164. package/dart-src/plugins/supabase/pubspec.yaml +19 -0
  165. package/dart-src/plugins/supabase/supabase/edge-functions/index.ts +44 -0
  166. package/dart-src/plugins/supabase/supabase/edge-functions/runtime.docker.integration.spec.ts +968 -0
  167. package/dart-src/plugins/supabase/supabase/migrations/20250103114225_init.sql +228 -0
  168. package/dart-src/plugins/supabase/supabase/migrations/20250314000000_hot-updater_0.13.0.sql +134 -0
  169. package/dart-src/plugins/supabase/supabase/migrations/20250516000000_hot-updater_0.18.0.sql +194 -0
  170. package/dart-src/plugins/supabase/supabase/migrations/20251014000000_hot-updater_0.21.0.sql +184 -0
  171. package/dart-src/plugins/supabase/supabase/migrations/20260401000000_hot-updater_0.29.0.sql +503 -0
  172. package/dart-src/plugins/supabase/supabase/migrations/20260414000000_hot-updater_0.30.0.sql +50 -0
  173. package/dart-src/plugins/supabase/supabase/migrations/20260422000000_hot-updater_0.31.0.sql +20 -0
  174. package/dart-src/plugins/supabase/supabase/migrations/20260520014100_hot-updater_rls.sql +48 -0
  175. package/package.json +2 -1
  176. package/scripts/postinstall.js +32 -23
  177. /package/bin/{flutter-patcher-linux → flutter-patcher-linux-x64} +0 -0
@@ -0,0 +1,154 @@
1
+ /// Abstraction over the Supabase client used by the database + storage plugins.
2
+ ///
3
+ /// Mirrors the TS `vi.mock("@supabase/supabase-js")` test seam: in production
4
+ /// this wraps the real `supabase` Dart package; in tests a mock implementation
5
+ /// is supplied via the plugin config's `clientFactory`.
6
+ library;
7
+
8
+ /// Minimal PostgREST query-builder surface used by the database plugin.
9
+ abstract class SupabaseQueryBuilderLike {
10
+ SupabaseFilterBuilderLike select(
11
+ String columns, {
12
+ bool? count,
13
+ bool? head,
14
+ });
15
+
16
+ SupabaseFilterBuilderLike delete();
17
+
18
+ Future<dynamic> upsert(
19
+ dynamic values, {
20
+ String? onConflict,
21
+ });
22
+ }
23
+
24
+ /// Minimal PostgREST filter-builder surface.
25
+ abstract class SupabaseFilterBuilderLike {
26
+ SupabaseFilterBuilderLike eq(String column, Object? value);
27
+ SupabaseFilterBuilderLike gt(String column, Object? value);
28
+ SupabaseFilterBuilderLike gte(String column, Object? value);
29
+ SupabaseFilterBuilderLike lt(String column, Object? value);
30
+ SupabaseFilterBuilderLike lte(String column, Object? value);
31
+ SupabaseFilterBuilderLike in_(String column, List<Object?> values);
32
+ SupabaseFilterBuilderLike isFilter(String column, Object? value);
33
+ SupabaseFilterBuilderLike not(String column, String operator, Object? value);
34
+ SupabaseFilterBuilderLike order(String column, {bool? ascending});
35
+ SupabaseFilterBuilderLike limit(int value);
36
+ SupabaseFilterBuilderLike range(int from, int to);
37
+ SupabaseFilterBuilderLike single();
38
+ Future<SupabaseResponseLike> execute();
39
+ }
40
+
41
+ /// The response shape returned by a query/filter builder.
42
+ abstract class SupabaseResponseLike {
43
+ Object? get data;
44
+ Object? get error;
45
+ int? get count;
46
+ }
47
+
48
+ /// Storage bucket surface used by the storage plugin.
49
+ abstract class SupabaseStorageBucketLike {
50
+ Future<SupabaseSignedUrlResult> createSignedUrl(
51
+ String path,
52
+ int expiresIn,
53
+ );
54
+
55
+ Future<SupabaseSignedUrlListResult> createSignedUrls(
56
+ List<String> paths,
57
+ int expiresIn,
58
+ );
59
+
60
+ Future<SupabaseUploadResult> upload(
61
+ String path,
62
+ List<int> fileBytes, {
63
+ String? contentType,
64
+ String? cacheControl,
65
+ });
66
+
67
+ Future<SupabaseRemoveResult> remove(List<String> paths);
68
+
69
+ Future<SupabaseExistsResult> exists(String path);
70
+
71
+ Future<SupabaseDownloadResult> download(String path);
72
+
73
+ /// List objects under an optional prefix (used by storage GC).
74
+ Future<SupabaseListResult> list([String? prefix]);
75
+ }
76
+
77
+ /// A list-objects result entry.
78
+ abstract class SupabaseStorageObject {
79
+ String get key;
80
+ int get size;
81
+ String? get lastModifiedAt;
82
+ }
83
+
84
+ /// A list-objects result.
85
+ abstract class SupabaseListResult {
86
+ List<SupabaseStorageObject>? get data;
87
+ Object? get error;
88
+ }
89
+
90
+ /// A signed URL result.
91
+ abstract class SupabaseSignedUrlResult {
92
+ String? get signedUrl;
93
+ Object? get error;
94
+ }
95
+
96
+ /// A batch signed URL result.
97
+ abstract class SupabaseSignedUrlListResult {
98
+ List<SupabaseSignedUrlResult>? get data;
99
+ Object? get error;
100
+ }
101
+
102
+ /// An upload result.
103
+ abstract class SupabaseUploadResult {
104
+ Object? get data;
105
+ Object? get error;
106
+ }
107
+
108
+ /// A remove result.
109
+ abstract class SupabaseRemoveResult {
110
+ Object? get data;
111
+ Object? get error;
112
+ String? get message;
113
+ }
114
+
115
+ /// An exists result.
116
+ abstract class SupabaseExistsResult {
117
+ bool? get data;
118
+ Object? get error;
119
+ String? get message;
120
+ }
121
+
122
+ /// A download result.
123
+ abstract class SupabaseDownloadResult {
124
+ List<int>? get data;
125
+ Object? get error;
126
+ String? get message;
127
+ }
128
+
129
+ /// The storage client surface used by the storage plugin.
130
+ abstract class SupabaseStorageClientLike {
131
+ SupabaseStorageBucketLike from(String bucket);
132
+ }
133
+
134
+ /// The top-level Supabase client surface used by both plugins.
135
+ abstract class SupabaseClientLike {
136
+ SupabaseQueryBuilderLike from(String table);
137
+ SupabaseStorageClientLike get storage;
138
+ Future<SupabaseRpcResponse> rpc(
139
+ String functionName, {
140
+ Map<String, dynamic>? params,
141
+ });
142
+ }
143
+
144
+ /// An RPC response.
145
+ abstract class SupabaseRpcResponse {
146
+ Object? get data;
147
+ Object? get error;
148
+ }
149
+
150
+ /// Factory that builds a [SupabaseClientLike] from credentials.
151
+ typedef SupabaseClientFactory = SupabaseClientLike Function(
152
+ String url,
153
+ String key,
154
+ );
@@ -0,0 +1,516 @@
1
+ /// Production adapter implementing [SupabaseClientLike] over the Supabase
2
+ /// REST (PostgREST) API using plain `http` calls.
3
+ ///
4
+ /// This is the Dart equivalent of `@supabase/supabase-js`'s `createClient`:
5
+ /// it talks to the same REST endpoints the JS client would, so the plugin
6
+ /// logic stays identical regardless of transport.
7
+ library;
8
+
9
+ import 'dart:convert';
10
+
11
+ import 'package:http/http.dart' as http;
12
+
13
+ import 'supabase_client_adapter.dart';
14
+
15
+ class _HttpResponse implements SupabaseResponseLike {
16
+ @override
17
+ final Object? data;
18
+ @override
19
+ final Object? error;
20
+ @override
21
+ final int? count;
22
+
23
+ const _HttpResponse(this.data, this.error, this.count);
24
+ }
25
+
26
+ class _FilterBuilder implements SupabaseFilterBuilderLike {
27
+ final String _baseUrl;
28
+ final String _table;
29
+ final Map<String, String> _query;
30
+ final Map<String, String> _headers;
31
+ final String _method;
32
+ final Object? _body;
33
+
34
+ _FilterBuilder({
35
+ required String baseUrl,
36
+ required String table,
37
+ required Map<String, String> headers,
38
+ Map<String, String>? query,
39
+ String method = 'GET',
40
+ Object? body,
41
+ }) : _baseUrl = baseUrl,
42
+ _table = table,
43
+ _headers = headers,
44
+ _query = query ?? {},
45
+ _method = method,
46
+ _body = body;
47
+
48
+ @override
49
+ SupabaseFilterBuilderLike eq(String column, Object? value) =>
50
+ _addFilter(column, 'eq', value);
51
+
52
+ @override
53
+ SupabaseFilterBuilderLike gt(String column, Object? value) =>
54
+ _addFilter(column, 'gt', value);
55
+
56
+ @override
57
+ SupabaseFilterBuilderLike gte(String column, Object? value) =>
58
+ _addFilter(column, 'gte', value);
59
+
60
+ @override
61
+ SupabaseFilterBuilderLike lt(String column, Object? value) =>
62
+ _addFilter(column, 'lt', value);
63
+
64
+ @override
65
+ SupabaseFilterBuilderLike lte(String column, Object? value) =>
66
+ _addFilter(column, 'lte', value);
67
+
68
+ @override
69
+ SupabaseFilterBuilderLike in_(String column, List<Object?> values) {
70
+ _query[column] = 'in.(${values.join(',')})';
71
+ return this;
72
+ }
73
+
74
+ @override
75
+ SupabaseFilterBuilderLike isFilter(String column, Object? value) {
76
+ _query[column] = 'is.${value ?? 'null'}';
77
+ return this;
78
+ }
79
+
80
+ @override
81
+ SupabaseFilterBuilderLike not(
82
+ String column,
83
+ String operator,
84
+ Object? value,
85
+ ) {
86
+ _query[column] = 'not.$operator.${_enc(value)}';
87
+ return this;
88
+ }
89
+
90
+ @override
91
+ SupabaseFilterBuilderLike order(String column, {bool? ascending}) {
92
+ _query['order'] = ascending == false ? '$column.desc' : '$column.asc';
93
+ return this;
94
+ }
95
+
96
+ @override
97
+ SupabaseFilterBuilderLike limit(int value) {
98
+ _query['limit'] = value.toString();
99
+ return this;
100
+ }
101
+
102
+ @override
103
+ SupabaseFilterBuilderLike range(int from, int to) {
104
+ _headers['Range'] = '$from-$to';
105
+ _headers['Range-Unit'] = 'items';
106
+ return this;
107
+ }
108
+
109
+ @override
110
+ SupabaseFilterBuilderLike single() {
111
+ _headers['Accept'] = 'application/vnd.pgrst.object+json';
112
+ return this;
113
+ }
114
+
115
+ SupabaseFilterBuilderLike _addFilter(
116
+ String column,
117
+ String op,
118
+ Object? value,
119
+ ) {
120
+ _query[column] = '$op.${_enc(value)}';
121
+ return this;
122
+ }
123
+
124
+ String _enc(Object? value) => value == null ? 'null' : value.toString();
125
+
126
+ @override
127
+ Future<SupabaseResponseLike> execute() async {
128
+ final uri = Uri.parse('$_baseUrl/rest/v1/$_table')
129
+ .replace(queryParameters: _query.isEmpty ? null : _query);
130
+ final headers = <String, String>{
131
+ ..._headers,
132
+ if (_method != 'GET' && _body != null)
133
+ 'Content-Type': 'application/json',
134
+ };
135
+
136
+ late http.Response res;
137
+ switch (_method) {
138
+ case 'DELETE':
139
+ res = await http.delete(uri, headers: headers);
140
+ break;
141
+ case 'POST':
142
+ res = await http.post(
143
+ uri,
144
+ headers: headers,
145
+ body: _body == null ? null : jsonEncode(_body),
146
+ );
147
+ break;
148
+ case 'PATCH':
149
+ res = await http.patch(
150
+ uri,
151
+ headers: headers,
152
+ body: _body == null ? null : jsonEncode(_body),
153
+ );
154
+ break;
155
+ default:
156
+ res = await http.get(uri, headers: headers);
157
+ }
158
+
159
+ if (res.statusCode >= 400) {
160
+ return _HttpResponse(null, jsonDecode(res.body), null);
161
+ }
162
+
163
+ final body = res.body.isEmpty ? null : jsonDecode(res.body);
164
+ final countHeader = res.headers['content-range'];
165
+ int? count;
166
+ if (countHeader != null) {
167
+ final slash = countHeader.indexOf('/');
168
+ if (slash >= 0 && slash + 1 < countHeader.length) {
169
+ count = int.tryParse(countHeader.substring(slash + 1));
170
+ }
171
+ }
172
+ return _HttpResponse(body, null, count);
173
+ }
174
+ }
175
+
176
+ class _QueryBuilder implements SupabaseQueryBuilderLike {
177
+ final String _baseUrl;
178
+ final String _table;
179
+ final Map<String, String> _headers;
180
+
181
+ _QueryBuilder(this._baseUrl, this._table, this._headers);
182
+
183
+ @override
184
+ SupabaseFilterBuilderLike select(
185
+ String columns, {
186
+ bool? count,
187
+ bool? head,
188
+ }) {
189
+ final headers = <String, String>{..._headers};
190
+ if (count == true) headers['Prefer'] = 'count=exact';
191
+ if (head == true) headers['Range'] = '0-0';
192
+ return _FilterBuilder(
193
+ baseUrl: _baseUrl,
194
+ table: _table,
195
+ headers: headers,
196
+ query: {'select': columns},
197
+ );
198
+ }
199
+
200
+ @override
201
+ SupabaseFilterBuilderLike delete() => _FilterBuilder(
202
+ baseUrl: _baseUrl,
203
+ table: _table,
204
+ headers: _headers,
205
+ method: 'DELETE',
206
+ );
207
+
208
+ @override
209
+ Future<dynamic> upsert(dynamic values, {String? onConflict}) async {
210
+ final uri = Uri.parse('$_baseUrl/rest/v1/$_table').replace(
211
+ queryParameters: onConflict != null
212
+ ? {'on_conflict': onConflict}
213
+ : null,
214
+ );
215
+ final headers = <String, String>{
216
+ ..._headers,
217
+ 'Content-Type': 'application/json',
218
+ 'Prefer': 'resolution=merge-duplicates',
219
+ };
220
+ final res = await http.post(
221
+ uri,
222
+ headers: headers,
223
+ body: jsonEncode(values),
224
+ );
225
+ if (res.statusCode >= 400) {
226
+ return _HttpResponse(null, jsonDecode(res.body), null);
227
+ }
228
+ return _HttpResponse(
229
+ res.body.isEmpty ? null : jsonDecode(res.body),
230
+ null,
231
+ null,
232
+ );
233
+ }
234
+ }
235
+
236
+ class _StorageBucket implements SupabaseStorageBucketLike {
237
+ final String _baseUrl;
238
+ final String _bucket;
239
+ final Map<String, String> _headers;
240
+
241
+ _StorageBucket(this._baseUrl, this._bucket, this._headers);
242
+
243
+ @override
244
+ Future<SupabaseSignedUrlResult> createSignedUrl(
245
+ String path,
246
+ int expiresIn,
247
+ ) async {
248
+ final uri = Uri.parse(
249
+ '$_baseUrl/storage/v1/object/sign/$_bucket/$path',
250
+ ).replace(queryParameters: {'expiresIn': expiresIn.toString()});
251
+ final res = await http.post(uri, headers: _headers);
252
+ if (res.statusCode >= 400) {
253
+ return _MockSignedUrlResult(null, jsonDecode(res.body));
254
+ }
255
+ final body = jsonDecode(res.body) as Map<String, dynamic>;
256
+ return _MockSignedUrlResult(
257
+ '$_baseUrl/storage/v1/object/sign/$_bucket/$path?...${body['signedURL']}',
258
+ null);
259
+ }
260
+
261
+ @override
262
+ Future<SupabaseSignedUrlListResult> createSignedUrls(
263
+ List<String> paths,
264
+ int expiresIn,
265
+ ) async {
266
+ final uri = Uri.parse('$_baseUrl/storage/v1/object/sign/$_bucket');
267
+ final res = await http.post(
268
+ uri,
269
+ headers: {..._headers, 'Content-Type': 'application/json'},
270
+ body: jsonEncode({
271
+ 'expiresIn': expiresIn,
272
+ 'paths': paths,
273
+ }),
274
+ );
275
+ if (res.statusCode >= 400) {
276
+ return _MockSignedUrlListResult(null, jsonDecode(res.body));
277
+ }
278
+ final body = jsonDecode(res.body) as List<dynamic>;
279
+ return _MockSignedUrlListResult(
280
+ body
281
+ .map((e) => _MockSignedUrlResult(
282
+ e['signedURL'] as String?,
283
+ e['error'] as String?,
284
+ ))
285
+ .toList(),
286
+ null,
287
+ );
288
+ }
289
+
290
+ @override
291
+ Future<SupabaseUploadResult> upload(
292
+ String path,
293
+ List<int> fileBytes, {
294
+ String? contentType,
295
+ String? cacheControl,
296
+ }) async {
297
+ final uri = Uri.parse('$_baseUrl/storage/v1/object/$_bucket/$path');
298
+ final res = await http.post(
299
+ uri,
300
+ headers: {
301
+ ..._headers,
302
+ 'Content-Type': contentType ?? 'application/octet-stream',
303
+ if (cacheControl != null) 'Cache-Control': cacheControl,
304
+ },
305
+ body: fileBytes,
306
+ );
307
+ if (res.statusCode >= 400) {
308
+ return _MockUploadResult(null, jsonDecode(res.body));
309
+ }
310
+ return _MockUploadResult(jsonDecode(res.body), null);
311
+ }
312
+
313
+ @override
314
+ Future<SupabaseRemoveResult> remove(List<String> paths) async {
315
+ final uri = Uri.parse('$_baseUrl/storage/v1/object/$_bucket');
316
+ final res = await http.delete(
317
+ uri,
318
+ headers: {..._headers, 'Content-Type': 'application/json'},
319
+ body: jsonEncode({'prefixes': paths}),
320
+ );
321
+ if (res.statusCode >= 400) {
322
+ final body = jsonDecode(res.body) as Map<String, dynamic>;
323
+ return _MockRemoveResult(null, body['message'] as String?);
324
+ }
325
+ return _MockRemoveResult(jsonDecode(res.body), null);
326
+ }
327
+
328
+ @override
329
+ Future<SupabaseExistsResult> exists(String path) async {
330
+ final uri = Uri.parse(
331
+ '$_baseUrl/storage/v1/object/$_bucket/$path',
332
+ ).replace(queryParameters: {'_method': 'HEAD'});
333
+ final res = await http.get(uri, headers: _headers);
334
+ if (res.statusCode == 404) {
335
+ return _MockExistsResult(false, null);
336
+ }
337
+ if (res.statusCode >= 400) {
338
+ final body = jsonDecode(res.body) as Map<String, dynamic>;
339
+ return _MockExistsResult(null, body['message'] as String?);
340
+ }
341
+ return _MockExistsResult(true, null);
342
+ }
343
+
344
+ @override
345
+ Future<SupabaseDownloadResult> download(String path) async {
346
+ final uri = Uri.parse('$_baseUrl/storage/v1/object/$_bucket/$path');
347
+ final res = await http.get(uri, headers: _headers);
348
+ if (res.statusCode >= 400) {
349
+ final body = jsonDecode(res.body) as Map<String, dynamic>;
350
+ return _MockDownloadResult(null, body['message'] as String?);
351
+ }
352
+ return _MockDownloadResult(res.bodyBytes, null);
353
+ }
354
+
355
+ @override
356
+ Future<SupabaseListResult> list([String? prefix]) async {
357
+ final uri = Uri.parse('$_baseUrl/storage/v1/object/list/$_bucket');
358
+ final res = await http.post(
359
+ uri,
360
+ headers: {..._headers, 'Content-Type': 'application/json'},
361
+ body: jsonEncode({'prefix': prefix ?? ''}),
362
+ );
363
+ if (res.statusCode >= 400) {
364
+ final body = jsonDecode(res.body) as Map<String, dynamic>;
365
+ return _MockListResult(null, body['message'] as String?);
366
+ }
367
+ final body = (res.body.isEmpty ? [] : jsonDecode(res.body)) as List<dynamic>;
368
+ return _MockListResult(
369
+ body
370
+ .map((e) => _MockStorageObject(e as Map<String, dynamic>))
371
+ .toList(),
372
+ null,
373
+ );
374
+ }
375
+ }
376
+
377
+ class _MockStorageObject implements SupabaseStorageObject {
378
+ _MockStorageObject(this._raw);
379
+ final Map<String, dynamic> _raw;
380
+
381
+ @override
382
+ String get key => _raw['name'] as String;
383
+
384
+ @override
385
+ int get size {
386
+ final meta = _raw['metadata'] as Map?;
387
+ final s = meta?['size'];
388
+ return s is num ? s.toInt() : 0;
389
+ }
390
+
391
+ @override
392
+ String? get lastModifiedAt => _raw['updated_at'] as String?;
393
+ }
394
+
395
+ class _MockListResult implements SupabaseListResult {
396
+ @override
397
+ final List<SupabaseStorageObject>? data;
398
+ @override
399
+ final Object? error;
400
+ const _MockListResult(this.data, this.error);
401
+ }
402
+
403
+ class _MockSignedUrlResult implements SupabaseSignedUrlResult {
404
+ @override
405
+ final String? signedUrl;
406
+ @override
407
+ final Object? error;
408
+ const _MockSignedUrlResult(this.signedUrl, this.error);
409
+ }
410
+
411
+ class _MockSignedUrlListResult implements SupabaseSignedUrlListResult {
412
+ @override
413
+ final List<SupabaseSignedUrlResult>? data;
414
+ @override
415
+ final Object? error;
416
+ const _MockSignedUrlListResult(this.data, this.error);
417
+ }
418
+
419
+ class _MockUploadResult implements SupabaseUploadResult {
420
+ @override
421
+ final Object? data;
422
+ @override
423
+ final Object? error;
424
+ const _MockUploadResult(this.data, this.error);
425
+ }
426
+
427
+ class _MockRemoveResult implements SupabaseRemoveResult {
428
+ @override
429
+ final Object? data;
430
+ @override
431
+ final Object? error;
432
+ @override
433
+ final String? message;
434
+ const _MockRemoveResult(this.data, this.message) : error = null;
435
+ }
436
+
437
+ class _MockExistsResult implements SupabaseExistsResult {
438
+ @override
439
+ final bool? data;
440
+ @override
441
+ final Object? error;
442
+ @override
443
+ final String? message;
444
+ const _MockExistsResult(this.data, this.error) : message = null;
445
+ }
446
+
447
+ class _MockDownloadResult implements SupabaseDownloadResult {
448
+ @override
449
+ final List<int>? data;
450
+ @override
451
+ final Object? error;
452
+ @override
453
+ final String? message;
454
+ const _MockDownloadResult(this.data, this.error) : message = null;
455
+ }
456
+
457
+ class _StorageClient implements SupabaseStorageClientLike {
458
+ final String _baseUrl;
459
+ final Map<String, String> _headers;
460
+ _StorageClient(this._baseUrl, this._headers);
461
+
462
+ @override
463
+ SupabaseStorageBucketLike from(String bucket) =>
464
+ _StorageBucket(_baseUrl, bucket, _headers);
465
+ }
466
+
467
+ /// [SupabaseClientLike] implementation backed by the Supabase REST API.
468
+ class SupabaseHttpClient implements SupabaseClientLike {
469
+ final String _baseUrl;
470
+ final Map<String, String> _headers;
471
+
472
+ SupabaseHttpClient(String supabaseUrl, String key)
473
+ : _baseUrl = supabaseUrl,
474
+ _headers = {
475
+ 'apikey': key,
476
+ 'Authorization': 'Bearer $key',
477
+ };
478
+
479
+ @override
480
+ SupabaseQueryBuilderLike from(String table) =>
481
+ _QueryBuilder(_baseUrl, table, _headers);
482
+
483
+ @override
484
+ SupabaseStorageClientLike get storage =>
485
+ _StorageClient(_baseUrl, _headers);
486
+
487
+ @override
488
+ Future<SupabaseRpcResponse> rpc(
489
+ String functionName, {
490
+ Map<String, dynamic>? params,
491
+ }) async {
492
+ final uri = Uri.parse('$_baseUrl/rest/v1/rpc/$functionName');
493
+ final res = await http.post(
494
+ uri,
495
+ headers: {..._headers, 'Content-Type': 'application/json'},
496
+ body: params == null ? null : jsonEncode(params),
497
+ );
498
+ if (res.statusCode >= 400) {
499
+ return _RpcResponse(null, jsonDecode(res.body));
500
+ }
501
+ final body = res.body.isEmpty ? null : jsonDecode(res.body);
502
+ return _RpcResponse(body, null);
503
+ }
504
+ }
505
+
506
+ class _RpcResponse implements SupabaseRpcResponse {
507
+ @override
508
+ final Object? data;
509
+ @override
510
+ final Object? error;
511
+ const _RpcResponse(this.data, this.error);
512
+ }
513
+
514
+ /// Default factory: builds a [SupabaseHttpClient] from credentials.
515
+ SupabaseClientLike createSupabaseHttpClient(String url, String key) =>
516
+ SupabaseHttpClient(url, key);
@@ -0,0 +1,34 @@
1
+ /// Faithful port of hot-updater `plugins/supabase/src/supabaseConfig.ts`.
2
+ library;
3
+
4
+ import 'supabase_client_adapter.dart';
5
+
6
+ /// Configuration requiring either a service role key or an anon key.
7
+ class SupabaseServiceRoleConfig {
8
+ final String supabaseUrl;
9
+ final String? supabaseServiceRoleKey;
10
+ final String? supabaseAnonKey;
11
+
12
+ /// Optional test seam: a factory that builds the [SupabaseClientLike] used by
13
+ /// the plugin. When omitted, [createSupabaseHttpClient] is used (REST API).
14
+ /// Mirrors the TS `vi.mock("@supabase/supabase-js")` test seam.
15
+ final SupabaseClientFactory? clientFactory;
16
+
17
+ const SupabaseServiceRoleConfig({
18
+ required this.supabaseUrl,
19
+ this.supabaseServiceRoleKey,
20
+ this.supabaseAnonKey,
21
+ this.clientFactory,
22
+ });
23
+
24
+ /// Resolve the best available key — service role preferred over anon.
25
+ String resolveKey() {
26
+ final key = supabaseServiceRoleKey ?? supabaseAnonKey;
27
+ if (key == null || key.isEmpty) {
28
+ throw StateError(
29
+ 'Supabase service role key is required. Set supabaseServiceRoleKey.',
30
+ );
31
+ }
32
+ return key;
33
+ }
34
+ }