diff --git a/.gcb/builds/resources/main.tf b/.gcb/builds/resources/main.tf index af1ee47e..62876baa 100644 --- a/.gcb/builds/resources/main.tf +++ b/.gcb/builds/resources/main.tf @@ -30,3 +30,9 @@ resource "google_cloudbuild_worker_pool" "pool" { } } +resource "google_firestore_database" "default" { + project = var.project + name = "(default)" + location_id = "us-central1" + type = "FIRESTORE_NATIVE" +} diff --git a/.gcb/builds/services/main.tf b/.gcb/builds/services/main.tf index 350f4325..db7b58b2 100644 --- a/.gcb/builds/services/main.tf +++ b/.gcb/builds/services/main.tf @@ -32,6 +32,18 @@ resource "google_project_service" "cloudfunctions" { disable_dependent_services = true } +resource "google_project_service" "firestore" { + project = var.project + service = "firestore.googleapis.com" + + timeouts { + create = "30m" + update = "40m" + } + + disable_dependent_services = true +} + resource "google_project_service" "iam" { project = var.project service = "iam.googleapis.com" diff --git a/.github/workflows/dart_checks.yaml b/.github/workflows/dart_checks.yaml index d1f2d3bd..29e95aba 100644 --- a/.github/workflows/dart_checks.yaml +++ b/.github/workflows/dart_checks.yaml @@ -25,7 +25,7 @@ on: # reproducable builds (formatting can change between SDK versions, ...). env: DART_VERSION: 3.9.2 - GOOGLE_CLOUD_PROJECT: skilful-orb-203421 + GOOGLE_CLOUD_PROJECT: test-project LIBRARIAN_VERSION: v0.10.2-0.20260423200643-d33862137d59 name: Dart Checks jobs: @@ -69,6 +69,38 @@ jobs: - run: docker run -d --rm -p 9000:9000 -p 8888:8888 gcr.io/cloud-devrel-public-resources/storage-testbench:latest - run: dart test -P storage-testbench . + firestore_emulator_tests: + name: Firestore Emulator Tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a + - uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e + with: + distribution: 'zulu' + java-version: '25' + - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c + with: + sdk: ${{ env.DART_VERSION }} + - run: dart pub get + - name: Install Firebase CLI + run: npm install -g firebase-tools + - name: Run Emulators + run: | + const { spawn } = require('child_process'); + const cmd = process.platform === 'win32' ? 'npx.cmd' : 'npx'; + spawn( + cmd, + ['firebase', 'emulators:start'], + { stdio: 'ignore', detached: true, shell: process.platform === 'win32' }).unref(); + shell: node {0} + working-directory: firestore_emulators + - name: Run Tests + env: + FIRESTORE_EMULATOR_HOST: 127.0.0.1:8080 + STORAGE_EMULATOR_HOST: 127.0.0.1:9199 + run: dart test -P firebase-emulator . + generator: name: Generators runs-on: ubuntu-latest diff --git a/.github/workflows/gcs_emulator_tests.yaml b/.github/workflows/gcs_emulator_tests.yaml deleted file mode 100644 index 92cbd390..00000000 --- a/.github/workflows/gcs_emulator_tests.yaml +++ /dev/null @@ -1,74 +0,0 @@ -# Copyright 2026 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -permissions: read-all - -# Run on PRs and pushes to the default branch that touch the GCS package. -on: - push: - branches: [main] - paths: - - 'pkgs/google_cloud_storage/**' - - '.github/workflows/gcs_emulator_tests.yaml' - pull_request: - branches: [main] - paths: - - 'pkgs/google_cloud_storage/**' - - '.github/workflows/gcs_emulator_tests.yaml' - -# We use a specific Dart SDK version here in order to have hermetic, -# reproducable builds (formatting can change between SDK versions, ...). -env: - DART_VERSION: 3.9.2 - -defaults: - run: - working-directory: pkgs/google_cloud_storage - -name: Google Cloud Storage Emulator Tests -jobs: - tests: - strategy: - fail-fast: false - matrix: - os: [macos-latest, windows-latest, ubuntu-latest] - platform: ["vm", "chrome"] - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a - - uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e - with: - distribution: 'zulu' - java-version: '25' - - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c - with: - sdk: ${{ env.DART_VERSION }} - - run: dart pub get - - name: Install Firebase CLI - run: npm install -g firebase-tools - - name: Run Emulators - run: | - const { spawn } = require('child_process'); - const cmd = process.platform === 'win32' ? 'npx.cmd' : 'npx'; - spawn( - cmd, - ['firebase', 'emulators:start'], - { stdio: 'ignore', detached: true, shell: process.platform === 'win32' }).unref(); - shell: node {0} - working-directory: pkgs/google_cloud_storage/emulator_test - - name: Run Tests - env: - STORAGE_EMULATOR_HOST: 127.0.0.1:9199 - run: dart test -p ${{ matrix.platform }} emulator_test -P firebase-emulator diff --git a/deps.png b/deps.png index 80807eca..f2453ec6 100644 Binary files a/deps.png and b/deps.png differ diff --git a/pkgs/google_cloud_storage/emulator_test/.firebaserc b/firestore_emulators/.firebaserc similarity index 100% rename from pkgs/google_cloud_storage/emulator_test/.firebaserc rename to firestore_emulators/.firebaserc diff --git a/pkgs/google_cloud_storage/emulator_test/.gitignore b/firestore_emulators/.gitignore similarity index 100% rename from pkgs/google_cloud_storage/emulator_test/.gitignore rename to firestore_emulators/.gitignore diff --git a/firestore_emulators/README.md b/firestore_emulators/README.md new file mode 100644 index 00000000..923adcfd --- /dev/null +++ b/firestore_emulators/README.md @@ -0,0 +1,31 @@ +# Firebase Emulator Tests + +This directory contains configuration for running tests that target +the [Firebase Emulators Suite][]. + +## Running Tests Locally + +To run these tests on your machine, you need to have the emulator running in +one terminal session and execute the tests in another. + +### 1. Start the Firebase Emulator + +Navigate to this directory and start the emulator: + +```bash +firebase emulators:start +``` + +### 2. Run the Tests + +In a separate terminal, navigate to the root of the repository and run the +tests: + +```bash +GOOGLE_CLOUD_PROJECT=test-project \ +FIRESTORE_EMULATOR_HOST=127.0.0.1:8080 \ +STORAGE_EMULATOR_HOST=127.0.0.1:9199 \ + dart test -P firebase-emulator . +``` + +[Firebase Emulators Suite]: https://firebase.google.com/docs/emulator-suite diff --git a/pkgs/google_cloud_storage/emulator_test/firebase.json b/firestore_emulators/firebase.json similarity index 70% rename from pkgs/google_cloud_storage/emulator_test/firebase.json rename to firestore_emulators/firebase.json index d44be2e7..fdd5e291 100644 --- a/pkgs/google_cloud_storage/emulator_test/firebase.json +++ b/firestore_emulators/firebase.json @@ -10,6 +10,9 @@ "enabled": true, "port": 4000 }, - "singleProjectMode": true + "singleProjectMode": true, + "firestore": { + "port": 8080 + } } } diff --git a/pkgs/google_cloud_storage/emulator_test/storage.rules b/firestore_emulators/storage.rules similarity index 100% rename from pkgs/google_cloud_storage/emulator_test/storage.rules rename to firestore_emulators/storage.rules diff --git a/generated/google_cloud_firestore_v1/.gitattributes b/generated/google_cloud_firestore_v1/.gitattributes new file mode 100644 index 00000000..a4e4eea4 --- /dev/null +++ b/generated/google_cloud_firestore_v1/.gitattributes @@ -0,0 +1,3 @@ +lib/firestore.dart linguist-generated=true +lib/testing.dart linguist-generated=true +lib/src/api.g.dart linguist-generated=true diff --git a/generated/google_cloud_firestore_v1/LICENSE b/generated/google_cloud_firestore_v1/LICENSE new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/generated/google_cloud_firestore_v1/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/generated/google_cloud_firestore_v1/README.md b/generated/google_cloud_firestore_v1/README.md new file mode 100644 index 00000000..b9a9e0b9 --- /dev/null +++ b/generated/google_cloud_firestore_v1/README.md @@ -0,0 +1,30 @@ +## Cloud Firestore API + +The Google Cloud client library for the Cloud Firestore API. + + + +> [!TIP] +> Most applications should use the higher-level +> [`package:google_cloud_firestore`](https://pub.dev/packages/google_cloud_firestore). + +> [!NOTE] +> This package is currently experimental and published under the +> [labs.dart.dev](https://dart.dev/dart-team-packages) pub publisher in order +> to solicit feedback. +> +> For packages in the labs.dart.dev publisher we generally plan to either +> graduate the package into a supported publisher (dart.dev, tools.dart.dev) +> after a period of feedback and iteration, or discontinue the package. +> These packages have a much higher expected rate of API and breaking changes. +> +> Your feedback is valuable and will help us evolve this package. For general +> feedback, suggestions, and comments, please file an issue in the +> [bug tracker](https://github.com/googleapis/google-cloud-dart/issues). + +## What's this? + +The Google Cloud client library for the Cloud Firestore API. + +Accesses the NoSQL document database built for automatic scaling, high +performance, and ease of application development. diff --git a/generated/google_cloud_firestore_v1/dart_test.yaml b/generated/google_cloud_firestore_v1/dart_test.yaml new file mode 100644 index 00000000..21da9650 --- /dev/null +++ b/generated/google_cloud_firestore_v1/dart_test.yaml @@ -0,0 +1,43 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +platforms: [vm, chrome] + +test-randomize-ordering-seed: random +# Keeping concurrency low helps limit load timeouts on chrome tests. +concurrency: 1 + +tags: + google-cloud: + firebase-emulator: + storage-testbench: + no-ulba: # tests that don't work with uniform bucket level access +exclude_tags: google-cloud || firebase-emulator || storage-testbench + +presets: + google-cloud: + include_tags: google-cloud + # Currently tests that require a configured Google Cloud project are only + # runnable on the VM because the browser does not have access to + # application default credentials. + platforms: [vm] + # Uncomment the the line below when running the tests locally with a + # project that has UBLA enabled. See: + # https://docs.cloud.google.com/storage/docs/uniform-bucket-level-access + # exclude_tags: no-ulba + firebase-emulator: + include_tags: firebase-emulator + storage-testbench: + include_tags: storage-testbench + platforms: [vm] diff --git a/generated/google_cloud_firestore_v1/example/main.dart b/generated/google_cloud_firestore_v1/example/main.dart new file mode 100644 index 00000000..4eead3b2 --- /dev/null +++ b/generated/google_cloud_firestore_v1/example/main.dart @@ -0,0 +1,43 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'package:google_cloud_firestore_v1/firestore.dart'; +import 'package:googleapis_auth/auth_io.dart' as auth; + +void main() async { + // Connects to the Cloud Firestore API using Application Default + // Connections (ADC). + // + // Before running this example, you need to authenticate with gcloud: + // + // ``` + // $ gcloud auth application-default login + // ``` + // + // See https://cloud.google.com/docs/authentication/application-default-credentials + final client = await auth.clientViaApplicationDefaultCredentials( + scopes: ['https://www.googleapis.com/auth/cloud-platform'], + ); + final service = Firestore(client: client); + + final result = await service.createDocument( + CreateDocumentRequest( + parent: 'projects//databases//documents', + collectionId: 'users', + document: Document(fields: {'firstName': Value(stringValue: 'Brian')}), + ), + ); + print('Created document: ${result.name}'); + service.close(); +} diff --git a/generated/google_cloud_firestore_v1/lib/firestore.dart b/generated/google_cloud_firestore_v1/lib/firestore.dart new file mode 100644 index 00000000..c856304a --- /dev/null +++ b/generated/google_cloud_firestore_v1/lib/firestore.dart @@ -0,0 +1,27 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Code generated by sidekick. DO NOT EDIT. + +/// The Google Cloud client for the Cloud Firestore API. +/// +/// Accesses the NoSQL document database built for automatic scaling, high +/// performance, and ease of application development. +library; + +// ignore_for_file: comment_references +// ignore_for_file: lines_longer_than_80_chars +// ignore_for_file: unintended_html_in_doc_comment + +export 'src/api.g.dart' hide FakeFirestore; diff --git a/generated/google_cloud_firestore_v1/lib/src/api.g.dart b/generated/google_cloud_firestore_v1/lib/src/api.g.dart new file mode 100644 index 00000000..7826f016 --- /dev/null +++ b/generated/google_cloud_firestore_v1/lib/src/api.g.dart @@ -0,0 +1,6400 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Code generated by sidekick. DO NOT EDIT. + +/// The Google Cloud client for the Cloud Firestore API. +/// +/// Accesses the NoSQL document database built for automatic scaling, high +/// performance, and ease of application development. +library; + +// ignore_for_file: avoid_unused_constructor_parameters +// ignore_for_file: camel_case_types +// ignore_for_file: comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: implementation_imports +// ignore_for_file: lines_longer_than_80_chars +// ignore_for_file: non_constant_identifier_names +// ignore_for_file: unintended_html_in_doc_comment +// ignore_for_file: use_null_aware_elements + +import 'package:google_cloud_longrunning/longrunning.dart'; +import 'package:google_cloud_protobuf/protobuf.dart'; +import 'package:google_cloud_protobuf/src/encoding.dart'; +import 'package:google_cloud_rpc/exceptions.dart'; +import 'package:google_cloud_rpc/rpc.dart'; +import 'package:google_cloud_rpc/service_client.dart'; +import 'package:google_cloud_type/type.dart'; +import 'package:http/http.dart' as http; + +const _apiKeys = ['GOOGLE_API_KEY']; + +/// The Cloud Firestore service. +/// +/// Cloud Firestore is a fast, fully managed, serverless, cloud-native NoSQL +/// document database that simplifies storing, syncing, and querying data for +/// your mobile, web, and IoT apps at global scale. Its client libraries provide +/// live synchronization and offline support, while its security features and +/// integrations with Firebase and Google Cloud Platform accelerate building +/// truly serverless apps. +final class Firestore { + static const _defaultHost = 'firestore.googleapis.com'; + final Uri _endPoint; + + final ServiceClient _client; + + /// Creates a `Firestore` using [client] for transport. + /// + /// The provided [http.Client] must be configured to provide whatever + /// authentication is required by `Firestore`. You can do that using + /// [`package:googleapis_auth`](https://pub.dev/packages/googleapis_auth). + /// + /// If [endPoint] is provided then its `scheme`, `host`, and `port` are + /// used for all API requests. For example, `Uri.http('127.0.0.1:8080')` + /// could be used to force the `Firestore` service to communicate with the + /// local emulator. + Firestore({required http.Client client, Uri? endPoint}) + : _client = ServiceClient(client: client), + _endPoint = endPoint == null + ? Uri.https(_defaultHost, '') + : Uri( + scheme: endPoint.scheme, + host: endPoint.host, + port: endPoint.port, + ); + + /// Creates a `Firestore` that does authentication through an API key. + /// + /// If called without arguments, the API key is taken from these environment + /// variables: + /// + /// - `GOOGLE_API_KEY` + /// + /// Throws [ConfigurationException] if called without arguments and none of + /// the above environment variables are set. On the web, + /// always throws [ConfigurationException] if called without arguments. + /// + /// See [API Keys Overview](https://cloud.google.com/api-keys/docs/overview). + factory Firestore.fromApiKey([String? apiKey]) => + Firestore(client: httpClientFromApiKey(apiKey, _apiKeys)); + + /// Gets a single document. + /// + /// Throws a [http.ClientException] if there were problems communicating with + /// the API service. Throws a [ServiceException] if the API method failed for + /// any reason. + Future getDocument(GetDocumentRequest request) async { + final url = _endPoint.replace( + path: '/v1/${request.name}', + queryParameters: { + if (request.mask?.fieldPaths case final $1? when $1.isNotDefault) + 'mask.fieldPaths': $1, + if (request.transaction case final $1?) 'transaction': encodeBytes($1)!, + if (request.readTime case final $1?) 'readTime': $1.toJson(), + }, + ); + final response = await _client.get(url); + return Document.fromJson(response); + } + + /// Lists documents. + /// + /// Throws a [http.ClientException] if there were problems communicating with + /// the API service. Throws a [ServiceException] if the API method failed for + /// any reason. + Future listDocuments( + ListDocumentsRequest request, + ) async { + final url = _endPoint.replace( + path: '/v1/${request.parent}/${request.collectionId}', + queryParameters: { + if (request.pageSize case final $1 when $1.isNotDefault) + 'pageSize': '${$1}', + if (request.pageToken case final $1 when $1.isNotDefault) + 'pageToken': $1, + if (request.orderBy case final $1 when $1.isNotDefault) 'orderBy': $1, + if (request.mask?.fieldPaths case final $1? when $1.isNotDefault) + 'mask.fieldPaths': $1, + if (request.transaction case final $1?) 'transaction': encodeBytes($1)!, + if (request.readTime case final $1?) 'readTime': $1.toJson(), + if (request.showMissing case final $1 when $1.isNotDefault) + 'showMissing': '${$1}', + }, + ); + final response = await _client.get(url); + return ListDocumentsResponse.fromJson(response); + } + + /// Updates or inserts a document. + /// + /// Throws a [http.ClientException] if there were problems communicating with + /// the API service. Throws a [ServiceException] if the API method failed for + /// any reason. + Future updateDocument(UpdateDocumentRequest request) async { + final url = _endPoint.replace( + path: '/v1/${request.document!.name}', + queryParameters: { + if (request.updateMask?.fieldPaths case final $1? when $1.isNotDefault) + 'updateMask.fieldPaths': $1, + if (request.mask?.fieldPaths case final $1? when $1.isNotDefault) + 'mask.fieldPaths': $1, + if (request.currentDocument?.exists case final $1?) + 'currentDocument.exists': '${$1}', + if (request.currentDocument?.updateTime case final $1?) + 'currentDocument.updateTime': $1.toJson(), + }, + ); + final response = await _client.patch(url, body: request.document); + return Document.fromJson(response); + } + + /// Deletes a document. + /// + /// Throws a [http.ClientException] if there were problems communicating with + /// the API service. Throws a [ServiceException] if the API method failed for + /// any reason. + Future deleteDocument(DeleteDocumentRequest request) async { + final url = _endPoint.replace( + path: '/v1/${request.name}', + queryParameters: { + if (request.currentDocument?.exists case final $1?) + 'currentDocument.exists': '${$1}', + if (request.currentDocument?.updateTime case final $1?) + 'currentDocument.updateTime': $1.toJson(), + }, + ); + await _client.delete(url); + } + + /// Gets multiple documents. + /// + /// Documents returned by this method are not guaranteed to be returned in the + /// same order that they were requested. + /// + /// Throws a [http.ClientException] if there were problems communicating with + /// the API service. Throws a [ServiceException] if the API method failed for + /// any reason. + Stream batchGetDocuments( + BatchGetDocumentsRequest request, + ) { + final url = _endPoint.replace( + path: '/v1/${request.database}/documents:batchGet', + ); + return _client + .postStreaming(url, body: request, enableSse: false) + .map(BatchGetDocumentsResponse.fromJson); + } + + /// Starts a new transaction. + /// + /// Throws a [http.ClientException] if there were problems communicating with + /// the API service. Throws a [ServiceException] if the API method failed for + /// any reason. + Future beginTransaction( + BeginTransactionRequest request, + ) async { + final url = _endPoint.replace( + path: '/v1/${request.database}/documents:beginTransaction', + ); + final response = await _client.post(url, body: request); + return BeginTransactionResponse.fromJson(response); + } + + /// Commits a transaction, while optionally updating documents. + /// + /// Throws a [http.ClientException] if there were problems communicating with + /// the API service. Throws a [ServiceException] if the API method failed for + /// any reason. + Future commit(CommitRequest request) async { + final url = _endPoint.replace( + path: '/v1/${request.database}/documents:commit', + ); + final response = await _client.post(url, body: request); + return CommitResponse.fromJson(response); + } + + /// Rolls back a transaction. + /// + /// Throws a [http.ClientException] if there were problems communicating with + /// the API service. Throws a [ServiceException] if the API method failed for + /// any reason. + Future rollback(RollbackRequest request) async { + final url = _endPoint.replace( + path: '/v1/${request.database}/documents:rollback', + ); + await _client.post(url, body: request); + } + + /// Runs a query. + /// + /// Throws a [http.ClientException] if there were problems communicating with + /// the API service. Throws a [ServiceException] if the API method failed for + /// any reason. + Stream runQuery(RunQueryRequest request) { + final url = _endPoint.replace(path: '/v1/${request.parent}:runQuery'); + return _client + .postStreaming(url, body: request, enableSse: false) + .map(RunQueryResponse.fromJson); + } + + /// Executes a pipeline query. + /// + /// Throws a [http.ClientException] if there were problems communicating with + /// the API service. Throws a [ServiceException] if the API method failed for + /// any reason. + Stream executePipeline( + ExecutePipelineRequest request, + ) { + final url = _endPoint.replace( + path: '/v1/${request.database}/documents:executePipeline', + ); + return _client + .postStreaming(url, body: request, enableSse: false) + .map(ExecutePipelineResponse.fromJson); + } + + /// Runs an aggregation query. + /// + /// Rather than producing `Document` results like + /// `Firestore.RunQuery`, this API + /// allows running an aggregation to produce a series of + /// `AggregationResult` server-side. + /// + /// High-Level Example: + /// + /// ``` + /// -- Return the number of documents in table given a filter. + /// SELECT COUNT(*) FROM ( SELECT * FROM k where a = true ); + /// ``` + /// + /// Throws a [http.ClientException] if there were problems communicating with + /// the API service. Throws a [ServiceException] if the API method failed for + /// any reason. + Stream runAggregationQuery( + RunAggregationQueryRequest request, + ) { + final url = _endPoint.replace( + path: '/v1/${request.parent}:runAggregationQuery', + ); + return _client + .postStreaming(url, body: request, enableSse: false) + .map(RunAggregationQueryResponse.fromJson); + } + + /// Partitions a query by returning partition cursors that can be used to run + /// the query in parallel. The returned partition cursors are split points that + /// can be used by RunQuery as starting/end points for the query results. + /// + /// Throws a [http.ClientException] if there were problems communicating with + /// the API service. Throws a [ServiceException] if the API method failed for + /// any reason. + Future partitionQuery( + PartitionQueryRequest request, + ) async { + final url = _endPoint.replace(path: '/v1/${request.parent}:partitionQuery'); + final response = await _client.post(url, body: request); + return PartitionQueryResponse.fromJson(response); + } + + /// Lists all the collection IDs underneath a document. + /// + /// Throws a [http.ClientException] if there were problems communicating with + /// the API service. Throws a [ServiceException] if the API method failed for + /// any reason. + Future listCollectionIds( + ListCollectionIdsRequest request, + ) async { + final url = _endPoint.replace( + path: '/v1/${request.parent}:listCollectionIds', + ); + final response = await _client.post(url, body: request); + return ListCollectionIdsResponse.fromJson(response); + } + + /// Applies a batch of write operations. + /// + /// The BatchWrite method does not apply the write operations atomically + /// and can apply them out of order. Method does not allow more than one write + /// per document. Each write succeeds or fails independently. See the + /// `BatchWriteResponse` for the + /// success status of each write. + /// + /// If you require an atomically applied set of writes, use + /// `Commit` instead. + /// + /// Throws a [http.ClientException] if there were problems communicating with + /// the API service. Throws a [ServiceException] if the API method failed for + /// any reason. + Future batchWrite(BatchWriteRequest request) async { + final url = _endPoint.replace( + path: '/v1/${request.database}/documents:batchWrite', + ); + final response = await _client.post(url, body: request); + return BatchWriteResponse.fromJson(response); + } + + /// Creates a new document. + /// + /// Throws a [http.ClientException] if there were problems communicating with + /// the API service. Throws a [ServiceException] if the API method failed for + /// any reason. + Future createDocument(CreateDocumentRequest request) async { + final url = _endPoint.replace( + path: '/v1/${request.parent}/${request.collectionId}', + queryParameters: { + if (request.documentId case final $1 when $1.isNotDefault) + 'documentId': $1, + if (request.mask?.fieldPaths case final $1? when $1.isNotDefault) + 'mask.fieldPaths': $1, + }, + ); + final response = await _client.post(url, body: request.document); + return Document.fromJson(response); + } + + /// Provides the `Operations` service functionality in this service. + /// + /// Throws a [http.ClientException] if there were problems communicating with + /// the API service. Throws a [ServiceException] if the API method failed for + /// any reason. + Future listOperations( + ListOperationsRequest request, + ) async { + final url = _endPoint.replace( + path: '/v1/${request.name}/operations', + queryParameters: { + if (request.filter case final $1 when $1.isNotDefault) 'filter': $1, + if (request.pageSize case final $1 when $1.isNotDefault) + 'pageSize': '${$1}', + if (request.pageToken case final $1 when $1.isNotDefault) + 'pageToken': $1, + if (request.returnPartialSuccess case final $1 when $1.isNotDefault) + 'returnPartialSuccess': '${$1}', + }, + ); + final response = await _client.get(url); + return ListOperationsResponse.fromJson(response); + } + + /// Provides the `Operations` service functionality in this service. + /// + /// Throws a [http.ClientException] if there were problems communicating with + /// the API service. Throws a [ServiceException] if the API method failed for + /// any reason. + /// + /// This method can be used to get the current status of a long-running + /// operation. + Future> getOperation< + T extends ProtoMessage, + S extends ProtoMessage + >(Operation request) async { + final url = _endPoint.replace(path: '/v1/${request.name}'); + final response = await _client.get(url); + return Operation.fromJson(response, request.operationHelper); + } + + /// Provides the `Operations` service functionality in this service. + /// + /// Throws a [http.ClientException] if there were problems communicating with + /// the API service. Throws a [ServiceException] if the API method failed for + /// any reason. + Future deleteOperation(DeleteOperationRequest request) async { + final url = _endPoint.replace(path: '/v1/${request.name}'); + await _client.delete(url); + } + + /// Provides the `Operations` service functionality in this service. + /// + /// Throws a [http.ClientException] if there were problems communicating with + /// the API service. Throws a [ServiceException] if the API method failed for + /// any reason. + Future cancelOperation(CancelOperationRequest request) async { + final url = _endPoint.replace(path: '/v1/${request.name}:cancel'); + await _client.post(url, body: request); + } + + /// Closes the client and cleans up any resources associated with it. + /// + /// Once [close] is called, no other methods should be called. + void close() => _client.close(); +} + +/// Testing fake for [Firestore]. +base class FakeFirestore implements Firestore { + final Future Function(GetDocumentRequest request)? _getDocument; + final Future Function(ListDocumentsRequest request)? + _listDocuments; + final Future Function(UpdateDocumentRequest request)? + _updateDocument; + final Future Function(DeleteDocumentRequest request)? _deleteDocument; + final Stream Function( + BatchGetDocumentsRequest request, + )? + _batchGetDocuments; + final Future Function( + BeginTransactionRequest request, + )? + _beginTransaction; + final Future Function(CommitRequest request)? _commit; + final Future Function(RollbackRequest request)? _rollback; + final Stream Function(RunQueryRequest request)? _runQuery; + final Stream Function( + ExecutePipelineRequest request, + )? + _executePipeline; + final Stream Function( + RunAggregationQueryRequest request, + )? + _runAggregationQuery; + final Future Function(PartitionQueryRequest request)? + _partitionQuery; + final Future Function( + ListCollectionIdsRequest request, + )? + _listCollectionIds; + final Future Function(BatchWriteRequest request)? + _batchWrite; + final Future Function(CreateDocumentRequest request)? + _createDocument; + final Future Function(ListOperationsRequest request)? + _listOperations; + final Future> Function< + T extends ProtoMessage, + S extends ProtoMessage + >(Operation request)? + _getOperation; + final Future Function(DeleteOperationRequest request)? _deleteOperation; + final Future Function(CancelOperationRequest request)? _cancelOperation; + + @override + Uri get _endPoint => throw UnsupportedError('_endPoint'); + @override + ServiceClient get _client => throw UnsupportedError('_client'); + + bool isClosed = false; + + FakeFirestore({ + Future Function(GetDocumentRequest request)? getDocument, + Future Function(ListDocumentsRequest request)? + listDocuments, + Future Function(UpdateDocumentRequest request)? updateDocument, + Future Function(DeleteDocumentRequest request)? deleteDocument, + Stream Function( + BatchGetDocumentsRequest request, + )? + batchGetDocuments, + Future Function(BeginTransactionRequest request)? + beginTransaction, + Future Function(CommitRequest request)? commit, + Future Function(RollbackRequest request)? rollback, + Stream Function(RunQueryRequest request)? runQuery, + Stream Function(ExecutePipelineRequest request)? + executePipeline, + Stream Function( + RunAggregationQueryRequest request, + )? + runAggregationQuery, + Future Function(PartitionQueryRequest request)? + partitionQuery, + Future Function( + ListCollectionIdsRequest request, + )? + listCollectionIds, + Future Function(BatchWriteRequest request)? batchWrite, + Future Function(CreateDocumentRequest request)? createDocument, + Future Function(ListOperationsRequest request)? + listOperations, + Future> Function< + T extends ProtoMessage, + S extends ProtoMessage + >(Operation request)? + getOperation, + Future Function(DeleteOperationRequest request)? deleteOperation, + Future Function(CancelOperationRequest request)? cancelOperation, + }) : _getDocument = getDocument, + _listDocuments = listDocuments, + _updateDocument = updateDocument, + _deleteDocument = deleteDocument, + _batchGetDocuments = batchGetDocuments, + _beginTransaction = beginTransaction, + _commit = commit, + _rollback = rollback, + _runQuery = runQuery, + _executePipeline = executePipeline, + _runAggregationQuery = runAggregationQuery, + _partitionQuery = partitionQuery, + _listCollectionIds = listCollectionIds, + _batchWrite = batchWrite, + _createDocument = createDocument, + _listOperations = listOperations, + _getOperation = getOperation, + _deleteOperation = deleteOperation, + _cancelOperation = cancelOperation; + + /// Gets a single document. + /// + /// Throws a [http.ClientException] if there were problems communicating with + /// the API service. Throws a [ServiceException] if the API method failed for + /// any reason. + @override + Future getDocument(GetDocumentRequest request) async { + if (isClosed) throw StateError('Service is closed'); + + if (_getDocument case final getDocument?) { + return getDocument(request); + } + throw UnsupportedError('getDocument'); + } + + /// Lists documents. + /// + /// Throws a [http.ClientException] if there were problems communicating with + /// the API service. Throws a [ServiceException] if the API method failed for + /// any reason. + @override + Future listDocuments( + ListDocumentsRequest request, + ) async { + if (isClosed) throw StateError('Service is closed'); + + if (_listDocuments case final listDocuments?) { + return listDocuments(request); + } + throw UnsupportedError('listDocuments'); + } + + /// Updates or inserts a document. + /// + /// Throws a [http.ClientException] if there were problems communicating with + /// the API service. Throws a [ServiceException] if the API method failed for + /// any reason. + @override + Future updateDocument(UpdateDocumentRequest request) async { + if (isClosed) throw StateError('Service is closed'); + + if (_updateDocument case final updateDocument?) { + return updateDocument(request); + } + throw UnsupportedError('updateDocument'); + } + + /// Deletes a document. + /// + /// Throws a [http.ClientException] if there were problems communicating with + /// the API service. Throws a [ServiceException] if the API method failed for + /// any reason. + @override + Future deleteDocument(DeleteDocumentRequest request) async { + if (isClosed) throw StateError('Service is closed'); + + if (_deleteDocument case final deleteDocument?) { + return deleteDocument(request); + } + throw UnsupportedError('deleteDocument'); + } + + /// Gets multiple documents. + /// + /// Documents returned by this method are not guaranteed to be returned in the + /// same order that they were requested. + /// + /// Throws a [http.ClientException] if there were problems communicating with + /// the API service. Throws a [ServiceException] if the API method failed for + /// any reason. + @override + Stream batchGetDocuments( + BatchGetDocumentsRequest request, + ) { + if (isClosed) throw StateError('Service is closed'); + if (_batchGetDocuments case final batchGetDocuments?) { + return batchGetDocuments(request); + } + throw UnsupportedError('batchGetDocuments'); + } + + /// Starts a new transaction. + /// + /// Throws a [http.ClientException] if there were problems communicating with + /// the API service. Throws a [ServiceException] if the API method failed for + /// any reason. + @override + Future beginTransaction( + BeginTransactionRequest request, + ) async { + if (isClosed) throw StateError('Service is closed'); + + if (_beginTransaction case final beginTransaction?) { + return beginTransaction(request); + } + throw UnsupportedError('beginTransaction'); + } + + /// Commits a transaction, while optionally updating documents. + /// + /// Throws a [http.ClientException] if there were problems communicating with + /// the API service. Throws a [ServiceException] if the API method failed for + /// any reason. + @override + Future commit(CommitRequest request) async { + if (isClosed) throw StateError('Service is closed'); + + if (_commit case final commit?) { + return commit(request); + } + throw UnsupportedError('commit'); + } + + /// Rolls back a transaction. + /// + /// Throws a [http.ClientException] if there were problems communicating with + /// the API service. Throws a [ServiceException] if the API method failed for + /// any reason. + @override + Future rollback(RollbackRequest request) async { + if (isClosed) throw StateError('Service is closed'); + + if (_rollback case final rollback?) { + return rollback(request); + } + throw UnsupportedError('rollback'); + } + + /// Runs a query. + /// + /// Throws a [http.ClientException] if there were problems communicating with + /// the API service. Throws a [ServiceException] if the API method failed for + /// any reason. + @override + Stream runQuery(RunQueryRequest request) { + if (isClosed) throw StateError('Service is closed'); + if (_runQuery case final runQuery?) { + return runQuery(request); + } + throw UnsupportedError('runQuery'); + } + + /// Executes a pipeline query. + /// + /// Throws a [http.ClientException] if there were problems communicating with + /// the API service. Throws a [ServiceException] if the API method failed for + /// any reason. + @override + Stream executePipeline( + ExecutePipelineRequest request, + ) { + if (isClosed) throw StateError('Service is closed'); + if (_executePipeline case final executePipeline?) { + return executePipeline(request); + } + throw UnsupportedError('executePipeline'); + } + + /// Runs an aggregation query. + /// + /// Rather than producing `Document` results like + /// `Firestore.RunQuery`, this API + /// allows running an aggregation to produce a series of + /// `AggregationResult` server-side. + /// + /// High-Level Example: + /// + /// ``` + /// -- Return the number of documents in table given a filter. + /// SELECT COUNT(*) FROM ( SELECT * FROM k where a = true ); + /// ``` + /// + /// Throws a [http.ClientException] if there were problems communicating with + /// the API service. Throws a [ServiceException] if the API method failed for + /// any reason. + @override + Stream runAggregationQuery( + RunAggregationQueryRequest request, + ) { + if (isClosed) throw StateError('Service is closed'); + if (_runAggregationQuery case final runAggregationQuery?) { + return runAggregationQuery(request); + } + throw UnsupportedError('runAggregationQuery'); + } + + /// Partitions a query by returning partition cursors that can be used to run + /// the query in parallel. The returned partition cursors are split points that + /// can be used by RunQuery as starting/end points for the query results. + /// + /// Throws a [http.ClientException] if there were problems communicating with + /// the API service. Throws a [ServiceException] if the API method failed for + /// any reason. + @override + Future partitionQuery( + PartitionQueryRequest request, + ) async { + if (isClosed) throw StateError('Service is closed'); + + if (_partitionQuery case final partitionQuery?) { + return partitionQuery(request); + } + throw UnsupportedError('partitionQuery'); + } + + /// Lists all the collection IDs underneath a document. + /// + /// Throws a [http.ClientException] if there were problems communicating with + /// the API service. Throws a [ServiceException] if the API method failed for + /// any reason. + @override + Future listCollectionIds( + ListCollectionIdsRequest request, + ) async { + if (isClosed) throw StateError('Service is closed'); + + if (_listCollectionIds case final listCollectionIds?) { + return listCollectionIds(request); + } + throw UnsupportedError('listCollectionIds'); + } + + /// Applies a batch of write operations. + /// + /// The BatchWrite method does not apply the write operations atomically + /// and can apply them out of order. Method does not allow more than one write + /// per document. Each write succeeds or fails independently. See the + /// `BatchWriteResponse` for the + /// success status of each write. + /// + /// If you require an atomically applied set of writes, use + /// `Commit` instead. + /// + /// Throws a [http.ClientException] if there were problems communicating with + /// the API service. Throws a [ServiceException] if the API method failed for + /// any reason. + @override + Future batchWrite(BatchWriteRequest request) async { + if (isClosed) throw StateError('Service is closed'); + + if (_batchWrite case final batchWrite?) { + return batchWrite(request); + } + throw UnsupportedError('batchWrite'); + } + + /// Creates a new document. + /// + /// Throws a [http.ClientException] if there were problems communicating with + /// the API service. Throws a [ServiceException] if the API method failed for + /// any reason. + @override + Future createDocument(CreateDocumentRequest request) async { + if (isClosed) throw StateError('Service is closed'); + + if (_createDocument case final createDocument?) { + return createDocument(request); + } + throw UnsupportedError('createDocument'); + } + + /// Provides the `Operations` service functionality in this service. + /// + /// Throws a [http.ClientException] if there were problems communicating with + /// the API service. Throws a [ServiceException] if the API method failed for + /// any reason. + @override + Future listOperations( + ListOperationsRequest request, + ) async { + if (isClosed) throw StateError('Service is closed'); + + if (_listOperations case final listOperations?) { + return listOperations(request); + } + throw UnsupportedError('listOperations'); + } + + /// Provides the `Operations` service functionality in this service. + /// + /// Throws a [http.ClientException] if there were problems communicating with + /// the API service. Throws a [ServiceException] if the API method failed for + /// any reason. + @override + Future> getOperation< + T extends ProtoMessage, + S extends ProtoMessage + >(Operation request) async { + if (isClosed) throw StateError('Service is closed'); + + if (_getOperation case final getOperation?) { + return getOperation(request); + } + throw UnsupportedError('getOperation'); + } + + /// Provides the `Operations` service functionality in this service. + /// + /// Throws a [http.ClientException] if there were problems communicating with + /// the API service. Throws a [ServiceException] if the API method failed for + /// any reason. + @override + Future deleteOperation(DeleteOperationRequest request) async { + if (isClosed) throw StateError('Service is closed'); + + if (_deleteOperation case final deleteOperation?) { + return deleteOperation(request); + } + throw UnsupportedError('deleteOperation'); + } + + /// Provides the `Operations` service functionality in this service. + /// + /// Throws a [http.ClientException] if there were problems communicating with + /// the API service. Throws a [ServiceException] if the API method failed for + /// any reason. + @override + Future cancelOperation(CancelOperationRequest request) async { + if (isClosed) throw StateError('Service is closed'); + + if (_cancelOperation case final cancelOperation?) { + return cancelOperation(request); + } + throw UnsupportedError('cancelOperation'); + } + + @override + void close() { + isClosed = true; + } +} + +/// The result of a single bucket from a Firestore aggregation query. +/// +/// The keys of `aggregate_fields` are the same for all results in an aggregation +/// query, unlike document queries which can have different fields present for +/// each result. +final class AggregationResult extends ProtoMessage { + static const String fullyQualifiedName = + 'google.firestore.v1.AggregationResult'; + + /// The result of the aggregation functions, ex: `COUNT(*) AS total_docs`. + /// + /// The key is the + /// `alias` + /// assigned to the aggregation function on input and the size of this map + /// equals the number of aggregation functions in the query. + final Map aggregateFields; + + AggregationResult({this.aggregateFields = const {}}) + : super(fullyQualifiedName); + + factory AggregationResult.fromJson(Object? j) { + final json = j as Map; + return AggregationResult( + aggregateFields: switch (json['aggregateFields']) { + null => {}, + Map $1 => { + for (final e in $1.entries) + decodeString(e.key): Value.fromJson(e.value), + }, + _ => throw const FormatException('"aggregateFields" is not an object'), + }, + ); + } + + @override + Object toJson() => { + if (aggregateFields.isNotDefault) + 'aggregateFields': { + for (final e in aggregateFields.entries) e.key: e.value.toJson(), + }, + }; + + @override + String toString() => 'AggregationResult()'; +} + +/// A sequence of bits, encoded in a byte array. +/// +/// Each byte in the `bitmap` byte array stores 8 bits of the sequence. The only +/// exception is the last byte, which may store 8 _or fewer_ bits. The `padding` +/// defines the number of bits of the last byte to be ignored as "padding". The +/// values of these "padding" bits are unspecified and must be ignored. +/// +/// To retrieve the first bit, bit 0, calculate: `(bitmap[0] & 0x01) != 0`. +/// To retrieve the second bit, bit 1, calculate: `(bitmap[0] & 0x02) != 0`. +/// To retrieve the third bit, bit 2, calculate: `(bitmap[0] & 0x04) != 0`. +/// To retrieve the fourth bit, bit 3, calculate: `(bitmap[0] & 0x08) != 0`. +/// To retrieve bit n, calculate: `(bitmap[n / 8] & (0x01 << (n % 8))) != 0`. +/// +/// The "size" of a `BitSequence` (the number of bits it contains) is calculated +/// by this formula: `(bitmap.length * 8) - padding`. +final class BitSequence extends ProtoMessage { + static const String fullyQualifiedName = 'google.firestore.v1.BitSequence'; + + /// The bytes that encode the bit sequence. + /// May have a length of zero. + final Uint8List bitmap; + + /// The number of bits of the last byte in `bitmap` to ignore as "padding". + /// If the length of `bitmap` is zero, then this value must be `0`. + /// Otherwise, this value must be between 0 and 7, inclusive. + final int padding; + + BitSequence({Uint8List? bitmap, this.padding = 0}) + : bitmap = bitmap ?? Uint8List(0), + super(fullyQualifiedName); + + factory BitSequence.fromJson(Object? j) { + final json = j as Map; + return BitSequence( + bitmap: switch (json['bitmap']) { + null => Uint8List(0), + Object $1 => decodeBytes($1), + }, + padding: switch (json['padding']) { + null => 0, + Object $1 => decodeInt($1), + }, + ); + } + + @override + Object toJson() => { + if (bitmap.isNotDefault) 'bitmap': encodeBytes(bitmap), + if (padding.isNotDefault) 'padding': padding, + }; + + @override + String toString() { + final $contents = ['bitmap=$bitmap', 'padding=$padding'].join(','); + return 'BitSequence(${$contents})'; + } +} + +/// A bloom filter (https://en.wikipedia.org/wiki/Bloom_filter). +/// +/// The bloom filter hashes the entries with MD5 and treats the resulting 128-bit +/// hash as 2 distinct 64-bit hash values, interpreted as unsigned integers +/// using 2's complement encoding. +/// +/// These two hash values, named `h1` and `h2`, are then used to compute the +/// `hash_count` hash values using the formula, starting at `i=0`: +/// +/// h(i) = h1 + (i * h2) +/// +/// These resulting values are then taken modulo the number of bits in the bloom +/// filter to get the bits of the bloom filter to test for the given entry. +final class BloomFilter extends ProtoMessage { + static const String fullyQualifiedName = 'google.firestore.v1.BloomFilter'; + + /// The bloom filter data. + final BitSequence? bits; + + /// The number of hashes used by the algorithm. + final int hashCount; + + BloomFilter({this.bits, this.hashCount = 0}) : super(fullyQualifiedName); + + factory BloomFilter.fromJson(Object? j) { + final json = j as Map; + return BloomFilter( + bits: switch (json['bits']) { + null => null, + Object $1 => BitSequence.fromJson($1), + }, + hashCount: switch (json['hashCount']) { + null => 0, + Object $1 => decodeInt($1), + }, + ); + } + + @override + Object toJson() => { + if (bits case final bits?) 'bits': bits.toJson(), + if (hashCount.isNotDefault) 'hashCount': hashCount, + }; + + @override + String toString() { + final $contents = ['hashCount=$hashCount'].join(','); + return 'BloomFilter(${$contents})'; + } +} + +/// A set of field paths on a document. +/// Used to restrict a get or update operation on a document to a subset of its +/// fields. +/// This is different from standard field masks, as this is always scoped to a +/// `Document`, and takes in account the dynamic +/// nature of `Value`. +final class DocumentMask extends ProtoMessage { + static const String fullyQualifiedName = 'google.firestore.v1.DocumentMask'; + + /// The list of field paths in the mask. See + /// `Document.fields` for a field path + /// syntax reference. + final List fieldPaths; + + DocumentMask({this.fieldPaths = const []}) : super(fullyQualifiedName); + + factory DocumentMask.fromJson(Object? j) { + final json = j as Map; + return DocumentMask( + fieldPaths: switch (json['fieldPaths']) { + null => [], + List $1 => [for (final i in $1) decodeString(i)], + _ => throw const FormatException('"fieldPaths" is not a list'), + }, + ); + } + + @override + Object toJson() => {if (fieldPaths.isNotDefault) 'fieldPaths': fieldPaths}; + + @override + String toString() => 'DocumentMask()'; +} + +/// A precondition on a document, used for conditional operations. +final class Precondition extends ProtoMessage { + static const String fullyQualifiedName = 'google.firestore.v1.Precondition'; + + /// When set to `true`, the target document must exist. + /// When set to `false`, the target document must not exist. + final bool? exists; + + /// When set, the target document must exist and have been last updated at + /// that time. Timestamp must be microsecond aligned. + final Timestamp? updateTime; + + Precondition({this.exists, this.updateTime}) : super(fullyQualifiedName); + + factory Precondition.fromJson(Object? j) { + final json = j as Map; + return Precondition( + exists: switch (json['exists']) { + null => null, + Object $1 => decodeBool($1), + }, + updateTime: switch (json['updateTime']) { + null => null, + Object $1 => Timestamp.fromJson($1), + }, + ); + } + + @override + Object toJson() => { + if (exists case final exists?) 'exists': exists, + if (updateTime case final updateTime?) 'updateTime': updateTime.toJson(), + }; + + @override + String toString() { + final $contents = [if (exists != null) 'exists=$exists'].join(','); + return 'Precondition(${$contents})'; + } +} + +/// Options for creating a new transaction. +final class TransactionOptions extends ProtoMessage { + static const String fullyQualifiedName = + 'google.firestore.v1.TransactionOptions'; + + /// The transaction can only be used for read operations. + final TransactionOptions_ReadOnly? readOnly; + + /// The transaction can be used for both read and write operations. + final TransactionOptions_ReadWrite? readWrite; + + TransactionOptions({this.readOnly, this.readWrite}) + : super(fullyQualifiedName); + + factory TransactionOptions.fromJson(Object? j) { + final json = j as Map; + return TransactionOptions( + readOnly: switch (json['readOnly']) { + null => null, + Object $1 => TransactionOptions_ReadOnly.fromJson($1), + }, + readWrite: switch (json['readWrite']) { + null => null, + Object $1 => TransactionOptions_ReadWrite.fromJson($1), + }, + ); + } + + @override + Object toJson() => { + if (readOnly case final readOnly?) 'readOnly': readOnly.toJson(), + if (readWrite case final readWrite?) 'readWrite': readWrite.toJson(), + }; + + @override + String toString() => 'TransactionOptions()'; +} + +/// Options for a transaction that can be used to read and write documents. +/// +/// Firestore does not allow 3rd party auth requests to create read-write. +/// transactions. +final class TransactionOptions_ReadWrite extends ProtoMessage { + static const String fullyQualifiedName = + 'google.firestore.v1.TransactionOptions.ReadWrite'; + + /// An optional transaction to retry. + final Uint8List retryTransaction; + + TransactionOptions_ReadWrite({Uint8List? retryTransaction}) + : retryTransaction = retryTransaction ?? Uint8List(0), + super(fullyQualifiedName); + + factory TransactionOptions_ReadWrite.fromJson(Object? j) { + final json = j as Map; + return TransactionOptions_ReadWrite( + retryTransaction: switch (json['retryTransaction']) { + null => Uint8List(0), + Object $1 => decodeBytes($1), + }, + ); + } + + @override + Object toJson() => { + if (retryTransaction.isNotDefault) + 'retryTransaction': encodeBytes(retryTransaction), + }; + + @override + String toString() { + final $contents = ['retryTransaction=$retryTransaction'].join(','); + return 'ReadWrite(${$contents})'; + } +} + +/// Options for a transaction that can only be used to read documents. +final class TransactionOptions_ReadOnly extends ProtoMessage { + static const String fullyQualifiedName = + 'google.firestore.v1.TransactionOptions.ReadOnly'; + + /// Reads documents at the given time. + /// + /// This must be a microsecond precision timestamp within the past one + /// hour, or if Point-in-Time Recovery is enabled, can additionally be a + /// whole minute timestamp within the past 7 days. + final Timestamp? readTime; + + TransactionOptions_ReadOnly({this.readTime}) : super(fullyQualifiedName); + + factory TransactionOptions_ReadOnly.fromJson(Object? j) { + final json = j as Map; + return TransactionOptions_ReadOnly( + readTime: switch (json['readTime']) { + null => null, + Object $1 => Timestamp.fromJson($1), + }, + ); + } + + @override + Object toJson() => { + if (readTime case final readTime?) 'readTime': readTime.toJson(), + }; + + @override + String toString() => 'ReadOnly()'; +} + +/// A Firestore document. +/// +/// Must not exceed 1 MiB - 4 bytes. +final class Document extends ProtoMessage { + static const String fullyQualifiedName = 'google.firestore.v1.Document'; + + /// The resource name of the document, for example + /// `projects/{project_id}/databases/{database_id}/documents/{document_path}`. + final String name; + + /// The document's fields. + /// + /// The map keys represent field names. + /// + /// Field names matching the regular expression `__.*__` are reserved. Reserved + /// field names are forbidden except in certain documented contexts. The field + /// names, represented as UTF-8, must not exceed 1,500 bytes and cannot be + /// empty. + /// + /// Field paths may be used in other contexts to refer to structured fields + /// defined here. For `map_value`, the field path is represented by a + /// dot-delimited (`.`) string of segments. Each segment is either a simple + /// field name (defined below) or a quoted field name. For example, the + /// structured field `"foo" : { map_value: { "x&y" : { string_value: "hello" + /// }}}` would be represented by the field path `` foo.`x&y` ``. + /// + /// A simple field name contains only characters `a` to `z`, `A` to `Z`, + /// `0` to `9`, or `_`, and must not start with `0` to `9`. For example, + /// `foo_bar_17`. + /// + /// A quoted field name starts and ends with `` ` `` and + /// may contain any character. Some characters, including `` ` ``, must be + /// escaped using a `\`. For example, `` `x&y` `` represents `x&y` and + /// `` `bak\`tik` `` represents `` bak`tik ``. + final Map fields; + + /// Output only. The time at which the document was created. + /// + /// This value increases monotonically when a document is deleted then + /// recreated. It can also be compared to values from other documents and + /// the `read_time` of a query. + final Timestamp? createTime; + + /// Output only. The time at which the document was last changed. + /// + /// This value is initially set to the `create_time` then increases + /// monotonically with each change to the document. It can also be + /// compared to values from other documents and the `read_time` of a query. + final Timestamp? updateTime; + + Document({ + this.name = '', + this.fields = const {}, + this.createTime, + this.updateTime, + }) : super(fullyQualifiedName); + + factory Document.fromJson(Object? j) { + final json = j as Map; + return Document( + name: switch (json['name']) { + null => '', + Object $1 => decodeString($1), + }, + fields: switch (json['fields']) { + null => {}, + Map $1 => { + for (final e in $1.entries) + decodeString(e.key): Value.fromJson(e.value), + }, + _ => throw const FormatException('"fields" is not an object'), + }, + createTime: switch (json['createTime']) { + null => null, + Object $1 => Timestamp.fromJson($1), + }, + updateTime: switch (json['updateTime']) { + null => null, + Object $1 => Timestamp.fromJson($1), + }, + ); + } + + @override + Object toJson() => { + if (name.isNotDefault) 'name': name, + if (fields.isNotDefault) + 'fields': {for (final e in fields.entries) e.key: e.value.toJson()}, + if (createTime case final createTime?) 'createTime': createTime.toJson(), + if (updateTime case final updateTime?) 'updateTime': updateTime.toJson(), + }; + + @override + String toString() { + final $contents = ['name=$name'].join(','); + return 'Document(${$contents})'; + } +} + +/// A message that can hold any of the supported value types. +final class Value extends ProtoMessage { + static const String fullyQualifiedName = 'google.firestore.v1.Value'; + + /// A null value. + final NullValue? nullValue; + + /// A boolean value. + final bool? booleanValue; + + /// An integer value. + final int? integerValue; + + /// A double value. + final double? doubleValue; + + /// A timestamp value. + /// + /// Precise only to microseconds. When stored, any additional precision is + /// rounded down. + final Timestamp? timestampValue; + + /// A string value. + /// + /// The string, represented as UTF-8, must not exceed 1 MiB - 89 bytes. + /// Only the first 1,500 bytes of the UTF-8 representation are considered by + /// queries. + final String? stringValue; + + /// A bytes value. + /// + /// Must not exceed 1 MiB - 89 bytes. + /// Only the first 1,500 bytes are considered by queries. + final Uint8List? bytesValue; + + /// A reference to a document. For example: + /// `projects/{project_id}/databases/{database_id}/documents/{document_path}`. + final String? referenceValue; + + /// A geo point value representing a point on the surface of Earth. + final LatLng? geoPointValue; + + /// An array value. + /// + /// Cannot directly contain another array value, though can contain a + /// map which contains another array. + final ArrayValue? arrayValue; + + /// A map value. + final MapValue? mapValue; + + /// Value which references a field. + /// + /// This is considered relative (vs absolute) since it only refers to a field + /// and not a field within a particular document. + /// + /// **Requires:** + /// + /// * Must follow [field reference][FieldReference.field_path] limitations. + /// + /// * Not allowed to be used when writing documents. + final String? fieldReferenceValue; + + /// Pointer to a variable defined elsewhere in a pipeline. + /// + /// Unlike `field_reference_value` which references a field within a + /// document, this refers to a variable, defined in a separate namespace than + /// the fields of a document. + final String? variableReferenceValue; + + /// A value that represents an unevaluated expression. + /// + /// **Requires:** + /// + /// * Not allowed to be used when writing documents. + final Function$? functionValue; + + /// A value that represents an unevaluated pipeline. + /// + /// **Requires:** + /// + /// * Not allowed to be used when writing documents. + final Pipeline? pipelineValue; + + Value({ + this.nullValue, + this.booleanValue, + this.integerValue, + this.doubleValue, + this.timestampValue, + this.stringValue, + this.bytesValue, + this.referenceValue, + this.geoPointValue, + this.arrayValue, + this.mapValue, + this.fieldReferenceValue, + this.variableReferenceValue, + this.functionValue, + this.pipelineValue, + }) : super(fullyQualifiedName); + + factory Value.fromJson(Object? j) { + final json = j as Map; + return Value( + nullValue: switch (json['nullValue']) { + null => null, + Object $1 => NullValue.fromJson($1), + }, + booleanValue: switch (json['booleanValue']) { + null => null, + Object $1 => decodeBool($1), + }, + integerValue: switch (json['integerValue']) { + null => null, + Object $1 => decodeInt64($1), + }, + doubleValue: switch (json['doubleValue']) { + null => null, + Object $1 => decodeDouble($1), + }, + timestampValue: switch (json['timestampValue']) { + null => null, + Object $1 => Timestamp.fromJson($1), + }, + stringValue: switch (json['stringValue']) { + null => null, + Object $1 => decodeString($1), + }, + bytesValue: switch (json['bytesValue']) { + null => null, + Object $1 => decodeBytes($1), + }, + referenceValue: switch (json['referenceValue']) { + null => null, + Object $1 => decodeString($1), + }, + geoPointValue: switch (json['geoPointValue']) { + null => null, + Object $1 => LatLng.fromJson($1), + }, + arrayValue: switch (json['arrayValue']) { + null => null, + Object $1 => ArrayValue.fromJson($1), + }, + mapValue: switch (json['mapValue']) { + null => null, + Object $1 => MapValue.fromJson($1), + }, + fieldReferenceValue: switch (json['fieldReferenceValue']) { + null => null, + Object $1 => decodeString($1), + }, + variableReferenceValue: switch (json['variableReferenceValue']) { + null => null, + Object $1 => decodeString($1), + }, + functionValue: switch (json['functionValue']) { + null => null, + Object $1 => Function$.fromJson($1), + }, + pipelineValue: switch (json['pipelineValue']) { + null => null, + Object $1 => Pipeline.fromJson($1), + }, + ); + } + + @override + Object toJson() => { + if (nullValue case final nullValue?) 'nullValue': nullValue.toJson(), + if (booleanValue case final booleanValue?) 'booleanValue': booleanValue, + if (integerValue case final integerValue?) + 'integerValue': integerValue.toString(), + if (doubleValue case final doubleValue?) + 'doubleValue': encodeDouble(doubleValue), + if (timestampValue case final timestampValue?) + 'timestampValue': timestampValue.toJson(), + if (stringValue case final stringValue?) 'stringValue': stringValue, + if (bytesValue case final bytesValue?) + 'bytesValue': encodeBytes(bytesValue), + if (referenceValue case final referenceValue?) + 'referenceValue': referenceValue, + if (geoPointValue case final geoPointValue?) + 'geoPointValue': geoPointValue.toJson(), + if (arrayValue case final arrayValue?) 'arrayValue': arrayValue.toJson(), + if (mapValue case final mapValue?) 'mapValue': mapValue.toJson(), + if (fieldReferenceValue case final fieldReferenceValue?) + 'fieldReferenceValue': fieldReferenceValue, + if (variableReferenceValue case final variableReferenceValue?) + 'variableReferenceValue': variableReferenceValue, + if (functionValue case final functionValue?) + 'functionValue': functionValue.toJson(), + if (pipelineValue case final pipelineValue?) + 'pipelineValue': pipelineValue.toJson(), + }; + + @override + String toString() { + final $contents = [ + if (nullValue != null) 'nullValue=$nullValue', + if (booleanValue != null) 'booleanValue=$booleanValue', + if (integerValue != null) 'integerValue=$integerValue', + if (doubleValue != null) 'doubleValue=$doubleValue', + if (stringValue != null) 'stringValue=$stringValue', + if (bytesValue != null) 'bytesValue=$bytesValue', + if (referenceValue != null) 'referenceValue=$referenceValue', + if (fieldReferenceValue != null) + 'fieldReferenceValue=$fieldReferenceValue', + if (variableReferenceValue != null) + 'variableReferenceValue=$variableReferenceValue', + ].join(','); + return 'Value(${$contents})'; + } +} + +/// An array value. +final class ArrayValue extends ProtoMessage { + static const String fullyQualifiedName = 'google.firestore.v1.ArrayValue'; + + /// Values in the array. + final List values; + + ArrayValue({this.values = const []}) : super(fullyQualifiedName); + + factory ArrayValue.fromJson(Object? j) { + final json = j as Map; + return ArrayValue( + values: switch (json['values']) { + null => [], + List $1 => [for (final i in $1) Value.fromJson(i)], + _ => throw const FormatException('"values" is not a list'), + }, + ); + } + + @override + Object toJson() => { + if (values.isNotDefault) 'values': [for (final i in values) i.toJson()], + }; + + @override + String toString() => 'ArrayValue()'; +} + +/// A map value. +final class MapValue extends ProtoMessage { + static const String fullyQualifiedName = 'google.firestore.v1.MapValue'; + + /// The map's fields. + /// + /// The map keys represent field names. Field names matching the regular + /// expression `__.*__` are reserved. Reserved field names are forbidden except + /// in certain documented contexts. The map keys, represented as UTF-8, must + /// not exceed 1,500 bytes and cannot be empty. + final Map fields; + + MapValue({this.fields = const {}}) : super(fullyQualifiedName); + + factory MapValue.fromJson(Object? j) { + final json = j as Map; + return MapValue( + fields: switch (json['fields']) { + null => {}, + Map $1 => { + for (final e in $1.entries) + decodeString(e.key): Value.fromJson(e.value), + }, + _ => throw const FormatException('"fields" is not an object'), + }, + ); + } + + @override + Object toJson() => { + if (fields.isNotDefault) + 'fields': {for (final e in fields.entries) e.key: e.value.toJson()}, + }; + + @override + String toString() => 'MapValue()'; +} + +/// Represents an unevaluated scalar expression. +/// +/// For example, the expression `like(user_name, "%alice%")` is represented as: +/// +/// ``` +/// name: "like" +/// args { field_reference: "user_name" } +/// args { string_value: "%alice%" } +/// ``` +final class Function$ extends ProtoMessage { + static const String fullyQualifiedName = 'google.firestore.v1.Function'; + + /// Required. The name of the function to evaluate. + /// + /// **Requires:** + /// + /// * must be in snake case (lower case with underscore separator). + final String name; + + /// Optional. Ordered list of arguments the given function expects. + final List args; + + /// Optional. Optional named arguments that certain functions may support. + final Map options; + + Function$({required this.name, this.args = const [], this.options = const {}}) + : super(fullyQualifiedName); + + factory Function$.fromJson(Object? j) { + final json = j as Map; + return Function$( + name: switch (json['name']) { + null => '', + Object $1 => decodeString($1), + }, + args: switch (json['args']) { + null => [], + List $1 => [for (final i in $1) Value.fromJson(i)], + _ => throw const FormatException('"args" is not a list'), + }, + options: switch (json['options']) { + null => {}, + Map $1 => { + for (final e in $1.entries) + decodeString(e.key): Value.fromJson(e.value), + }, + _ => throw const FormatException('"options" is not an object'), + }, + ); + } + + @override + Object toJson() => { + 'name': name, + if (args.isNotDefault) 'args': [for (final i in args) i.toJson()], + if (options.isNotDefault) + 'options': {for (final e in options.entries) e.key: e.value.toJson()}, + }; + + @override + String toString() { + final $contents = ['name=$name'].join(','); + return 'Function(${$contents})'; + } +} + +/// A Firestore query represented as an ordered list of operations / stages. +final class Pipeline extends ProtoMessage { + static const String fullyQualifiedName = 'google.firestore.v1.Pipeline'; + + /// Required. Ordered list of stages to evaluate. + final List stages; + + Pipeline({required this.stages}) : super(fullyQualifiedName); + + factory Pipeline.fromJson(Object? j) { + final json = j as Map; + return Pipeline( + stages: switch (json['stages']) { + null => [], + List $1 => [for (final i in $1) Pipeline_Stage.fromJson(i)], + _ => throw const FormatException('"stages" is not a list'), + }, + ); + } + + @override + Object toJson() => { + 'stages': [for (final i in stages) i.toJson()], + }; + + @override + String toString() => 'Pipeline()'; +} + +/// A single operation within a pipeline. +/// +/// A stage is made up of a unique name, and a list of arguments. The exact +/// number of arguments & types is dependent on the stage type. +/// +/// To give an example, the stage `filter(state = "MD")` would be encoded as: +/// +/// ``` +/// name: "filter" +/// args { +/// function_value { +/// name: "eq" +/// args { field_reference_value: "state" } +/// args { string_value: "MD" } +/// } +/// } +/// ``` +/// +/// See public documentation for the full list. +final class Pipeline_Stage extends ProtoMessage { + static const String fullyQualifiedName = 'google.firestore.v1.Pipeline.Stage'; + + /// Required. The name of the stage to evaluate. + /// + /// **Requires:** + /// + /// * must be in snake case (lower case with underscore separator). + final String name; + + /// Optional. Ordered list of arguments the given stage expects. + final List args; + + /// Optional. Optional named arguments that certain functions may support. + final Map options; + + Pipeline_Stage({ + required this.name, + this.args = const [], + this.options = const {}, + }) : super(fullyQualifiedName); + + factory Pipeline_Stage.fromJson(Object? j) { + final json = j as Map; + return Pipeline_Stage( + name: switch (json['name']) { + null => '', + Object $1 => decodeString($1), + }, + args: switch (json['args']) { + null => [], + List $1 => [for (final i in $1) Value.fromJson(i)], + _ => throw const FormatException('"args" is not a list'), + }, + options: switch (json['options']) { + null => {}, + Map $1 => { + for (final e in $1.entries) + decodeString(e.key): Value.fromJson(e.value), + }, + _ => throw const FormatException('"options" is not an object'), + }, + ); + } + + @override + Object toJson() => { + 'name': name, + if (args.isNotDefault) 'args': [for (final i in args) i.toJson()], + if (options.isNotDefault) + 'options': {for (final e in options.entries) e.key: e.value.toJson()}, + }; + + @override + String toString() { + final $contents = ['name=$name'].join(','); + return 'Stage(${$contents})'; + } +} + +/// Pipeline explain stats. +/// +/// Depending on the explain options in the original request, this can contain +/// the optimized plan and / or execution stats. +final class ExplainStats extends ProtoMessage { + static const String fullyQualifiedName = 'google.firestore.v1.ExplainStats'; + + /// The format depends on the `output_format` options in the request. + /// + /// Currently there are two supported options: `TEXT` and `JSON`. + /// Both supply a `google.protobuf.StringValue`. + final Any? data; + + ExplainStats({this.data}) : super(fullyQualifiedName); + + factory ExplainStats.fromJson(Object? j) { + final json = j as Map; + return ExplainStats( + data: switch (json['data']) { + null => null, + Object $1 => Any.fromJson($1), + }, + ); + } + + @override + Object toJson() => {if (data case final data?) 'data': data.toJson()}; + + @override + String toString() => 'ExplainStats()'; +} + +/// The request for +/// `Firestore.GetDocument`. +final class GetDocumentRequest extends ProtoMessage { + static const String fullyQualifiedName = + 'google.firestore.v1.GetDocumentRequest'; + + /// Required. The resource name of the Document to get. In the format: + /// `projects/{project_id}/databases/{database_id}/documents/{document_path}`. + final String name; + + /// The fields to return. If not set, returns all fields. + /// + /// If the document has a field that is not present in this mask, that field + /// will not be returned in the response. + final DocumentMask? mask; + + /// Reads the document in a transaction. + final Uint8List? transaction; + + /// Reads the version of the document at the given time. + /// + /// This must be a microsecond precision timestamp within the past one hour, + /// or if Point-in-Time Recovery is enabled, can additionally be a whole + /// minute timestamp within the past 7 days. + final Timestamp? readTime; + + GetDocumentRequest({ + required this.name, + this.mask, + this.transaction, + this.readTime, + }) : super(fullyQualifiedName); + + factory GetDocumentRequest.fromJson(Object? j) { + final json = j as Map; + return GetDocumentRequest( + name: switch (json['name']) { + null => '', + Object $1 => decodeString($1), + }, + mask: switch (json['mask']) { + null => null, + Object $1 => DocumentMask.fromJson($1), + }, + transaction: switch (json['transaction']) { + null => null, + Object $1 => decodeBytes($1), + }, + readTime: switch (json['readTime']) { + null => null, + Object $1 => Timestamp.fromJson($1), + }, + ); + } + + @override + Object toJson() => { + 'name': name, + if (mask case final mask?) 'mask': mask.toJson(), + if (transaction case final transaction?) + 'transaction': encodeBytes(transaction), + if (readTime case final readTime?) 'readTime': readTime.toJson(), + }; + + @override + String toString() { + final $contents = [ + 'name=$name', + if (transaction != null) 'transaction=$transaction', + ].join(','); + return 'GetDocumentRequest(${$contents})'; + } +} + +/// The request for +/// `Firestore.ListDocuments`. +final class ListDocumentsRequest extends ProtoMessage { + static const String fullyQualifiedName = + 'google.firestore.v1.ListDocumentsRequest'; + + /// Required. The parent resource name. In the format: + /// `projects/{project_id}/databases/{database_id}/documents` or + /// `projects/{project_id}/databases/{database_id}/documents/{document_path}`. + /// + /// For example: + /// `projects/my-project/databases/my-database/documents` or + /// `projects/my-project/databases/my-database/documents/chatrooms/my-chatroom` + final String parent; + + /// Optional. The collection ID, relative to `parent`, to list. + /// + /// For example: `chatrooms` or `messages`. + /// + /// This is optional, and when not provided, Firestore will list documents + /// from all collections under the provided `parent`. + final String collectionId; + + /// Optional. The maximum number of documents to return in a single response. + /// + /// Firestore may return fewer than this value. + final int pageSize; + + /// Optional. A page token, received from a previous `ListDocuments` response. + /// + /// Provide this to retrieve the subsequent page. When paginating, all other + /// parameters (with the exception of `page_size`) must match the values set + /// in the request that generated the page token. + final String pageToken; + + /// Optional. The optional ordering of the documents to return. + /// + /// For example: `priority desc, __name__ desc`. + /// + /// This mirrors the [`ORDER BY`][google.firestore.v1.StructuredQuery.order_by] + /// used in Firestore queries but in a string representation. When absent, + /// documents are ordered based on `__name__ ASC`. + final String orderBy; + + /// Optional. The fields to return. If not set, returns all fields. + /// + /// If a document has a field that is not present in this mask, that field + /// will not be returned in the response. + final DocumentMask? mask; + + /// Perform the read as part of an already active transaction. + final Uint8List? transaction; + + /// Perform the read at the provided time. + /// + /// This must be a microsecond precision timestamp within the past one hour, + /// or if Point-in-Time Recovery is enabled, can additionally be a whole + /// minute timestamp within the past 7 days. + final Timestamp? readTime; + + /// If the list should show missing documents. + /// + /// A document is missing if it does not exist, but there are sub-documents + /// nested underneath it. When true, such missing documents will be returned + /// with a key but will not have fields, + /// [`create_time`][google.firestore.v1.Document.create_time], or + /// [`update_time`][google.firestore.v1.Document.update_time] set. + /// + /// Requests with `show_missing` may not specify `where` or `order_by`. + final bool showMissing; + + ListDocumentsRequest({ + required this.parent, + this.collectionId = '', + this.pageSize = 0, + this.pageToken = '', + this.orderBy = '', + this.mask, + this.transaction, + this.readTime, + this.showMissing = false, + }) : super(fullyQualifiedName); + + factory ListDocumentsRequest.fromJson(Object? j) { + final json = j as Map; + return ListDocumentsRequest( + parent: switch (json['parent']) { + null => '', + Object $1 => decodeString($1), + }, + collectionId: switch (json['collectionId']) { + null => '', + Object $1 => decodeString($1), + }, + pageSize: switch (json['pageSize']) { + null => 0, + Object $1 => decodeInt($1), + }, + pageToken: switch (json['pageToken']) { + null => '', + Object $1 => decodeString($1), + }, + orderBy: switch (json['orderBy']) { + null => '', + Object $1 => decodeString($1), + }, + mask: switch (json['mask']) { + null => null, + Object $1 => DocumentMask.fromJson($1), + }, + transaction: switch (json['transaction']) { + null => null, + Object $1 => decodeBytes($1), + }, + readTime: switch (json['readTime']) { + null => null, + Object $1 => Timestamp.fromJson($1), + }, + showMissing: switch (json['showMissing']) { + null => false, + Object $1 => decodeBool($1), + }, + ); + } + + @override + Object toJson() => { + 'parent': parent, + if (collectionId.isNotDefault) 'collectionId': collectionId, + if (pageSize.isNotDefault) 'pageSize': pageSize, + if (pageToken.isNotDefault) 'pageToken': pageToken, + if (orderBy.isNotDefault) 'orderBy': orderBy, + if (mask case final mask?) 'mask': mask.toJson(), + if (transaction case final transaction?) + 'transaction': encodeBytes(transaction), + if (readTime case final readTime?) 'readTime': readTime.toJson(), + if (showMissing.isNotDefault) 'showMissing': showMissing, + }; + + @override + String toString() { + final $contents = [ + 'parent=$parent', + 'collectionId=$collectionId', + 'pageSize=$pageSize', + 'pageToken=$pageToken', + 'orderBy=$orderBy', + if (transaction != null) 'transaction=$transaction', + 'showMissing=$showMissing', + ].join(','); + return 'ListDocumentsRequest(${$contents})'; + } +} + +/// The response for +/// `Firestore.ListDocuments`. +final class ListDocumentsResponse extends ProtoMessage { + static const String fullyQualifiedName = + 'google.firestore.v1.ListDocumentsResponse'; + + /// The Documents found. + final List documents; + + /// A token to retrieve the next page of documents. + /// + /// If this field is omitted, there are no subsequent pages. + final String nextPageToken; + + ListDocumentsResponse({this.documents = const [], this.nextPageToken = ''}) + : super(fullyQualifiedName); + + factory ListDocumentsResponse.fromJson(Object? j) { + final json = j as Map; + return ListDocumentsResponse( + documents: switch (json['documents']) { + null => [], + List $1 => [for (final i in $1) Document.fromJson(i)], + _ => throw const FormatException('"documents" is not a list'), + }, + nextPageToken: switch (json['nextPageToken']) { + null => '', + Object $1 => decodeString($1), + }, + ); + } + + @override + Object toJson() => { + if (documents.isNotDefault) + 'documents': [for (final i in documents) i.toJson()], + if (nextPageToken.isNotDefault) 'nextPageToken': nextPageToken, + }; + + @override + String toString() { + final $contents = ['nextPageToken=$nextPageToken'].join(','); + return 'ListDocumentsResponse(${$contents})'; + } +} + +/// The request for +/// `Firestore.CreateDocument`. +final class CreateDocumentRequest extends ProtoMessage { + static const String fullyQualifiedName = + 'google.firestore.v1.CreateDocumentRequest'; + + /// Required. The parent resource. For example: + /// `projects/{project_id}/databases/{database_id}/documents` or + /// `projects/{project_id}/databases/{database_id}/documents/chatrooms/{chatroom_id}` + final String parent; + + /// Required. The collection ID, relative to `parent`, to list. For example: + /// `chatrooms`. + final String collectionId; + + /// The client-assigned document ID to use for this document. + /// + /// Optional. If not specified, an ID will be assigned by the service. + final String documentId; + + /// Required. The document to create. `name` must not be set. + final Document? document; + + /// The fields to return. If not set, returns all fields. + /// + /// If the document has a field that is not present in this mask, that field + /// will not be returned in the response. + final DocumentMask? mask; + + CreateDocumentRequest({ + required this.parent, + required this.collectionId, + this.documentId = '', + required this.document, + this.mask, + }) : super(fullyQualifiedName); + + factory CreateDocumentRequest.fromJson(Object? j) { + final json = j as Map; + return CreateDocumentRequest( + parent: switch (json['parent']) { + null => '', + Object $1 => decodeString($1), + }, + collectionId: switch (json['collectionId']) { + null => '', + Object $1 => decodeString($1), + }, + documentId: switch (json['documentId']) { + null => '', + Object $1 => decodeString($1), + }, + document: switch (json['document']) { + null => null, + Object $1 => Document.fromJson($1), + }, + mask: switch (json['mask']) { + null => null, + Object $1 => DocumentMask.fromJson($1), + }, + ); + } + + @override + Object toJson() => { + 'parent': parent, + 'collectionId': collectionId, + if (documentId.isNotDefault) 'documentId': documentId, + if (document case final document?) 'document': document.toJson(), + if (mask case final mask?) 'mask': mask.toJson(), + }; + + @override + String toString() { + final $contents = [ + 'parent=$parent', + 'collectionId=$collectionId', + 'documentId=$documentId', + ].join(','); + return 'CreateDocumentRequest(${$contents})'; + } +} + +/// The request for +/// `Firestore.UpdateDocument`. +final class UpdateDocumentRequest extends ProtoMessage { + static const String fullyQualifiedName = + 'google.firestore.v1.UpdateDocumentRequest'; + + /// Required. The updated document. + /// Creates the document if it does not already exist. + final Document? document; + + /// The fields to update. + /// None of the field paths in the mask may contain a reserved name. + /// + /// If the document exists on the server and has fields not referenced in the + /// mask, they are left unchanged. + /// Fields referenced in the mask, but not present in the input document, are + /// deleted from the document on the server. + final DocumentMask? updateMask; + + /// The fields to return. If not set, returns all fields. + /// + /// If the document has a field that is not present in this mask, that field + /// will not be returned in the response. + final DocumentMask? mask; + + /// An optional precondition on the document. + /// The request will fail if this is set and not met by the target document. + final Precondition? currentDocument; + + UpdateDocumentRequest({ + required this.document, + this.updateMask, + this.mask, + this.currentDocument, + }) : super(fullyQualifiedName); + + factory UpdateDocumentRequest.fromJson(Object? j) { + final json = j as Map; + return UpdateDocumentRequest( + document: switch (json['document']) { + null => null, + Object $1 => Document.fromJson($1), + }, + updateMask: switch (json['updateMask']) { + null => null, + Object $1 => DocumentMask.fromJson($1), + }, + mask: switch (json['mask']) { + null => null, + Object $1 => DocumentMask.fromJson($1), + }, + currentDocument: switch (json['currentDocument']) { + null => null, + Object $1 => Precondition.fromJson($1), + }, + ); + } + + @override + Object toJson() => { + if (document case final document?) 'document': document.toJson(), + if (updateMask case final updateMask?) 'updateMask': updateMask.toJson(), + if (mask case final mask?) 'mask': mask.toJson(), + if (currentDocument case final currentDocument?) + 'currentDocument': currentDocument.toJson(), + }; + + @override + String toString() => 'UpdateDocumentRequest()'; +} + +/// The request for +/// `Firestore.DeleteDocument`. +final class DeleteDocumentRequest extends ProtoMessage { + static const String fullyQualifiedName = + 'google.firestore.v1.DeleteDocumentRequest'; + + /// Required. The resource name of the Document to delete. In the format: + /// `projects/{project_id}/databases/{database_id}/documents/{document_path}`. + final String name; + + /// An optional precondition on the document. + /// The request will fail if this is set and not met by the target document. + final Precondition? currentDocument; + + DeleteDocumentRequest({required this.name, this.currentDocument}) + : super(fullyQualifiedName); + + factory DeleteDocumentRequest.fromJson(Object? j) { + final json = j as Map; + return DeleteDocumentRequest( + name: switch (json['name']) { + null => '', + Object $1 => decodeString($1), + }, + currentDocument: switch (json['currentDocument']) { + null => null, + Object $1 => Precondition.fromJson($1), + }, + ); + } + + @override + Object toJson() => { + 'name': name, + if (currentDocument case final currentDocument?) + 'currentDocument': currentDocument.toJson(), + }; + + @override + String toString() { + final $contents = ['name=$name'].join(','); + return 'DeleteDocumentRequest(${$contents})'; + } +} + +/// The request for +/// `Firestore.BatchGetDocuments`. +final class BatchGetDocumentsRequest extends ProtoMessage { + static const String fullyQualifiedName = + 'google.firestore.v1.BatchGetDocumentsRequest'; + + /// Required. The database name. In the format: + /// `projects/{project_id}/databases/{database_id}`. + final String database; + + /// The names of the documents to retrieve. In the format: + /// `projects/{project_id}/databases/{database_id}/documents/{document_path}`. + /// The request will fail if any of the document is not a child resource of the + /// given `database`. Duplicate names will be elided. + final List documents; + + /// The fields to return. If not set, returns all fields. + /// + /// If a document has a field that is not present in this mask, that field will + /// not be returned in the response. + final DocumentMask? mask; + + /// Reads documents in a transaction. + final Uint8List? transaction; + + /// Starts a new transaction and reads the documents. + /// Defaults to a read-only transaction. + /// The new transaction ID will be returned as the first response in the + /// stream. + final TransactionOptions? newTransaction; + + /// Reads documents as they were at the given time. + /// + /// This must be a microsecond precision timestamp within the past one hour, + /// or if Point-in-Time Recovery is enabled, can additionally be a whole + /// minute timestamp within the past 7 days. + final Timestamp? readTime; + + BatchGetDocumentsRequest({ + required this.database, + this.documents = const [], + this.mask, + this.transaction, + this.newTransaction, + this.readTime, + }) : super(fullyQualifiedName); + + factory BatchGetDocumentsRequest.fromJson(Object? j) { + final json = j as Map; + return BatchGetDocumentsRequest( + database: switch (json['database']) { + null => '', + Object $1 => decodeString($1), + }, + documents: switch (json['documents']) { + null => [], + List $1 => [for (final i in $1) decodeString(i)], + _ => throw const FormatException('"documents" is not a list'), + }, + mask: switch (json['mask']) { + null => null, + Object $1 => DocumentMask.fromJson($1), + }, + transaction: switch (json['transaction']) { + null => null, + Object $1 => decodeBytes($1), + }, + newTransaction: switch (json['newTransaction']) { + null => null, + Object $1 => TransactionOptions.fromJson($1), + }, + readTime: switch (json['readTime']) { + null => null, + Object $1 => Timestamp.fromJson($1), + }, + ); + } + + @override + Object toJson() => { + 'database': database, + if (documents.isNotDefault) 'documents': documents, + if (mask case final mask?) 'mask': mask.toJson(), + if (transaction case final transaction?) + 'transaction': encodeBytes(transaction), + if (newTransaction case final newTransaction?) + 'newTransaction': newTransaction.toJson(), + if (readTime case final readTime?) 'readTime': readTime.toJson(), + }; + + @override + String toString() { + final $contents = [ + 'database=$database', + if (transaction != null) 'transaction=$transaction', + ].join(','); + return 'BatchGetDocumentsRequest(${$contents})'; + } +} + +/// The streamed response for +/// `Firestore.BatchGetDocuments`. +final class BatchGetDocumentsResponse extends ProtoMessage { + static const String fullyQualifiedName = + 'google.firestore.v1.BatchGetDocumentsResponse'; + + /// A document that was requested. + final Document? found; + + /// A document name that was requested but does not exist. In the format: + /// `projects/{project_id}/databases/{database_id}/documents/{document_path}`. + final String? missing; + + /// The transaction that was started as part of this request. + /// Will only be set in the first response, and only if + /// `BatchGetDocumentsRequest.new_transaction` + /// was set in the request. + final Uint8List transaction; + + /// The time at which the document was read. + /// This may be monotically increasing, in this case the previous documents in + /// the result stream are guaranteed not to have changed between their + /// read_time and this one. + final Timestamp? readTime; + + BatchGetDocumentsResponse({ + this.found, + this.missing, + Uint8List? transaction, + this.readTime, + }) : transaction = transaction ?? Uint8List(0), + super(fullyQualifiedName); + + factory BatchGetDocumentsResponse.fromJson(Object? j) { + final json = j as Map; + return BatchGetDocumentsResponse( + found: switch (json['found']) { + null => null, + Object $1 => Document.fromJson($1), + }, + missing: switch (json['missing']) { + null => null, + Object $1 => decodeString($1), + }, + transaction: switch (json['transaction']) { + null => Uint8List(0), + Object $1 => decodeBytes($1), + }, + readTime: switch (json['readTime']) { + null => null, + Object $1 => Timestamp.fromJson($1), + }, + ); + } + + @override + Object toJson() => { + if (found case final found?) 'found': found.toJson(), + if (missing case final missing?) 'missing': missing, + if (transaction.isNotDefault) 'transaction': encodeBytes(transaction), + if (readTime case final readTime?) 'readTime': readTime.toJson(), + }; + + @override + String toString() { + final $contents = [ + if (missing != null) 'missing=$missing', + 'transaction=$transaction', + ].join(','); + return 'BatchGetDocumentsResponse(${$contents})'; + } +} + +/// The request for +/// `Firestore.BeginTransaction`. +final class BeginTransactionRequest extends ProtoMessage { + static const String fullyQualifiedName = + 'google.firestore.v1.BeginTransactionRequest'; + + /// Required. The database name. In the format: + /// `projects/{project_id}/databases/{database_id}`. + final String database; + + /// The options for the transaction. + /// Defaults to a read-write transaction. + final TransactionOptions? options; + + BeginTransactionRequest({required this.database, this.options}) + : super(fullyQualifiedName); + + factory BeginTransactionRequest.fromJson(Object? j) { + final json = j as Map; + return BeginTransactionRequest( + database: switch (json['database']) { + null => '', + Object $1 => decodeString($1), + }, + options: switch (json['options']) { + null => null, + Object $1 => TransactionOptions.fromJson($1), + }, + ); + } + + @override + Object toJson() => { + 'database': database, + if (options case final options?) 'options': options.toJson(), + }; + + @override + String toString() { + final $contents = ['database=$database'].join(','); + return 'BeginTransactionRequest(${$contents})'; + } +} + +/// The response for +/// `Firestore.BeginTransaction`. +final class BeginTransactionResponse extends ProtoMessage { + static const String fullyQualifiedName = + 'google.firestore.v1.BeginTransactionResponse'; + + /// The transaction that was started. + final Uint8List transaction; + + BeginTransactionResponse({Uint8List? transaction}) + : transaction = transaction ?? Uint8List(0), + super(fullyQualifiedName); + + factory BeginTransactionResponse.fromJson(Object? j) { + final json = j as Map; + return BeginTransactionResponse( + transaction: switch (json['transaction']) { + null => Uint8List(0), + Object $1 => decodeBytes($1), + }, + ); + } + + @override + Object toJson() => { + if (transaction.isNotDefault) 'transaction': encodeBytes(transaction), + }; + + @override + String toString() { + final $contents = ['transaction=$transaction'].join(','); + return 'BeginTransactionResponse(${$contents})'; + } +} + +/// The request for `Firestore.Commit`. +final class CommitRequest extends ProtoMessage { + static const String fullyQualifiedName = 'google.firestore.v1.CommitRequest'; + + /// Required. The database name. In the format: + /// `projects/{project_id}/databases/{database_id}`. + final String database; + + /// The writes to apply. + /// + /// Always executed atomically and in order. + final List writes; + + /// If set, applies all writes in this transaction, and commits it. + final Uint8List transaction; + + CommitRequest({ + required this.database, + this.writes = const [], + Uint8List? transaction, + }) : transaction = transaction ?? Uint8List(0), + super(fullyQualifiedName); + + factory CommitRequest.fromJson(Object? j) { + final json = j as Map; + return CommitRequest( + database: switch (json['database']) { + null => '', + Object $1 => decodeString($1), + }, + writes: switch (json['writes']) { + null => [], + List $1 => [for (final i in $1) Write.fromJson(i)], + _ => throw const FormatException('"writes" is not a list'), + }, + transaction: switch (json['transaction']) { + null => Uint8List(0), + Object $1 => decodeBytes($1), + }, + ); + } + + @override + Object toJson() => { + 'database': database, + if (writes.isNotDefault) 'writes': [for (final i in writes) i.toJson()], + if (transaction.isNotDefault) 'transaction': encodeBytes(transaction), + }; + + @override + String toString() { + final $contents = [ + 'database=$database', + 'transaction=$transaction', + ].join(','); + return 'CommitRequest(${$contents})'; + } +} + +/// The response for `Firestore.Commit`. +final class CommitResponse extends ProtoMessage { + static const String fullyQualifiedName = 'google.firestore.v1.CommitResponse'; + + /// The result of applying the writes. + /// + /// This i-th write result corresponds to the i-th write in the + /// request. + final List writeResults; + + /// The time at which the commit occurred. Any read with an equal or greater + /// `read_time` is guaranteed to see the effects of the commit. + final Timestamp? commitTime; + + CommitResponse({this.writeResults = const [], this.commitTime}) + : super(fullyQualifiedName); + + factory CommitResponse.fromJson(Object? j) { + final json = j as Map; + return CommitResponse( + writeResults: switch (json['writeResults']) { + null => [], + List $1 => [for (final i in $1) WriteResult.fromJson(i)], + _ => throw const FormatException('"writeResults" is not a list'), + }, + commitTime: switch (json['commitTime']) { + null => null, + Object $1 => Timestamp.fromJson($1), + }, + ); + } + + @override + Object toJson() => { + if (writeResults.isNotDefault) + 'writeResults': [for (final i in writeResults) i.toJson()], + if (commitTime case final commitTime?) 'commitTime': commitTime.toJson(), + }; + + @override + String toString() => 'CommitResponse()'; +} + +/// The request for `Firestore.Rollback`. +final class RollbackRequest extends ProtoMessage { + static const String fullyQualifiedName = + 'google.firestore.v1.RollbackRequest'; + + /// Required. The database name. In the format: + /// `projects/{project_id}/databases/{database_id}`. + final String database; + + /// Required. The transaction to roll back. + final Uint8List transaction; + + RollbackRequest({required this.database, required this.transaction}) + : super(fullyQualifiedName); + + factory RollbackRequest.fromJson(Object? j) { + final json = j as Map; + return RollbackRequest( + database: switch (json['database']) { + null => '', + Object $1 => decodeString($1), + }, + transaction: switch (json['transaction']) { + null => Uint8List(0), + Object $1 => decodeBytes($1), + }, + ); + } + + @override + Object toJson() => { + 'database': database, + 'transaction': encodeBytes(transaction), + }; + + @override + String toString() { + final $contents = [ + 'database=$database', + 'transaction=$transaction', + ].join(','); + return 'RollbackRequest(${$contents})'; + } +} + +/// The request for `Firestore.RunQuery`. +final class RunQueryRequest extends ProtoMessage { + static const String fullyQualifiedName = + 'google.firestore.v1.RunQueryRequest'; + + /// Required. The parent resource name. In the format: + /// `projects/{project_id}/databases/{database_id}/documents` or + /// `projects/{project_id}/databases/{database_id}/documents/{document_path}`. + /// For example: + /// `projects/my-project/databases/my-database/documents` or + /// `projects/my-project/databases/my-database/documents/chatrooms/my-chatroom` + final String parent; + + /// A structured query. + final StructuredQuery? structuredQuery; + + /// Run the query within an already active transaction. + /// + /// The value here is the opaque transaction ID to execute the query in. + final Uint8List? transaction; + + /// Starts a new transaction and reads the documents. + /// Defaults to a read-only transaction. + /// The new transaction ID will be returned as the first response in the + /// stream. + final TransactionOptions? newTransaction; + + /// Reads documents as they were at the given time. + /// + /// This must be a microsecond precision timestamp within the past one hour, + /// or if Point-in-Time Recovery is enabled, can additionally be a whole + /// minute timestamp within the past 7 days. + final Timestamp? readTime; + + /// Optional. Explain options for the query. If set, additional query + /// statistics will be returned. If not, only query results will be returned. + final ExplainOptions? explainOptions; + + RunQueryRequest({ + required this.parent, + this.structuredQuery, + this.transaction, + this.newTransaction, + this.readTime, + this.explainOptions, + }) : super(fullyQualifiedName); + + factory RunQueryRequest.fromJson(Object? j) { + final json = j as Map; + return RunQueryRequest( + parent: switch (json['parent']) { + null => '', + Object $1 => decodeString($1), + }, + structuredQuery: switch (json['structuredQuery']) { + null => null, + Object $1 => StructuredQuery.fromJson($1), + }, + transaction: switch (json['transaction']) { + null => null, + Object $1 => decodeBytes($1), + }, + newTransaction: switch (json['newTransaction']) { + null => null, + Object $1 => TransactionOptions.fromJson($1), + }, + readTime: switch (json['readTime']) { + null => null, + Object $1 => Timestamp.fromJson($1), + }, + explainOptions: switch (json['explainOptions']) { + null => null, + Object $1 => ExplainOptions.fromJson($1), + }, + ); + } + + @override + Object toJson() => { + 'parent': parent, + if (structuredQuery case final structuredQuery?) + 'structuredQuery': structuredQuery.toJson(), + if (transaction case final transaction?) + 'transaction': encodeBytes(transaction), + if (newTransaction case final newTransaction?) + 'newTransaction': newTransaction.toJson(), + if (readTime case final readTime?) 'readTime': readTime.toJson(), + if (explainOptions case final explainOptions?) + 'explainOptions': explainOptions.toJson(), + }; + + @override + String toString() { + final $contents = [ + 'parent=$parent', + if (transaction != null) 'transaction=$transaction', + ].join(','); + return 'RunQueryRequest(${$contents})'; + } +} + +/// The response for +/// `Firestore.RunQuery`. +final class RunQueryResponse extends ProtoMessage { + static const String fullyQualifiedName = + 'google.firestore.v1.RunQueryResponse'; + + /// The transaction that was started as part of this request. + /// Can only be set in the first response, and only if + /// `RunQueryRequest.new_transaction` + /// was set in the request. If set, no other fields will be set in this + /// response. + final Uint8List transaction; + + /// A query result, not set when reporting partial progress. + final Document? document; + + /// The time at which the document was read. This may be monotonically + /// increasing; in this case, the previous documents in the result stream are + /// guaranteed not to have changed between their `read_time` and this one. + /// + /// If the query returns no results, a response with `read_time` and no + /// `document` will be sent, and this represents the time at which the query + /// was run. + final Timestamp? readTime; + + /// The number of results that have been skipped due to an offset between + /// the last response and the current response. + final int skippedResults; + + /// If present, Firestore has completely finished the request and no more + /// documents will be returned. + final bool? done; + + /// Query explain metrics. This is only present when the + /// `RunQueryRequest.explain_options` + /// is provided, and it is sent only once with the last response in the stream. + final ExplainMetrics? explainMetrics; + + RunQueryResponse({ + Uint8List? transaction, + this.document, + this.readTime, + this.skippedResults = 0, + this.done, + this.explainMetrics, + }) : transaction = transaction ?? Uint8List(0), + super(fullyQualifiedName); + + factory RunQueryResponse.fromJson(Object? j) { + final json = j as Map; + return RunQueryResponse( + transaction: switch (json['transaction']) { + null => Uint8List(0), + Object $1 => decodeBytes($1), + }, + document: switch (json['document']) { + null => null, + Object $1 => Document.fromJson($1), + }, + readTime: switch (json['readTime']) { + null => null, + Object $1 => Timestamp.fromJson($1), + }, + skippedResults: switch (json['skippedResults']) { + null => 0, + Object $1 => decodeInt($1), + }, + done: switch (json['done']) { + null => null, + Object $1 => decodeBool($1), + }, + explainMetrics: switch (json['explainMetrics']) { + null => null, + Object $1 => ExplainMetrics.fromJson($1), + }, + ); + } + + @override + Object toJson() => { + if (transaction.isNotDefault) 'transaction': encodeBytes(transaction), + if (document case final document?) 'document': document.toJson(), + if (readTime case final readTime?) 'readTime': readTime.toJson(), + if (skippedResults.isNotDefault) 'skippedResults': skippedResults, + if (done case final done?) 'done': done, + if (explainMetrics case final explainMetrics?) + 'explainMetrics': explainMetrics.toJson(), + }; + + @override + String toString() { + final $contents = [ + 'transaction=$transaction', + 'skippedResults=$skippedResults', + if (done != null) 'done=$done', + ].join(','); + return 'RunQueryResponse(${$contents})'; + } +} + +/// The request for +/// `Firestore.ExecutePipeline`. +final class ExecutePipelineRequest extends ProtoMessage { + static const String fullyQualifiedName = + 'google.firestore.v1.ExecutePipelineRequest'; + + /// Required. Database identifier, in the form + /// `projects/{project}/databases/{database}`. + final String database; + + /// A pipelined operation. + final StructuredPipeline? structuredPipeline; + + /// Run the query within an already active transaction. + /// + /// The value here is the opaque transaction ID to execute the query in. + final Uint8List? transaction; + + /// Execute the pipeline in a new transaction. + /// + /// The identifier of the newly created transaction will be returned in the + /// first response on the stream. This defaults to a read-only transaction. + final TransactionOptions? newTransaction; + + /// Execute the pipeline in a snapshot transaction at the given time. + /// + /// This must be a microsecond precision timestamp within the past one hour, + /// or if Point-in-Time Recovery is enabled, can additionally be a whole + /// minute timestamp within the past 7 days. + final Timestamp? readTime; + + ExecutePipelineRequest({ + required this.database, + this.structuredPipeline, + this.transaction, + this.newTransaction, + this.readTime, + }) : super(fullyQualifiedName); + + factory ExecutePipelineRequest.fromJson(Object? j) { + final json = j as Map; + return ExecutePipelineRequest( + database: switch (json['database']) { + null => '', + Object $1 => decodeString($1), + }, + structuredPipeline: switch (json['structuredPipeline']) { + null => null, + Object $1 => StructuredPipeline.fromJson($1), + }, + transaction: switch (json['transaction']) { + null => null, + Object $1 => decodeBytes($1), + }, + newTransaction: switch (json['newTransaction']) { + null => null, + Object $1 => TransactionOptions.fromJson($1), + }, + readTime: switch (json['readTime']) { + null => null, + Object $1 => Timestamp.fromJson($1), + }, + ); + } + + @override + Object toJson() => { + 'database': database, + if (structuredPipeline case final structuredPipeline?) + 'structuredPipeline': structuredPipeline.toJson(), + if (transaction case final transaction?) + 'transaction': encodeBytes(transaction), + if (newTransaction case final newTransaction?) + 'newTransaction': newTransaction.toJson(), + if (readTime case final readTime?) 'readTime': readTime.toJson(), + }; + + @override + String toString() { + final $contents = [ + 'database=$database', + if (transaction != null) 'transaction=$transaction', + ].join(','); + return 'ExecutePipelineRequest(${$contents})'; + } +} + +/// The response for `Firestore.Execute`. +final class ExecutePipelineResponse extends ProtoMessage { + static const String fullyQualifiedName = + 'google.firestore.v1.ExecutePipelineResponse'; + + /// Newly created transaction identifier. + /// + /// This field is only specified as part of the first response from the server, + /// alongside the `results` field when the original request specified + /// `ExecuteRequest.new_transaction`. + final Uint8List transaction; + + /// An ordered batch of results returned executing a pipeline. + /// + /// The batch size is variable, and can even be zero for when only a partial + /// progress message is returned. + /// + /// The fields present in the returned documents are only those that were + /// explicitly requested in the pipeline, this includes those like + /// [`__name__`][google.firestore.v1.Document.name] and + /// [`__update_time__`][google.firestore.v1.Document.update_time]. This is + /// explicitly a divergence from `Firestore.RunQuery` / `Firestore.GetDocument` + /// RPCs which always return such fields even when they are not specified in + /// the [`mask`][google.firestore.v1.DocumentMask]. + final List results; + + /// The time at which the results are valid. + /// + /// This is a (not strictly) monotonically increasing value across multiple + /// responses in the same stream. The API guarantees that all previously + /// returned results are still valid at the latest `execution_time`. This + /// allows the API consumer to treat the query if it ran at the latest + /// `execution_time` returned. + /// + /// If the query returns no results, a response with `execution_time` and no + /// `results` will be sent, and this represents the time at which the operation + /// was run. + final Timestamp? executionTime; + + /// Query explain stats. + /// + /// This is present on the **last** response if the request configured explain + /// to run in 'analyze' or 'explain' mode in the pipeline options. If the query + /// does not return any results, a response with `explain_stats` and no + /// `results` will still be sent. + final ExplainStats? explainStats; + + ExecutePipelineResponse({ + Uint8List? transaction, + this.results = const [], + this.executionTime, + this.explainStats, + }) : transaction = transaction ?? Uint8List(0), + super(fullyQualifiedName); + + factory ExecutePipelineResponse.fromJson(Object? j) { + final json = j as Map; + return ExecutePipelineResponse( + transaction: switch (json['transaction']) { + null => Uint8List(0), + Object $1 => decodeBytes($1), + }, + results: switch (json['results']) { + null => [], + List $1 => [for (final i in $1) Document.fromJson(i)], + _ => throw const FormatException('"results" is not a list'), + }, + executionTime: switch (json['executionTime']) { + null => null, + Object $1 => Timestamp.fromJson($1), + }, + explainStats: switch (json['explainStats']) { + null => null, + Object $1 => ExplainStats.fromJson($1), + }, + ); + } + + @override + Object toJson() => { + if (transaction.isNotDefault) 'transaction': encodeBytes(transaction), + if (results.isNotDefault) 'results': [for (final i in results) i.toJson()], + if (executionTime case final executionTime?) + 'executionTime': executionTime.toJson(), + if (explainStats case final explainStats?) + 'explainStats': explainStats.toJson(), + }; + + @override + String toString() { + final $contents = ['transaction=$transaction'].join(','); + return 'ExecutePipelineResponse(${$contents})'; + } +} + +/// The request for +/// `Firestore.RunAggregationQuery`. +final class RunAggregationQueryRequest extends ProtoMessage { + static const String fullyQualifiedName = + 'google.firestore.v1.RunAggregationQueryRequest'; + + /// Required. The parent resource name. In the format: + /// `projects/{project_id}/databases/{database_id}/documents` or + /// `projects/{project_id}/databases/{database_id}/documents/{document_path}`. + /// For example: + /// `projects/my-project/databases/my-database/documents` or + /// `projects/my-project/databases/my-database/documents/chatrooms/my-chatroom` + final String parent; + + /// An aggregation query. + final StructuredAggregationQuery? structuredAggregationQuery; + + /// Run the aggregation within an already active transaction. + /// + /// The value here is the opaque transaction ID to execute the query in. + final Uint8List? transaction; + + /// Starts a new transaction as part of the query, defaulting to read-only. + /// + /// The new transaction ID will be returned as the first response in the + /// stream. + final TransactionOptions? newTransaction; + + /// Executes the query at the given timestamp. + /// + /// This must be a microsecond precision timestamp within the past one hour, + /// or if Point-in-Time Recovery is enabled, can additionally be a whole + /// minute timestamp within the past 7 days. + final Timestamp? readTime; + + /// Optional. Explain options for the query. If set, additional query + /// statistics will be returned. If not, only query results will be returned. + final ExplainOptions? explainOptions; + + RunAggregationQueryRequest({ + required this.parent, + this.structuredAggregationQuery, + this.transaction, + this.newTransaction, + this.readTime, + this.explainOptions, + }) : super(fullyQualifiedName); + + factory RunAggregationQueryRequest.fromJson(Object? j) { + final json = j as Map; + return RunAggregationQueryRequest( + parent: switch (json['parent']) { + null => '', + Object $1 => decodeString($1), + }, + structuredAggregationQuery: switch (json['structuredAggregationQuery']) { + null => null, + Object $1 => StructuredAggregationQuery.fromJson($1), + }, + transaction: switch (json['transaction']) { + null => null, + Object $1 => decodeBytes($1), + }, + newTransaction: switch (json['newTransaction']) { + null => null, + Object $1 => TransactionOptions.fromJson($1), + }, + readTime: switch (json['readTime']) { + null => null, + Object $1 => Timestamp.fromJson($1), + }, + explainOptions: switch (json['explainOptions']) { + null => null, + Object $1 => ExplainOptions.fromJson($1), + }, + ); + } + + @override + Object toJson() => { + 'parent': parent, + if (structuredAggregationQuery case final structuredAggregationQuery?) + 'structuredAggregationQuery': structuredAggregationQuery.toJson(), + if (transaction case final transaction?) + 'transaction': encodeBytes(transaction), + if (newTransaction case final newTransaction?) + 'newTransaction': newTransaction.toJson(), + if (readTime case final readTime?) 'readTime': readTime.toJson(), + if (explainOptions case final explainOptions?) + 'explainOptions': explainOptions.toJson(), + }; + + @override + String toString() { + final $contents = [ + 'parent=$parent', + if (transaction != null) 'transaction=$transaction', + ].join(','); + return 'RunAggregationQueryRequest(${$contents})'; + } +} + +/// The response for +/// `Firestore.RunAggregationQuery`. +final class RunAggregationQueryResponse extends ProtoMessage { + static const String fullyQualifiedName = + 'google.firestore.v1.RunAggregationQueryResponse'; + + /// A single aggregation result. + /// + /// Not present when reporting partial progress. + final AggregationResult? result; + + /// The transaction that was started as part of this request. + /// + /// Only present on the first response when the request requested to start + /// a new transaction. + final Uint8List transaction; + + /// The time at which the aggregate result was computed. This is always + /// monotonically increasing; in this case, the previous AggregationResult in + /// the result stream are guaranteed not to have changed between their + /// `read_time` and this one. + /// + /// If the query returns no results, a response with `read_time` and no + /// `result` will be sent, and this represents the time at which the query + /// was run. + final Timestamp? readTime; + + /// Query explain metrics. This is only present when the + /// `RunAggregationQueryRequest.explain_options` + /// is provided, and it is sent only once with the last response in the stream. + final ExplainMetrics? explainMetrics; + + RunAggregationQueryResponse({ + this.result, + Uint8List? transaction, + this.readTime, + this.explainMetrics, + }) : transaction = transaction ?? Uint8List(0), + super(fullyQualifiedName); + + factory RunAggregationQueryResponse.fromJson(Object? j) { + final json = j as Map; + return RunAggregationQueryResponse( + result: switch (json['result']) { + null => null, + Object $1 => AggregationResult.fromJson($1), + }, + transaction: switch (json['transaction']) { + null => Uint8List(0), + Object $1 => decodeBytes($1), + }, + readTime: switch (json['readTime']) { + null => null, + Object $1 => Timestamp.fromJson($1), + }, + explainMetrics: switch (json['explainMetrics']) { + null => null, + Object $1 => ExplainMetrics.fromJson($1), + }, + ); + } + + @override + Object toJson() => { + if (result case final result?) 'result': result.toJson(), + if (transaction.isNotDefault) 'transaction': encodeBytes(transaction), + if (readTime case final readTime?) 'readTime': readTime.toJson(), + if (explainMetrics case final explainMetrics?) + 'explainMetrics': explainMetrics.toJson(), + }; + + @override + String toString() { + final $contents = ['transaction=$transaction'].join(','); + return 'RunAggregationQueryResponse(${$contents})'; + } +} + +/// The request for +/// `Firestore.PartitionQuery`. +final class PartitionQueryRequest extends ProtoMessage { + static const String fullyQualifiedName = + 'google.firestore.v1.PartitionQueryRequest'; + + /// Required. The parent resource name. In the format: + /// `projects/{project_id}/databases/{database_id}/documents`. + /// Document resource names are not supported; only database resource names + /// can be specified. + final String parent; + + /// A structured query. + /// Query must specify collection with all descendants and be ordered by name + /// ascending. Other filters, order bys, limits, offsets, and start/end + /// cursors are not supported. + final StructuredQuery? structuredQuery; + + /// The desired maximum number of partition points. + /// The partitions may be returned across multiple pages of results. + /// The number must be positive. The actual number of partitions + /// returned may be fewer. + /// + /// For example, this may be set to one fewer than the number of parallel + /// queries to be run, or in running a data pipeline job, one fewer than the + /// number of workers or compute instances available. + final int partitionCount; + + /// The `next_page_token` value returned from a previous call to + /// PartitionQuery that may be used to get an additional set of results. + /// There are no ordering guarantees between sets of results. Thus, using + /// multiple sets of results will require merging the different result sets. + /// + /// For example, two subsequent calls using a page_token may return: + /// + /// * cursor B, cursor M, cursor Q + /// * cursor A, cursor U, cursor W + /// + /// To obtain a complete result set ordered with respect to the results of the + /// query supplied to PartitionQuery, the results sets should be merged: + /// cursor A, cursor B, cursor M, cursor Q, cursor U, cursor W + final String pageToken; + + /// The maximum number of partitions to return in this call, subject to + /// `partition_count`. + /// + /// For example, if `partition_count` = 10 and `page_size` = 8, the first call + /// to PartitionQuery will return up to 8 partitions and a `next_page_token` + /// if more results exist. A second call to PartitionQuery will return up to + /// 2 partitions, to complete the total of 10 specified in `partition_count`. + final int pageSize; + + /// Reads documents as they were at the given time. + /// + /// This must be a microsecond precision timestamp within the past one hour, + /// or if Point-in-Time Recovery is enabled, can additionally be a whole + /// minute timestamp within the past 7 days. + final Timestamp? readTime; + + PartitionQueryRequest({ + required this.parent, + this.structuredQuery, + this.partitionCount = 0, + this.pageToken = '', + this.pageSize = 0, + this.readTime, + }) : super(fullyQualifiedName); + + factory PartitionQueryRequest.fromJson(Object? j) { + final json = j as Map; + return PartitionQueryRequest( + parent: switch (json['parent']) { + null => '', + Object $1 => decodeString($1), + }, + structuredQuery: switch (json['structuredQuery']) { + null => null, + Object $1 => StructuredQuery.fromJson($1), + }, + partitionCount: switch (json['partitionCount']) { + null => 0, + Object $1 => decodeInt64($1), + }, + pageToken: switch (json['pageToken']) { + null => '', + Object $1 => decodeString($1), + }, + pageSize: switch (json['pageSize']) { + null => 0, + Object $1 => decodeInt($1), + }, + readTime: switch (json['readTime']) { + null => null, + Object $1 => Timestamp.fromJson($1), + }, + ); + } + + @override + Object toJson() => { + 'parent': parent, + if (structuredQuery case final structuredQuery?) + 'structuredQuery': structuredQuery.toJson(), + if (partitionCount.isNotDefault) + 'partitionCount': partitionCount.toString(), + if (pageToken.isNotDefault) 'pageToken': pageToken, + if (pageSize.isNotDefault) 'pageSize': pageSize, + if (readTime case final readTime?) 'readTime': readTime.toJson(), + }; + + @override + String toString() { + final $contents = [ + 'parent=$parent', + 'partitionCount=$partitionCount', + 'pageToken=$pageToken', + 'pageSize=$pageSize', + ].join(','); + return 'PartitionQueryRequest(${$contents})'; + } +} + +/// The response for +/// `Firestore.PartitionQuery`. +final class PartitionQueryResponse extends ProtoMessage { + static const String fullyQualifiedName = + 'google.firestore.v1.PartitionQueryResponse'; + + /// Partition results. + /// Each partition is a split point that can be used by RunQuery as a starting + /// or end point for the query results. The RunQuery requests must be made with + /// the same query supplied to this PartitionQuery request. The partition + /// cursors will be ordered according to same ordering as the results of the + /// query supplied to PartitionQuery. + /// + /// For example, if a PartitionQuery request returns partition cursors A and B, + /// running the following three queries will return the entire result set of + /// the original query: + /// + /// * query, end_at A + /// * query, start_at A, end_at B + /// * query, start_at B + /// + /// An empty result may indicate that the query has too few results to be + /// partitioned, or that the query is not yet supported for partitioning. + final List partitions; + + /// A page token that may be used to request an additional set of results, up + /// to the number specified by `partition_count` in the PartitionQuery request. + /// If blank, there are no more results. + final String nextPageToken; + + PartitionQueryResponse({this.partitions = const [], this.nextPageToken = ''}) + : super(fullyQualifiedName); + + factory PartitionQueryResponse.fromJson(Object? j) { + final json = j as Map; + return PartitionQueryResponse( + partitions: switch (json['partitions']) { + null => [], + List $1 => [for (final i in $1) Cursor.fromJson(i)], + _ => throw const FormatException('"partitions" is not a list'), + }, + nextPageToken: switch (json['nextPageToken']) { + null => '', + Object $1 => decodeString($1), + }, + ); + } + + @override + Object toJson() => { + if (partitions.isNotDefault) + 'partitions': [for (final i in partitions) i.toJson()], + if (nextPageToken.isNotDefault) 'nextPageToken': nextPageToken, + }; + + @override + String toString() { + final $contents = ['nextPageToken=$nextPageToken'].join(','); + return 'PartitionQueryResponse(${$contents})'; + } +} + +/// The request for `Firestore.Write`. +/// +/// The first request creates a stream, or resumes an existing one from a token. +/// +/// When creating a new stream, the server replies with a response containing +/// only an ID and a token, to use in the next request. +/// +/// When resuming a stream, the server first streams any responses later than the +/// given token, then a response containing only an up-to-date token, to use in +/// the next request. +final class WriteRequest extends ProtoMessage { + static const String fullyQualifiedName = 'google.firestore.v1.WriteRequest'; + + /// Required. The database name. In the format: + /// `projects/{project_id}/databases/{database_id}`. + /// This is only required in the first message. + final String database; + + /// The ID of the write stream to resume. + /// This may only be set in the first message. When left empty, a new write + /// stream will be created. + final String streamId; + + /// The writes to apply. + /// + /// Always executed atomically and in order. + /// This must be empty on the first request. + /// This may be empty on the last request. + /// This must not be empty on all other requests. + final List writes; + + /// A stream token that was previously sent by the server. + /// + /// The client should set this field to the token from the most recent + /// `WriteResponse` it has received. This + /// acknowledges that the client has received responses up to this token. After + /// sending this token, earlier tokens may not be used anymore. + /// + /// The server may close the stream if there are too many unacknowledged + /// responses. + /// + /// Leave this field unset when creating a new stream. To resume a stream at + /// a specific point, set this field and the `stream_id` field. + /// + /// Leave this field unset when creating a new stream. + final Uint8List streamToken; + + /// Labels associated with this write request. + final Map labels; + + WriteRequest({ + required this.database, + this.streamId = '', + this.writes = const [], + Uint8List? streamToken, + this.labels = const {}, + }) : streamToken = streamToken ?? Uint8List(0), + super(fullyQualifiedName); + + factory WriteRequest.fromJson(Object? j) { + final json = j as Map; + return WriteRequest( + database: switch (json['database']) { + null => '', + Object $1 => decodeString($1), + }, + streamId: switch (json['streamId']) { + null => '', + Object $1 => decodeString($1), + }, + writes: switch (json['writes']) { + null => [], + List $1 => [for (final i in $1) Write.fromJson(i)], + _ => throw const FormatException('"writes" is not a list'), + }, + streamToken: switch (json['streamToken']) { + null => Uint8List(0), + Object $1 => decodeBytes($1), + }, + labels: switch (json['labels']) { + null => {}, + Map $1 => { + for (final e in $1.entries) + decodeString(e.key): decodeString(e.value), + }, + _ => throw const FormatException('"labels" is not an object'), + }, + ); + } + + @override + Object toJson() => { + 'database': database, + if (streamId.isNotDefault) 'streamId': streamId, + if (writes.isNotDefault) 'writes': [for (final i in writes) i.toJson()], + if (streamToken.isNotDefault) 'streamToken': encodeBytes(streamToken), + if (labels.isNotDefault) 'labels': labels, + }; + + @override + String toString() { + final $contents = [ + 'database=$database', + 'streamId=$streamId', + 'streamToken=$streamToken', + ].join(','); + return 'WriteRequest(${$contents})'; + } +} + +/// The response for `Firestore.Write`. +final class WriteResponse extends ProtoMessage { + static const String fullyQualifiedName = 'google.firestore.v1.WriteResponse'; + + /// The ID of the stream. + /// Only set on the first message, when a new stream was created. + final String streamId; + + /// A token that represents the position of this response in the stream. + /// This can be used by a client to resume the stream at this point. + /// + /// This field is always set. + final Uint8List streamToken; + + /// The result of applying the writes. + /// + /// This i-th write result corresponds to the i-th write in the + /// request. + final List writeResults; + + /// The time at which the commit occurred. Any read with an equal or greater + /// `read_time` is guaranteed to see the effects of the write. + final Timestamp? commitTime; + + WriteResponse({ + this.streamId = '', + Uint8List? streamToken, + this.writeResults = const [], + this.commitTime, + }) : streamToken = streamToken ?? Uint8List(0), + super(fullyQualifiedName); + + factory WriteResponse.fromJson(Object? j) { + final json = j as Map; + return WriteResponse( + streamId: switch (json['streamId']) { + null => '', + Object $1 => decodeString($1), + }, + streamToken: switch (json['streamToken']) { + null => Uint8List(0), + Object $1 => decodeBytes($1), + }, + writeResults: switch (json['writeResults']) { + null => [], + List $1 => [for (final i in $1) WriteResult.fromJson(i)], + _ => throw const FormatException('"writeResults" is not a list'), + }, + commitTime: switch (json['commitTime']) { + null => null, + Object $1 => Timestamp.fromJson($1), + }, + ); + } + + @override + Object toJson() => { + if (streamId.isNotDefault) 'streamId': streamId, + if (streamToken.isNotDefault) 'streamToken': encodeBytes(streamToken), + if (writeResults.isNotDefault) + 'writeResults': [for (final i in writeResults) i.toJson()], + if (commitTime case final commitTime?) 'commitTime': commitTime.toJson(), + }; + + @override + String toString() { + final $contents = [ + 'streamId=$streamId', + 'streamToken=$streamToken', + ].join(','); + return 'WriteResponse(${$contents})'; + } +} + +/// A request for `Firestore.Listen` +final class ListenRequest extends ProtoMessage { + static const String fullyQualifiedName = 'google.firestore.v1.ListenRequest'; + + /// Required. The database name. In the format: + /// `projects/{project_id}/databases/{database_id}`. + final String database; + + /// A target to add to this stream. + final Target? addTarget; + + /// The ID of a target to remove from this stream. + final int? removeTarget; + + /// Labels associated with this target change. + final Map labels; + + ListenRequest({ + required this.database, + this.addTarget, + this.removeTarget, + this.labels = const {}, + }) : super(fullyQualifiedName); + + factory ListenRequest.fromJson(Object? j) { + final json = j as Map; + return ListenRequest( + database: switch (json['database']) { + null => '', + Object $1 => decodeString($1), + }, + addTarget: switch (json['addTarget']) { + null => null, + Object $1 => Target.fromJson($1), + }, + removeTarget: switch (json['removeTarget']) { + null => null, + Object $1 => decodeInt($1), + }, + labels: switch (json['labels']) { + null => {}, + Map $1 => { + for (final e in $1.entries) + decodeString(e.key): decodeString(e.value), + }, + _ => throw const FormatException('"labels" is not an object'), + }, + ); + } + + @override + Object toJson() => { + 'database': database, + if (addTarget case final addTarget?) 'addTarget': addTarget.toJson(), + if (removeTarget case final removeTarget?) 'removeTarget': removeTarget, + if (labels.isNotDefault) 'labels': labels, + }; + + @override + String toString() { + final $contents = [ + 'database=$database', + if (removeTarget != null) 'removeTarget=$removeTarget', + ].join(','); + return 'ListenRequest(${$contents})'; + } +} + +/// The response for `Firestore.Listen`. +final class ListenResponse extends ProtoMessage { + static const String fullyQualifiedName = 'google.firestore.v1.ListenResponse'; + + /// Targets have changed. + final TargetChange? targetChange; + + /// A `Document` has changed. + final DocumentChange? documentChange; + + /// A `Document` has been deleted. + final DocumentDelete? documentDelete; + + /// A `Document` has been removed from a target + /// (because it is no longer relevant to that target). + final DocumentRemove? documentRemove; + + /// A filter to apply to the set of documents previously returned for the + /// given target. + /// + /// Returned when documents may have been removed from the given target, but + /// the exact documents are unknown. + final ExistenceFilter? filter; + + ListenResponse({ + this.targetChange, + this.documentChange, + this.documentDelete, + this.documentRemove, + this.filter, + }) : super(fullyQualifiedName); + + factory ListenResponse.fromJson(Object? j) { + final json = j as Map; + return ListenResponse( + targetChange: switch (json['targetChange']) { + null => null, + Object $1 => TargetChange.fromJson($1), + }, + documentChange: switch (json['documentChange']) { + null => null, + Object $1 => DocumentChange.fromJson($1), + }, + documentDelete: switch (json['documentDelete']) { + null => null, + Object $1 => DocumentDelete.fromJson($1), + }, + documentRemove: switch (json['documentRemove']) { + null => null, + Object $1 => DocumentRemove.fromJson($1), + }, + filter: switch (json['filter']) { + null => null, + Object $1 => ExistenceFilter.fromJson($1), + }, + ); + } + + @override + Object toJson() => { + if (targetChange case final targetChange?) + 'targetChange': targetChange.toJson(), + if (documentChange case final documentChange?) + 'documentChange': documentChange.toJson(), + if (documentDelete case final documentDelete?) + 'documentDelete': documentDelete.toJson(), + if (documentRemove case final documentRemove?) + 'documentRemove': documentRemove.toJson(), + if (filter case final filter?) 'filter': filter.toJson(), + }; + + @override + String toString() => 'ListenResponse()'; +} + +/// A specification of a set of documents to listen to. +final class Target extends ProtoMessage { + static const String fullyQualifiedName = 'google.firestore.v1.Target'; + + /// A target specified by a query. + final Target_QueryTarget? query; + + /// A target specified by a set of document names. + final Target_DocumentsTarget? documents; + + /// A resume token from a prior + /// `TargetChange` for an identical target. + /// + /// Using a resume token with a different target is unsupported and may fail. + final Uint8List? resumeToken; + + /// Start listening after a specific `read_time`. + /// + /// The client must know the state of matching documents at this time. + final Timestamp? readTime; + + /// The target ID that identifies the target on the stream. Must be a positive + /// number and non-zero. + /// + /// If `target_id` is 0 (or unspecified), the server will assign an ID for this + /// target and return that in a `TargetChange::ADD` event. Once a target with + /// `target_id=0` is added, all subsequent targets must also have + /// `target_id=0`. If an `AddTarget` request with `target_id != 0` is + /// sent to the server after a target with `target_id=0` is added, the server + /// will immediately send a response with a `TargetChange::Remove` event. + /// + /// Note that if the client sends multiple `AddTarget` requests + /// without an ID, the order of IDs returned in `TargetChange.target_ids` are + /// undefined. Therefore, clients should provide a target ID instead of relying + /// on the server to assign one. + /// + /// If `target_id` is non-zero, there must not be an existing active target on + /// this stream with the same ID. + final int targetId; + + /// If the target should be removed once it is current and consistent. + final bool once; + + /// The number of documents that last matched the query at the resume token or + /// read time. + /// + /// This value is only relevant when a `resume_type` is provided. This value + /// being present and greater than zero signals that the client wants + /// `ExistenceFilter.unchanged_names` to be included in the response. + final Int32Value? expectedCount; + + Target({ + this.query, + this.documents, + this.resumeToken, + this.readTime, + this.targetId = 0, + this.once = false, + this.expectedCount, + }) : super(fullyQualifiedName); + + factory Target.fromJson(Object? j) { + final json = j as Map; + return Target( + query: switch (json['query']) { + null => null, + Object $1 => Target_QueryTarget.fromJson($1), + }, + documents: switch (json['documents']) { + null => null, + Object $1 => Target_DocumentsTarget.fromJson($1), + }, + resumeToken: switch (json['resumeToken']) { + null => null, + Object $1 => decodeBytes($1), + }, + readTime: switch (json['readTime']) { + null => null, + Object $1 => Timestamp.fromJson($1), + }, + targetId: switch (json['targetId']) { + null => 0, + Object $1 => decodeInt($1), + }, + once: switch (json['once']) { + null => false, + Object $1 => decodeBool($1), + }, + expectedCount: switch (json['expectedCount']) { + null => null, + Object $1 => Int32Value.fromJson($1), + }, + ); + } + + @override + Object toJson() => { + if (query case final query?) 'query': query.toJson(), + if (documents case final documents?) 'documents': documents.toJson(), + if (resumeToken case final resumeToken?) + 'resumeToken': encodeBytes(resumeToken), + if (readTime case final readTime?) 'readTime': readTime.toJson(), + if (targetId.isNotDefault) 'targetId': targetId, + if (once.isNotDefault) 'once': once, + if (expectedCount case final expectedCount?) + 'expectedCount': expectedCount.toJson(), + }; + + @override + String toString() { + final $contents = [ + if (resumeToken != null) 'resumeToken=$resumeToken', + 'targetId=$targetId', + 'once=$once', + ].join(','); + return 'Target(${$contents})'; + } +} + +/// A target specified by a set of documents names. +final class Target_DocumentsTarget extends ProtoMessage { + static const String fullyQualifiedName = + 'google.firestore.v1.Target.DocumentsTarget'; + + /// The names of the documents to retrieve. In the format: + /// `projects/{project_id}/databases/{database_id}/documents/{document_path}`. + /// The request will fail if any of the document is not a child resource of + /// the given `database`. Duplicate names will be elided. + final List documents; + + Target_DocumentsTarget({this.documents = const []}) + : super(fullyQualifiedName); + + factory Target_DocumentsTarget.fromJson(Object? j) { + final json = j as Map; + return Target_DocumentsTarget( + documents: switch (json['documents']) { + null => [], + List $1 => [for (final i in $1) decodeString(i)], + _ => throw const FormatException('"documents" is not a list'), + }, + ); + } + + @override + Object toJson() => {if (documents.isNotDefault) 'documents': documents}; + + @override + String toString() => 'DocumentsTarget()'; +} + +/// A target specified by a query. +final class Target_QueryTarget extends ProtoMessage { + static const String fullyQualifiedName = + 'google.firestore.v1.Target.QueryTarget'; + + /// The parent resource name. In the format: + /// `projects/{project_id}/databases/{database_id}/documents` or + /// `projects/{project_id}/databases/{database_id}/documents/{document_path}`. + /// For example: + /// `projects/my-project/databases/my-database/documents` or + /// `projects/my-project/databases/my-database/documents/chatrooms/my-chatroom` + final String parent; + + /// A structured query. + final StructuredQuery? structuredQuery; + + Target_QueryTarget({this.parent = '', this.structuredQuery}) + : super(fullyQualifiedName); + + factory Target_QueryTarget.fromJson(Object? j) { + final json = j as Map; + return Target_QueryTarget( + parent: switch (json['parent']) { + null => '', + Object $1 => decodeString($1), + }, + structuredQuery: switch (json['structuredQuery']) { + null => null, + Object $1 => StructuredQuery.fromJson($1), + }, + ); + } + + @override + Object toJson() => { + if (parent.isNotDefault) 'parent': parent, + if (structuredQuery case final structuredQuery?) + 'structuredQuery': structuredQuery.toJson(), + }; + + @override + String toString() { + final $contents = ['parent=$parent'].join(','); + return 'QueryTarget(${$contents})'; + } +} + +/// Targets being watched have changed. +final class TargetChange extends ProtoMessage { + static const String fullyQualifiedName = 'google.firestore.v1.TargetChange'; + + /// The type of change that occurred. + final TargetChange_TargetChangeType targetChangeType; + + /// The target IDs of targets that have changed. + /// + /// If empty, the change applies to all targets. + /// + /// The order of the target IDs is not defined. + final List targetIds; + + /// The error that resulted in this change, if applicable. + final Status? cause; + + /// A token that can be used to resume the stream for the given `target_ids`, + /// or all targets if `target_ids` is empty. + /// + /// Not set on every target change. + final Uint8List resumeToken; + + /// The consistent `read_time` for the given `target_ids` (omitted when the + /// target_ids are not at a consistent snapshot). + /// + /// The stream is guaranteed to send a `read_time` with `target_ids` empty + /// whenever the entire stream reaches a new consistent snapshot. ADD, + /// CURRENT, and RESET messages are guaranteed to (eventually) result in a + /// new consistent snapshot (while NO_CHANGE and REMOVE messages are not). + /// + /// For a given stream, `read_time` is guaranteed to be monotonically + /// increasing. + final Timestamp? readTime; + + TargetChange({ + this.targetChangeType = TargetChange_TargetChangeType.$default, + this.targetIds = const [], + this.cause, + Uint8List? resumeToken, + this.readTime, + }) : resumeToken = resumeToken ?? Uint8List(0), + super(fullyQualifiedName); + + factory TargetChange.fromJson(Object? j) { + final json = j as Map; + return TargetChange( + targetChangeType: switch (json['targetChangeType']) { + null => TargetChange_TargetChangeType.$default, + Object $1 => TargetChange_TargetChangeType.fromJson($1), + }, + targetIds: switch (json['targetIds']) { + null => [], + List $1 => [for (final i in $1) decodeInt(i)], + _ => throw const FormatException('"targetIds" is not a list'), + }, + cause: switch (json['cause']) { + null => null, + Object $1 => Status.fromJson($1), + }, + resumeToken: switch (json['resumeToken']) { + null => Uint8List(0), + Object $1 => decodeBytes($1), + }, + readTime: switch (json['readTime']) { + null => null, + Object $1 => Timestamp.fromJson($1), + }, + ); + } + + @override + Object toJson() => { + if (targetChangeType.isNotDefault) + 'targetChangeType': targetChangeType.toJson(), + if (targetIds.isNotDefault) 'targetIds': targetIds, + if (cause case final cause?) 'cause': cause.toJson(), + if (resumeToken.isNotDefault) 'resumeToken': encodeBytes(resumeToken), + if (readTime case final readTime?) 'readTime': readTime.toJson(), + }; + + @override + String toString() { + final $contents = [ + 'targetChangeType=$targetChangeType', + 'resumeToken=$resumeToken', + ].join(','); + return 'TargetChange(${$contents})'; + } +} + +/// The type of change. +final class TargetChange_TargetChangeType extends ProtoEnum { + /// No change has occurred. Used only to send an updated `resume_token`. + static const noChange = TargetChange_TargetChangeType('NO_CHANGE'); + + /// The targets have been added. + static const add = TargetChange_TargetChangeType('ADD'); + + /// The targets have been removed. + static const remove = TargetChange_TargetChangeType('REMOVE'); + + /// The targets reflect all changes committed before the targets were added + /// to the stream. + /// + /// This will be sent after or with a `read_time` that is greater than or + /// equal to the time at which the targets were added. + /// + /// Listeners can wait for this change if read-after-write semantics + /// are desired. + static const current = TargetChange_TargetChangeType('CURRENT'); + + /// The targets have been reset, and a new initial state for the targets + /// will be returned in subsequent changes. + /// + /// After the initial state is complete, `CURRENT` will be returned even + /// if the target was previously indicated to be `CURRENT`. + static const reset = TargetChange_TargetChangeType('RESET'); + + /// The default value for [TargetChange_TargetChangeType]. + static const $default = noChange; + + const TargetChange_TargetChangeType(super.value); + + factory TargetChange_TargetChangeType.fromJson(Object? json) => + TargetChange_TargetChangeType(json as String); + + bool get isNotDefault => this != $default; + + @override + String toString() => 'TargetChangeType.$value'; +} + +/// The request for +/// `Firestore.ListCollectionIds`. +final class ListCollectionIdsRequest extends ProtoMessage { + static const String fullyQualifiedName = + 'google.firestore.v1.ListCollectionIdsRequest'; + + /// Required. The parent document. In the format: + /// `projects/{project_id}/databases/{database_id}/documents/{document_path}`. + /// For example: + /// `projects/my-project/databases/my-database/documents/chatrooms/my-chatroom` + final String parent; + + /// The maximum number of results to return. + final int pageSize; + + /// A page token. Must be a value from + /// `ListCollectionIdsResponse`. + final String pageToken; + + /// Reads documents as they were at the given time. + /// + /// This must be a microsecond precision timestamp within the past one hour, + /// or if Point-in-Time Recovery is enabled, can additionally be a whole + /// minute timestamp within the past 7 days. + final Timestamp? readTime; + + ListCollectionIdsRequest({ + required this.parent, + this.pageSize = 0, + this.pageToken = '', + this.readTime, + }) : super(fullyQualifiedName); + + factory ListCollectionIdsRequest.fromJson(Object? j) { + final json = j as Map; + return ListCollectionIdsRequest( + parent: switch (json['parent']) { + null => '', + Object $1 => decodeString($1), + }, + pageSize: switch (json['pageSize']) { + null => 0, + Object $1 => decodeInt($1), + }, + pageToken: switch (json['pageToken']) { + null => '', + Object $1 => decodeString($1), + }, + readTime: switch (json['readTime']) { + null => null, + Object $1 => Timestamp.fromJson($1), + }, + ); + } + + @override + Object toJson() => { + 'parent': parent, + if (pageSize.isNotDefault) 'pageSize': pageSize, + if (pageToken.isNotDefault) 'pageToken': pageToken, + if (readTime case final readTime?) 'readTime': readTime.toJson(), + }; + + @override + String toString() { + final $contents = [ + 'parent=$parent', + 'pageSize=$pageSize', + 'pageToken=$pageToken', + ].join(','); + return 'ListCollectionIdsRequest(${$contents})'; + } +} + +/// The response from +/// `Firestore.ListCollectionIds`. +final class ListCollectionIdsResponse extends ProtoMessage { + static const String fullyQualifiedName = + 'google.firestore.v1.ListCollectionIdsResponse'; + + /// The collection ids. + final List collectionIds; + + /// A page token that may be used to continue the list. + final String nextPageToken; + + ListCollectionIdsResponse({ + this.collectionIds = const [], + this.nextPageToken = '', + }) : super(fullyQualifiedName); + + factory ListCollectionIdsResponse.fromJson(Object? j) { + final json = j as Map; + return ListCollectionIdsResponse( + collectionIds: switch (json['collectionIds']) { + null => [], + List $1 => [for (final i in $1) decodeString(i)], + _ => throw const FormatException('"collectionIds" is not a list'), + }, + nextPageToken: switch (json['nextPageToken']) { + null => '', + Object $1 => decodeString($1), + }, + ); + } + + @override + Object toJson() => { + if (collectionIds.isNotDefault) 'collectionIds': collectionIds, + if (nextPageToken.isNotDefault) 'nextPageToken': nextPageToken, + }; + + @override + String toString() { + final $contents = ['nextPageToken=$nextPageToken'].join(','); + return 'ListCollectionIdsResponse(${$contents})'; + } +} + +/// The request for +/// `Firestore.BatchWrite`. +final class BatchWriteRequest extends ProtoMessage { + static const String fullyQualifiedName = + 'google.firestore.v1.BatchWriteRequest'; + + /// Required. The database name. In the format: + /// `projects/{project_id}/databases/{database_id}`. + final String database; + + /// The writes to apply. + /// + /// Method does not apply writes atomically and does not guarantee ordering. + /// Each write succeeds or fails independently. You cannot write to the same + /// document more than once per request. + final List writes; + + /// Labels associated with this batch write. + final Map labels; + + BatchWriteRequest({ + required this.database, + this.writes = const [], + this.labels = const {}, + }) : super(fullyQualifiedName); + + factory BatchWriteRequest.fromJson(Object? j) { + final json = j as Map; + return BatchWriteRequest( + database: switch (json['database']) { + null => '', + Object $1 => decodeString($1), + }, + writes: switch (json['writes']) { + null => [], + List $1 => [for (final i in $1) Write.fromJson(i)], + _ => throw const FormatException('"writes" is not a list'), + }, + labels: switch (json['labels']) { + null => {}, + Map $1 => { + for (final e in $1.entries) + decodeString(e.key): decodeString(e.value), + }, + _ => throw const FormatException('"labels" is not an object'), + }, + ); + } + + @override + Object toJson() => { + 'database': database, + if (writes.isNotDefault) 'writes': [for (final i in writes) i.toJson()], + if (labels.isNotDefault) 'labels': labels, + }; + + @override + String toString() { + final $contents = ['database=$database'].join(','); + return 'BatchWriteRequest(${$contents})'; + } +} + +/// The response from +/// `Firestore.BatchWrite`. +final class BatchWriteResponse extends ProtoMessage { + static const String fullyQualifiedName = + 'google.firestore.v1.BatchWriteResponse'; + + /// The result of applying the writes. + /// + /// This i-th write result corresponds to the i-th write in the + /// request. + final List writeResults; + + /// The status of applying the writes. + /// + /// This i-th write status corresponds to the i-th write in the + /// request. + final List status; + + BatchWriteResponse({this.writeResults = const [], this.status = const []}) + : super(fullyQualifiedName); + + factory BatchWriteResponse.fromJson(Object? j) { + final json = j as Map; + return BatchWriteResponse( + writeResults: switch (json['writeResults']) { + null => [], + List $1 => [for (final i in $1) WriteResult.fromJson(i)], + _ => throw const FormatException('"writeResults" is not a list'), + }, + status: switch (json['status']) { + null => [], + List $1 => [for (final i in $1) Status.fromJson(i)], + _ => throw const FormatException('"status" is not a list'), + }, + ); + } + + @override + Object toJson() => { + if (writeResults.isNotDefault) + 'writeResults': [for (final i in writeResults) i.toJson()], + if (status.isNotDefault) 'status': [for (final i in status) i.toJson()], + }; + + @override + String toString() => 'BatchWriteResponse()'; +} + +/// A Firestore query represented as an ordered list of operations / stages. +/// +/// This is considered the top-level function which plans and executes a query. +/// It is logically equivalent to `query(stages, options)`, but prevents the +/// client from having to build a function wrapper. +final class StructuredPipeline extends ProtoMessage { + static const String fullyQualifiedName = + 'google.firestore.v1.StructuredPipeline'; + + /// Required. The pipeline query to execute. + final Pipeline? pipeline; + + /// Optional. Optional query-level arguments. + final Map options; + + StructuredPipeline({required this.pipeline, this.options = const {}}) + : super(fullyQualifiedName); + + factory StructuredPipeline.fromJson(Object? j) { + final json = j as Map; + return StructuredPipeline( + pipeline: switch (json['pipeline']) { + null => null, + Object $1 => Pipeline.fromJson($1), + }, + options: switch (json['options']) { + null => {}, + Map $1 => { + for (final e in $1.entries) + decodeString(e.key): Value.fromJson(e.value), + }, + _ => throw const FormatException('"options" is not an object'), + }, + ); + } + + @override + Object toJson() => { + if (pipeline case final pipeline?) 'pipeline': pipeline.toJson(), + if (options.isNotDefault) + 'options': {for (final e in options.entries) e.key: e.value.toJson()}, + }; + + @override + String toString() => 'StructuredPipeline()'; +} + +/// A Firestore query. +/// +/// The query stages are executed in the following order: +/// 1. from +/// 2. where +/// 3. select +/// 4. order_by + start_at + end_at +/// 5. offset +/// 6. limit +/// 7. find_nearest +final class StructuredQuery extends ProtoMessage { + static const String fullyQualifiedName = + 'google.firestore.v1.StructuredQuery'; + + /// Optional sub-set of the fields to return. + /// + /// This acts as a `DocumentMask` over the + /// documents returned from a query. When not set, assumes that the caller + /// wants all fields returned. + final StructuredQuery_Projection? select; + + /// The collections to query. + final List from; + + /// The filter to apply. + final StructuredQuery_Filter? where; + + /// The order to apply to the query results. + /// + /// Firestore allows callers to provide a full ordering, a partial ordering, or + /// no ordering at all. In all cases, Firestore guarantees a stable ordering + /// through the following rules: + /// + /// * The `order_by` is required to reference all fields used with an + /// inequality filter. + /// * All fields that are required to be in the `order_by` but are not already + /// present are appended in lexicographical ordering of the field name. + /// * If an order on `__name__` is not specified, it is appended by default. + /// + /// Fields are appended with the same sort direction as the last order + /// specified, or 'ASCENDING' if no order was specified. For example: + /// + /// * `ORDER BY a` becomes `ORDER BY a ASC, __name__ ASC` + /// * `ORDER BY a DESC` becomes `ORDER BY a DESC, __name__ DESC` + /// * `WHERE a > 1` becomes `WHERE a > 1 ORDER BY a ASC, __name__ ASC` + /// * `WHERE __name__ > ... AND a > 1` becomes + /// `WHERE __name__ > ... AND a > 1 ORDER BY a ASC, __name__ ASC` + final List orderBy; + + /// A potential prefix of a position in the result set to start the query at. + /// + /// The ordering of the result set is based on the `ORDER BY` clause of the + /// original query. + /// + /// ``` + /// SELECT * FROM k WHERE a = 1 AND b > 2 ORDER BY b ASC, __name__ ASC; + /// ``` + /// + /// This query's results are ordered by `(b ASC, __name__ ASC)`. + /// + /// Cursors can reference either the full ordering or a prefix of the location, + /// though it cannot reference more fields than what are in the provided + /// `ORDER BY`. + /// + /// Continuing off the example above, attaching the following start cursors + /// will have varying impact: + /// + /// - `START BEFORE (2, /k/123)`: start the query right before `a = 1 AND + /// b > 2 AND __name__ > /k/123`. + /// - `START AFTER (10)`: start the query right after `a = 1 AND b > 10`. + /// + /// Unlike `OFFSET` which requires scanning over the first N results to skip, + /// a start cursor allows the query to begin at a logical position. This + /// position is not required to match an actual result, it will scan forward + /// from this position to find the next document. + /// + /// Requires: + /// + /// * The number of values cannot be greater than the number of fields + /// specified in the `ORDER BY` clause. + final Cursor? startAt; + + /// A potential prefix of a position in the result set to end the query at. + /// + /// This is similar to `START_AT` but with it controlling the end position + /// rather than the start position. + /// + /// Requires: + /// + /// * The number of values cannot be greater than the number of fields + /// specified in the `ORDER BY` clause. + final Cursor? endAt; + + /// The number of documents to skip before returning the first result. + /// + /// This applies after the constraints specified by the `WHERE`, `START AT`, & + /// `END AT` but before the `LIMIT` clause. + /// + /// Requires: + /// + /// * The value must be greater than or equal to zero if specified. + final int offset; + + /// The maximum number of results to return. + /// + /// Applies after all other constraints. + /// + /// Requires: + /// + /// * The value must be greater than or equal to zero if specified. + final Int32Value? limit; + + /// Optional. A potential nearest neighbors search. + /// + /// Applies after all other filters and ordering. + /// + /// Finds the closest vector embeddings to the given query vector. + final StructuredQuery_FindNearest? findNearest; + + StructuredQuery({ + this.select, + this.from = const [], + this.where, + this.orderBy = const [], + this.startAt, + this.endAt, + this.offset = 0, + this.limit, + this.findNearest, + }) : super(fullyQualifiedName); + + factory StructuredQuery.fromJson(Object? j) { + final json = j as Map; + return StructuredQuery( + select: switch (json['select']) { + null => null, + Object $1 => StructuredQuery_Projection.fromJson($1), + }, + from: switch (json['from']) { + null => [], + List $1 => [ + for (final i in $1) StructuredQuery_CollectionSelector.fromJson(i), + ], + _ => throw const FormatException('"from" is not a list'), + }, + where: switch (json['where']) { + null => null, + Object $1 => StructuredQuery_Filter.fromJson($1), + }, + orderBy: switch (json['orderBy']) { + null => [], + List $1 => [ + for (final i in $1) StructuredQuery_Order.fromJson(i), + ], + _ => throw const FormatException('"orderBy" is not a list'), + }, + startAt: switch (json['startAt']) { + null => null, + Object $1 => Cursor.fromJson($1), + }, + endAt: switch (json['endAt']) { + null => null, + Object $1 => Cursor.fromJson($1), + }, + offset: switch (json['offset']) { + null => 0, + Object $1 => decodeInt($1), + }, + limit: switch (json['limit']) { + null => null, + Object $1 => Int32Value.fromJson($1), + }, + findNearest: switch (json['findNearest']) { + null => null, + Object $1 => StructuredQuery_FindNearest.fromJson($1), + }, + ); + } + + @override + Object toJson() => { + if (select case final select?) 'select': select.toJson(), + if (from.isNotDefault) 'from': [for (final i in from) i.toJson()], + if (where case final where?) 'where': where.toJson(), + if (orderBy.isNotDefault) 'orderBy': [for (final i in orderBy) i.toJson()], + if (startAt case final startAt?) 'startAt': startAt.toJson(), + if (endAt case final endAt?) 'endAt': endAt.toJson(), + if (offset.isNotDefault) 'offset': offset, + if (limit case final limit?) 'limit': limit.toJson(), + if (findNearest case final findNearest?) + 'findNearest': findNearest.toJson(), + }; + + @override + String toString() { + final $contents = ['offset=$offset'].join(','); + return 'StructuredQuery(${$contents})'; + } +} + +/// A selection of a collection, such as `messages as m1`. +final class StructuredQuery_CollectionSelector extends ProtoMessage { + static const String fullyQualifiedName = + 'google.firestore.v1.StructuredQuery.CollectionSelector'; + + /// The collection ID. + /// When set, selects only collections with this ID. + final String collectionId; + + /// When false, selects only collections that are immediate children of + /// the `parent` specified in the containing `RunQueryRequest`. + /// When true, selects all descendant collections. + final bool allDescendants; + + StructuredQuery_CollectionSelector({ + this.collectionId = '', + this.allDescendants = false, + }) : super(fullyQualifiedName); + + factory StructuredQuery_CollectionSelector.fromJson(Object? j) { + final json = j as Map; + return StructuredQuery_CollectionSelector( + collectionId: switch (json['collectionId']) { + null => '', + Object $1 => decodeString($1), + }, + allDescendants: switch (json['allDescendants']) { + null => false, + Object $1 => decodeBool($1), + }, + ); + } + + @override + Object toJson() => { + if (collectionId.isNotDefault) 'collectionId': collectionId, + if (allDescendants.isNotDefault) 'allDescendants': allDescendants, + }; + + @override + String toString() { + final $contents = [ + 'collectionId=$collectionId', + 'allDescendants=$allDescendants', + ].join(','); + return 'CollectionSelector(${$contents})'; + } +} + +/// A filter. +final class StructuredQuery_Filter extends ProtoMessage { + static const String fullyQualifiedName = + 'google.firestore.v1.StructuredQuery.Filter'; + + /// A composite filter. + final StructuredQuery_CompositeFilter? compositeFilter; + + /// A filter on a document field. + final StructuredQuery_FieldFilter? fieldFilter; + + /// A filter that takes exactly one argument. + final StructuredQuery_UnaryFilter? unaryFilter; + + StructuredQuery_Filter({ + this.compositeFilter, + this.fieldFilter, + this.unaryFilter, + }) : super(fullyQualifiedName); + + factory StructuredQuery_Filter.fromJson(Object? j) { + final json = j as Map; + return StructuredQuery_Filter( + compositeFilter: switch (json['compositeFilter']) { + null => null, + Object $1 => StructuredQuery_CompositeFilter.fromJson($1), + }, + fieldFilter: switch (json['fieldFilter']) { + null => null, + Object $1 => StructuredQuery_FieldFilter.fromJson($1), + }, + unaryFilter: switch (json['unaryFilter']) { + null => null, + Object $1 => StructuredQuery_UnaryFilter.fromJson($1), + }, + ); + } + + @override + Object toJson() => { + if (compositeFilter case final compositeFilter?) + 'compositeFilter': compositeFilter.toJson(), + if (fieldFilter case final fieldFilter?) + 'fieldFilter': fieldFilter.toJson(), + if (unaryFilter case final unaryFilter?) + 'unaryFilter': unaryFilter.toJson(), + }; + + @override + String toString() => 'Filter()'; +} + +/// A filter that merges multiple other filters using the given operator. +final class StructuredQuery_CompositeFilter extends ProtoMessage { + static const String fullyQualifiedName = + 'google.firestore.v1.StructuredQuery.CompositeFilter'; + + /// The operator for combining multiple filters. + final StructuredQuery_CompositeFilter_Operator op; + + /// The list of filters to combine. + /// + /// Requires: + /// + /// * At least one filter is present. + final List filters; + + StructuredQuery_CompositeFilter({ + this.op = StructuredQuery_CompositeFilter_Operator.$default, + this.filters = const [], + }) : super(fullyQualifiedName); + + factory StructuredQuery_CompositeFilter.fromJson(Object? j) { + final json = j as Map; + return StructuredQuery_CompositeFilter( + op: switch (json['op']) { + null => StructuredQuery_CompositeFilter_Operator.$default, + Object $1 => StructuredQuery_CompositeFilter_Operator.fromJson($1), + }, + filters: switch (json['filters']) { + null => [], + List $1 => [ + for (final i in $1) StructuredQuery_Filter.fromJson(i), + ], + _ => throw const FormatException('"filters" is not a list'), + }, + ); + } + + @override + Object toJson() => { + if (op.isNotDefault) 'op': op.toJson(), + if (filters.isNotDefault) 'filters': [for (final i in filters) i.toJson()], + }; + + @override + String toString() { + final $contents = ['op=$op'].join(','); + return 'CompositeFilter(${$contents})'; + } +} + +/// A composite filter operator. +final class StructuredQuery_CompositeFilter_Operator extends ProtoEnum { + /// Unspecified. This value must not be used. + static const operatorUnspecified = StructuredQuery_CompositeFilter_Operator( + 'OPERATOR_UNSPECIFIED', + ); + + /// Documents are required to satisfy all of the combined filters. + static const and = StructuredQuery_CompositeFilter_Operator('AND'); + + /// Documents are required to satisfy at least one of the combined filters. + static const or = StructuredQuery_CompositeFilter_Operator('OR'); + + /// The default value for [StructuredQuery_CompositeFilter_Operator]. + static const $default = operatorUnspecified; + + const StructuredQuery_CompositeFilter_Operator(super.value); + + factory StructuredQuery_CompositeFilter_Operator.fromJson(Object? json) => + StructuredQuery_CompositeFilter_Operator(json as String); + + bool get isNotDefault => this != $default; + + @override + String toString() => 'Operator.$value'; +} + +/// A filter on a specific field. +final class StructuredQuery_FieldFilter extends ProtoMessage { + static const String fullyQualifiedName = + 'google.firestore.v1.StructuredQuery.FieldFilter'; + + /// The field to filter by. + final StructuredQuery_FieldReference? field; + + /// The operator to filter by. + final StructuredQuery_FieldFilter_Operator op; + + /// The value to compare to. + final Value? value; + + StructuredQuery_FieldFilter({ + this.field, + this.op = StructuredQuery_FieldFilter_Operator.$default, + this.value, + }) : super(fullyQualifiedName); + + factory StructuredQuery_FieldFilter.fromJson(Object? j) { + final json = j as Map; + return StructuredQuery_FieldFilter( + field: switch (json['field']) { + null => null, + Object $1 => StructuredQuery_FieldReference.fromJson($1), + }, + op: switch (json['op']) { + null => StructuredQuery_FieldFilter_Operator.$default, + Object $1 => StructuredQuery_FieldFilter_Operator.fromJson($1), + }, + value: switch (json['value']) { + null => null, + Object $1 => Value.fromJson($1), + }, + ); + } + + @override + Object toJson() => { + if (field case final field?) 'field': field.toJson(), + if (op.isNotDefault) 'op': op.toJson(), + if (value case final value?) 'value': value.toJson(), + }; + + @override + String toString() { + final $contents = ['op=$op'].join(','); + return 'FieldFilter(${$contents})'; + } +} + +/// A field filter operator. +final class StructuredQuery_FieldFilter_Operator extends ProtoEnum { + /// Unspecified. This value must not be used. + static const operatorUnspecified = StructuredQuery_FieldFilter_Operator( + 'OPERATOR_UNSPECIFIED', + ); + + /// The given `field` is less than the given `value`. + /// + /// Requires: + /// + /// * That `field` come first in `order_by`. + static const lessThan = StructuredQuery_FieldFilter_Operator('LESS_THAN'); + + /// The given `field` is less than or equal to the given `value`. + /// + /// Requires: + /// + /// * That `field` come first in `order_by`. + static const lessThanOrEqual = StructuredQuery_FieldFilter_Operator( + 'LESS_THAN_OR_EQUAL', + ); + + /// The given `field` is greater than the given `value`. + /// + /// Requires: + /// + /// * That `field` come first in `order_by`. + static const greaterThan = StructuredQuery_FieldFilter_Operator( + 'GREATER_THAN', + ); + + /// The given `field` is greater than or equal to the given `value`. + /// + /// Requires: + /// + /// * That `field` come first in `order_by`. + static const greaterThanOrEqual = StructuredQuery_FieldFilter_Operator( + 'GREATER_THAN_OR_EQUAL', + ); + + /// The given `field` is equal to the given `value`. + static const equal = StructuredQuery_FieldFilter_Operator('EQUAL'); + + /// The given `field` is not equal to the given `value`. + /// + /// Requires: + /// + /// * No other `NOT_EQUAL`, `NOT_IN`, `IS_NOT_NULL`, or `IS_NOT_NAN`. + /// * That `field` comes first in the `order_by`. + static const notEqual = StructuredQuery_FieldFilter_Operator('NOT_EQUAL'); + + /// The given `field` is an array that contains the given `value`. + static const arrayContains = StructuredQuery_FieldFilter_Operator( + 'ARRAY_CONTAINS', + ); + + /// The given `field` is equal to at least one value in the given array. + /// + /// Requires: + /// + /// * That `value` is a non-empty `ArrayValue`, subject to disjunction + /// limits. + /// * No `NOT_IN` filters in the same query. + static const in$ = StructuredQuery_FieldFilter_Operator('IN'); + + /// The given `field` is an array that contains any of the values in the + /// given array. + /// + /// Requires: + /// + /// * That `value` is a non-empty `ArrayValue`, subject to disjunction + /// limits. + /// * No other `ARRAY_CONTAINS_ANY` filters within the same disjunction. + /// * No `NOT_IN` filters in the same query. + static const arrayContainsAny = StructuredQuery_FieldFilter_Operator( + 'ARRAY_CONTAINS_ANY', + ); + + /// The value of the `field` is not in the given array. + /// + /// Requires: + /// + /// * That `value` is a non-empty `ArrayValue` with at most 10 values. + /// * No other `OR`, `IN`, `ARRAY_CONTAINS_ANY`, `NOT_IN`, `NOT_EQUAL`, + /// `IS_NOT_NULL`, or `IS_NOT_NAN`. + /// * That `field` comes first in the `order_by`. + static const notIn = StructuredQuery_FieldFilter_Operator('NOT_IN'); + + /// The default value for [StructuredQuery_FieldFilter_Operator]. + static const $default = operatorUnspecified; + + const StructuredQuery_FieldFilter_Operator(super.value); + + factory StructuredQuery_FieldFilter_Operator.fromJson(Object? json) => + StructuredQuery_FieldFilter_Operator(json as String); + + bool get isNotDefault => this != $default; + + @override + String toString() => 'Operator.$value'; +} + +/// A filter with a single operand. +final class StructuredQuery_UnaryFilter extends ProtoMessage { + static const String fullyQualifiedName = + 'google.firestore.v1.StructuredQuery.UnaryFilter'; + + /// The unary operator to apply. + final StructuredQuery_UnaryFilter_Operator op; + + /// The field to which to apply the operator. + final StructuredQuery_FieldReference? field; + + StructuredQuery_UnaryFilter({ + this.op = StructuredQuery_UnaryFilter_Operator.$default, + this.field, + }) : super(fullyQualifiedName); + + factory StructuredQuery_UnaryFilter.fromJson(Object? j) { + final json = j as Map; + return StructuredQuery_UnaryFilter( + op: switch (json['op']) { + null => StructuredQuery_UnaryFilter_Operator.$default, + Object $1 => StructuredQuery_UnaryFilter_Operator.fromJson($1), + }, + field: switch (json['field']) { + null => null, + Object $1 => StructuredQuery_FieldReference.fromJson($1), + }, + ); + } + + @override + Object toJson() => { + if (op.isNotDefault) 'op': op.toJson(), + if (field case final field?) 'field': field.toJson(), + }; + + @override + String toString() { + final $contents = ['op=$op'].join(','); + return 'UnaryFilter(${$contents})'; + } +} + +/// A unary operator. +final class StructuredQuery_UnaryFilter_Operator extends ProtoEnum { + /// Unspecified. This value must not be used. + static const operatorUnspecified = StructuredQuery_UnaryFilter_Operator( + 'OPERATOR_UNSPECIFIED', + ); + + /// The given `field` is equal to `NaN`. + static const isNan = StructuredQuery_UnaryFilter_Operator('IS_NAN'); + + /// The given `field` is equal to `NULL`. + static const isNull = StructuredQuery_UnaryFilter_Operator('IS_NULL'); + + /// The given `field` is not equal to `NaN`. + /// + /// Requires: + /// + /// * No other `NOT_EQUAL`, `NOT_IN`, `IS_NOT_NULL`, or `IS_NOT_NAN`. + /// * That `field` comes first in the `order_by`. + static const isNotNan = StructuredQuery_UnaryFilter_Operator('IS_NOT_NAN'); + + /// The given `field` is not equal to `NULL`. + /// + /// Requires: + /// + /// * A single `NOT_EQUAL`, `NOT_IN`, `IS_NOT_NULL`, or `IS_NOT_NAN`. + /// * That `field` comes first in the `order_by`. + static const isNotNull = StructuredQuery_UnaryFilter_Operator('IS_NOT_NULL'); + + /// The default value for [StructuredQuery_UnaryFilter_Operator]. + static const $default = operatorUnspecified; + + const StructuredQuery_UnaryFilter_Operator(super.value); + + factory StructuredQuery_UnaryFilter_Operator.fromJson(Object? json) => + StructuredQuery_UnaryFilter_Operator(json as String); + + bool get isNotDefault => this != $default; + + @override + String toString() => 'Operator.$value'; +} + +/// An order on a field. +final class StructuredQuery_Order extends ProtoMessage { + static const String fullyQualifiedName = + 'google.firestore.v1.StructuredQuery.Order'; + + /// The field to order by. + final StructuredQuery_FieldReference? field; + + /// The direction to order by. Defaults to `ASCENDING`. + final StructuredQuery_Direction direction; + + StructuredQuery_Order({ + this.field, + this.direction = StructuredQuery_Direction.$default, + }) : super(fullyQualifiedName); + + factory StructuredQuery_Order.fromJson(Object? j) { + final json = j as Map; + return StructuredQuery_Order( + field: switch (json['field']) { + null => null, + Object $1 => StructuredQuery_FieldReference.fromJson($1), + }, + direction: switch (json['direction']) { + null => StructuredQuery_Direction.$default, + Object $1 => StructuredQuery_Direction.fromJson($1), + }, + ); + } + + @override + Object toJson() => { + if (field case final field?) 'field': field.toJson(), + if (direction.isNotDefault) 'direction': direction.toJson(), + }; + + @override + String toString() { + final $contents = ['direction=$direction'].join(','); + return 'Order(${$contents})'; + } +} + +/// A reference to a field in a document, ex: `stats.operations`. +final class StructuredQuery_FieldReference extends ProtoMessage { + static const String fullyQualifiedName = + 'google.firestore.v1.StructuredQuery.FieldReference'; + + /// A reference to a field in a document. + /// + /// Requires: + /// + /// * MUST be a dot-delimited (`.`) string of segments, where each segment + /// conforms to [document field name][google.firestore.v1.Document.fields] + /// limitations. + final String fieldPath; + + StructuredQuery_FieldReference({this.fieldPath = ''}) + : super(fullyQualifiedName); + + factory StructuredQuery_FieldReference.fromJson(Object? j) { + final json = j as Map; + return StructuredQuery_FieldReference( + fieldPath: switch (json['fieldPath']) { + null => '', + Object $1 => decodeString($1), + }, + ); + } + + @override + Object toJson() => {if (fieldPath.isNotDefault) 'fieldPath': fieldPath}; + + @override + String toString() { + final $contents = ['fieldPath=$fieldPath'].join(','); + return 'FieldReference(${$contents})'; + } +} + +/// The projection of document's fields to return. +final class StructuredQuery_Projection extends ProtoMessage { + static const String fullyQualifiedName = + 'google.firestore.v1.StructuredQuery.Projection'; + + /// The fields to return. + /// + /// If empty, all fields are returned. To only return the name + /// of the document, use `['__name__']`. + final List fields; + + StructuredQuery_Projection({this.fields = const []}) + : super(fullyQualifiedName); + + factory StructuredQuery_Projection.fromJson(Object? j) { + final json = j as Map; + return StructuredQuery_Projection( + fields: switch (json['fields']) { + null => [], + List $1 => [ + for (final i in $1) StructuredQuery_FieldReference.fromJson(i), + ], + _ => throw const FormatException('"fields" is not a list'), + }, + ); + } + + @override + Object toJson() => { + if (fields.isNotDefault) 'fields': [for (final i in fields) i.toJson()], + }; + + @override + String toString() => 'Projection()'; +} + +/// Nearest Neighbors search config. The ordering provided by FindNearest +/// supersedes the order_by stage. If multiple documents have the same vector +/// distance, the returned document order is not guaranteed to be stable +/// between queries. +final class StructuredQuery_FindNearest extends ProtoMessage { + static const String fullyQualifiedName = + 'google.firestore.v1.StructuredQuery.FindNearest'; + + /// Required. An indexed vector field to search upon. Only documents which + /// contain vectors whose dimensionality match the query_vector can be + /// returned. + final StructuredQuery_FieldReference? vectorField; + + /// Required. The query vector that we are searching on. Must be a vector of + /// no more than 2048 dimensions. + final Value? queryVector; + + /// Required. The distance measure to use, required. + final StructuredQuery_FindNearest_DistanceMeasure distanceMeasure; + + /// Required. The number of nearest neighbors to return. Must be a positive + /// integer of no more than 1000. + final Int32Value? limit; + + /// Optional. Optional name of the field to output the result of the vector + /// distance calculation. Must conform to [document field + /// name][google.firestore.v1.Document.fields] limitations. + final String distanceResultField; + + /// Optional. Option to specify a threshold for which no less similar + /// documents will be returned. The behavior of the specified + /// `distance_measure` will affect the meaning of the distance threshold. + /// Since DOT_PRODUCT distances increase when the vectors are more similar, + /// the comparison is inverted. + /// + /// * For EUCLIDEAN, COSINE: `WHERE distance <= distance_threshold` + /// * For DOT_PRODUCT: `WHERE distance >= distance_threshold` + final DoubleValue? distanceThreshold; + + StructuredQuery_FindNearest({ + required this.vectorField, + required this.queryVector, + required this.distanceMeasure, + required this.limit, + this.distanceResultField = '', + this.distanceThreshold, + }) : super(fullyQualifiedName); + + factory StructuredQuery_FindNearest.fromJson(Object? j) { + final json = j as Map; + return StructuredQuery_FindNearest( + vectorField: switch (json['vectorField']) { + null => null, + Object $1 => StructuredQuery_FieldReference.fromJson($1), + }, + queryVector: switch (json['queryVector']) { + null => null, + Object $1 => Value.fromJson($1), + }, + distanceMeasure: switch (json['distanceMeasure']) { + null => StructuredQuery_FindNearest_DistanceMeasure.$default, + Object $1 => StructuredQuery_FindNearest_DistanceMeasure.fromJson($1), + }, + limit: switch (json['limit']) { + null => null, + Object $1 => Int32Value.fromJson($1), + }, + distanceResultField: switch (json['distanceResultField']) { + null => '', + Object $1 => decodeString($1), + }, + distanceThreshold: switch (json['distanceThreshold']) { + null => null, + Object $1 => DoubleValue.fromJson($1), + }, + ); + } + + @override + Object toJson() => { + if (vectorField case final vectorField?) + 'vectorField': vectorField.toJson(), + if (queryVector case final queryVector?) + 'queryVector': queryVector.toJson(), + 'distanceMeasure': distanceMeasure.toJson(), + if (limit case final limit?) 'limit': limit.toJson(), + if (distanceResultField.isNotDefault) + 'distanceResultField': distanceResultField, + if (distanceThreshold case final distanceThreshold?) + 'distanceThreshold': distanceThreshold.toJson(), + }; + + @override + String toString() { + final $contents = [ + 'distanceMeasure=$distanceMeasure', + 'distanceResultField=$distanceResultField', + ].join(','); + return 'FindNearest(${$contents})'; + } +} + +/// The distance measure to use when comparing vectors. +final class StructuredQuery_FindNearest_DistanceMeasure extends ProtoEnum { + /// Should not be set. + static const distanceMeasureUnspecified = + StructuredQuery_FindNearest_DistanceMeasure( + 'DISTANCE_MEASURE_UNSPECIFIED', + ); + + /// Measures the EUCLIDEAN distance between the vectors. See + /// [Euclidean](https://en.wikipedia.org/wiki/Euclidean_distance) to learn + /// more. The resulting distance decreases the more similar two vectors + /// are. + static const euclidean = StructuredQuery_FindNearest_DistanceMeasure( + 'EUCLIDEAN', + ); + + /// COSINE distance compares vectors based on the angle between them, which + /// allows you to measure similarity that isn't based on the vectors + /// magnitude. We recommend using DOT_PRODUCT with unit normalized vectors + /// instead of COSINE distance, which is mathematically equivalent with + /// better performance. See [Cosine + /// Similarity](https://en.wikipedia.org/wiki/Cosine_similarity) to learn + /// more about COSINE similarity and COSINE distance. The resulting + /// COSINE distance decreases the more similar two vectors are. + static const cosine = StructuredQuery_FindNearest_DistanceMeasure('COSINE'); + + /// Similar to cosine but is affected by the magnitude of the vectors. See + /// [Dot Product](https://en.wikipedia.org/wiki/Dot_product) to learn more. + /// The resulting distance increases the more similar two vectors are. + static const dotProduct = StructuredQuery_FindNearest_DistanceMeasure( + 'DOT_PRODUCT', + ); + + /// The default value for [StructuredQuery_FindNearest_DistanceMeasure]. + static const $default = distanceMeasureUnspecified; + + const StructuredQuery_FindNearest_DistanceMeasure(super.value); + + factory StructuredQuery_FindNearest_DistanceMeasure.fromJson(Object? json) => + StructuredQuery_FindNearest_DistanceMeasure(json as String); + + bool get isNotDefault => this != $default; + + @override + String toString() => 'DistanceMeasure.$value'; +} + +/// A sort direction. +final class StructuredQuery_Direction extends ProtoEnum { + /// Unspecified. + static const directionUnspecified = StructuredQuery_Direction( + 'DIRECTION_UNSPECIFIED', + ); + + /// Ascending. + static const ascending = StructuredQuery_Direction('ASCENDING'); + + /// Descending. + static const descending = StructuredQuery_Direction('DESCENDING'); + + /// The default value for [StructuredQuery_Direction]. + static const $default = directionUnspecified; + + const StructuredQuery_Direction(super.value); + + factory StructuredQuery_Direction.fromJson(Object? json) => + StructuredQuery_Direction(json as String); + + bool get isNotDefault => this != $default; + + @override + String toString() => 'Direction.$value'; +} + +/// Firestore query for running an aggregation over a +/// `StructuredQuery`. +final class StructuredAggregationQuery extends ProtoMessage { + static const String fullyQualifiedName = + 'google.firestore.v1.StructuredAggregationQuery'; + + /// Nested structured query. + final StructuredQuery? structuredQuery; + + /// Optional. Series of aggregations to apply over the results of the + /// `structured_query`. + /// + /// Requires: + /// + /// * A minimum of one and maximum of five aggregations per query. + final List aggregations; + + StructuredAggregationQuery({ + this.structuredQuery, + this.aggregations = const [], + }) : super(fullyQualifiedName); + + factory StructuredAggregationQuery.fromJson(Object? j) { + final json = j as Map; + return StructuredAggregationQuery( + structuredQuery: switch (json['structuredQuery']) { + null => null, + Object $1 => StructuredQuery.fromJson($1), + }, + aggregations: switch (json['aggregations']) { + null => [], + List $1 => [ + for (final i in $1) + StructuredAggregationQuery_Aggregation.fromJson(i), + ], + _ => throw const FormatException('"aggregations" is not a list'), + }, + ); + } + + @override + Object toJson() => { + if (structuredQuery case final structuredQuery?) + 'structuredQuery': structuredQuery.toJson(), + if (aggregations.isNotDefault) + 'aggregations': [for (final i in aggregations) i.toJson()], + }; + + @override + String toString() => 'StructuredAggregationQuery()'; +} + +/// Defines an aggregation that produces a single result. +final class StructuredAggregationQuery_Aggregation extends ProtoMessage { + static const String fullyQualifiedName = + 'google.firestore.v1.StructuredAggregationQuery.Aggregation'; + + /// Count aggregator. + final StructuredAggregationQuery_Aggregation_Count? count; + + /// Sum aggregator. + final StructuredAggregationQuery_Aggregation_Sum? sum; + + /// Average aggregator. + final StructuredAggregationQuery_Aggregation_Avg? avg; + + /// Optional. Optional name of the field to store the result of the + /// aggregation into. + /// + /// If not provided, Firestore will pick a default name following the format + /// `field_`. For example: + /// + /// ``` + /// AGGREGATE + /// COUNT_UP_TO(1) AS count_up_to_1, + /// COUNT_UP_TO(2), + /// COUNT_UP_TO(3) AS count_up_to_3, + /// COUNT(*) + /// OVER ( + /// ... + /// ); + /// ``` + /// + /// becomes: + /// + /// ``` + /// AGGREGATE + /// COUNT_UP_TO(1) AS count_up_to_1, + /// COUNT_UP_TO(2) AS field_1, + /// COUNT_UP_TO(3) AS count_up_to_3, + /// COUNT(*) AS field_2 + /// OVER ( + /// ... + /// ); + /// ``` + /// + /// Requires: + /// + /// * Must be unique across all aggregation aliases. + /// * Conform to [document field name][google.firestore.v1.Document.fields] + /// limitations. + final String alias; + + StructuredAggregationQuery_Aggregation({ + this.count, + this.sum, + this.avg, + this.alias = '', + }) : super(fullyQualifiedName); + + factory StructuredAggregationQuery_Aggregation.fromJson(Object? j) { + final json = j as Map; + return StructuredAggregationQuery_Aggregation( + count: switch (json['count']) { + null => null, + Object $1 => StructuredAggregationQuery_Aggregation_Count.fromJson($1), + }, + sum: switch (json['sum']) { + null => null, + Object $1 => StructuredAggregationQuery_Aggregation_Sum.fromJson($1), + }, + avg: switch (json['avg']) { + null => null, + Object $1 => StructuredAggregationQuery_Aggregation_Avg.fromJson($1), + }, + alias: switch (json['alias']) { + null => '', + Object $1 => decodeString($1), + }, + ); + } + + @override + Object toJson() => { + if (count case final count?) 'count': count.toJson(), + if (sum case final sum?) 'sum': sum.toJson(), + if (avg case final avg?) 'avg': avg.toJson(), + if (alias.isNotDefault) 'alias': alias, + }; + + @override + String toString() { + final $contents = ['alias=$alias'].join(','); + return 'Aggregation(${$contents})'; + } +} + +/// Count of documents that match the query. +/// +/// The `COUNT(*)` aggregation function operates on the entire document +/// so it does not require a field reference. +final class StructuredAggregationQuery_Aggregation_Count extends ProtoMessage { + static const String fullyQualifiedName = + 'google.firestore.v1.StructuredAggregationQuery.Aggregation.Count'; + + /// Optional. Optional constraint on the maximum number of documents to + /// count. + /// + /// This provides a way to set an upper bound on the number of documents + /// to scan, limiting latency, and cost. + /// + /// Unspecified is interpreted as no bound. + /// + /// High-Level Example: + /// + /// ``` + /// AGGREGATE COUNT_UP_TO(1000) OVER ( SELECT * FROM k ); + /// ``` + /// + /// Requires: + /// + /// * Must be greater than zero when present. + final Int64Value? upTo; + + StructuredAggregationQuery_Aggregation_Count({this.upTo}) + : super(fullyQualifiedName); + + factory StructuredAggregationQuery_Aggregation_Count.fromJson(Object? j) { + final json = j as Map; + return StructuredAggregationQuery_Aggregation_Count( + upTo: switch (json['upTo']) { + null => null, + Object $1 => Int64Value.fromJson($1), + }, + ); + } + + @override + Object toJson() => {if (upTo case final upTo?) 'upTo': upTo.toJson()}; + + @override + String toString() => 'Count()'; +} + +/// Sum of the values of the requested field. +/// +/// * Only numeric values will be aggregated. All non-numeric values +/// including `NULL` are skipped. +/// +/// * If the aggregated values contain `NaN`, returns `NaN`. Infinity math +/// follows IEEE-754 standards. +/// +/// * If the aggregated value set is empty, returns 0. +/// +/// * Returns a 64-bit integer if all aggregated numbers are integers and the +/// sum result does not overflow. Otherwise, the result is returned as a +/// double. Note that even if all the aggregated values are integers, the +/// result is returned as a double if it cannot fit within a 64-bit signed +/// integer. When this occurs, the returned value will lose precision. +/// +/// * When underflow occurs, floating-point aggregation is non-deterministic. +/// This means that running the same query repeatedly without any changes to +/// the underlying values could produce slightly different results each +/// time. In those cases, values should be stored as integers over +/// floating-point numbers. +final class StructuredAggregationQuery_Aggregation_Sum extends ProtoMessage { + static const String fullyQualifiedName = + 'google.firestore.v1.StructuredAggregationQuery.Aggregation.Sum'; + + /// The field to aggregate on. + final StructuredQuery_FieldReference? field; + + StructuredAggregationQuery_Aggregation_Sum({this.field}) + : super(fullyQualifiedName); + + factory StructuredAggregationQuery_Aggregation_Sum.fromJson(Object? j) { + final json = j as Map; + return StructuredAggregationQuery_Aggregation_Sum( + field: switch (json['field']) { + null => null, + Object $1 => StructuredQuery_FieldReference.fromJson($1), + }, + ); + } + + @override + Object toJson() => {if (field case final field?) 'field': field.toJson()}; + + @override + String toString() => 'Sum()'; +} + +/// Average of the values of the requested field. +/// +/// * Only numeric values will be aggregated. All non-numeric values +/// including `NULL` are skipped. +/// +/// * If the aggregated values contain `NaN`, returns `NaN`. Infinity math +/// follows IEEE-754 standards. +/// +/// * If the aggregated value set is empty, returns `NULL`. +/// +/// * Always returns the result as a double. +final class StructuredAggregationQuery_Aggregation_Avg extends ProtoMessage { + static const String fullyQualifiedName = + 'google.firestore.v1.StructuredAggregationQuery.Aggregation.Avg'; + + /// The field to aggregate on. + final StructuredQuery_FieldReference? field; + + StructuredAggregationQuery_Aggregation_Avg({this.field}) + : super(fullyQualifiedName); + + factory StructuredAggregationQuery_Aggregation_Avg.fromJson(Object? j) { + final json = j as Map; + return StructuredAggregationQuery_Aggregation_Avg( + field: switch (json['field']) { + null => null, + Object $1 => StructuredQuery_FieldReference.fromJson($1), + }, + ); + } + + @override + Object toJson() => {if (field case final field?) 'field': field.toJson()}; + + @override + String toString() => 'Avg()'; +} + +/// A position in a query result set. +final class Cursor extends ProtoMessage { + static const String fullyQualifiedName = 'google.firestore.v1.Cursor'; + + /// The values that represent a position, in the order they appear in + /// the order by clause of a query. + /// + /// Can contain fewer values than specified in the order by clause. + final List values; + + /// If the position is just before or just after the given values, relative + /// to the sort order defined by the query. + final bool before; + + Cursor({this.values = const [], this.before = false}) + : super(fullyQualifiedName); + + factory Cursor.fromJson(Object? j) { + final json = j as Map; + return Cursor( + values: switch (json['values']) { + null => [], + List $1 => [for (final i in $1) Value.fromJson(i)], + _ => throw const FormatException('"values" is not a list'), + }, + before: switch (json['before']) { + null => false, + Object $1 => decodeBool($1), + }, + ); + } + + @override + Object toJson() => { + if (values.isNotDefault) 'values': [for (final i in values) i.toJson()], + if (before.isNotDefault) 'before': before, + }; + + @override + String toString() { + final $contents = ['before=$before'].join(','); + return 'Cursor(${$contents})'; + } +} + +/// Explain options for the query. +final class ExplainOptions extends ProtoMessage { + static const String fullyQualifiedName = 'google.firestore.v1.ExplainOptions'; + + /// Optional. Whether to execute this query. + /// + /// When false (the default), the query will be planned, returning only + /// metrics from the planning stages. + /// + /// When true, the query will be planned and executed, returning the full + /// query results along with both planning and execution stage metrics. + final bool analyze; + + ExplainOptions({this.analyze = false}) : super(fullyQualifiedName); + + factory ExplainOptions.fromJson(Object? j) { + final json = j as Map; + return ExplainOptions( + analyze: switch (json['analyze']) { + null => false, + Object $1 => decodeBool($1), + }, + ); + } + + @override + Object toJson() => {if (analyze.isNotDefault) 'analyze': analyze}; + + @override + String toString() { + final $contents = ['analyze=$analyze'].join(','); + return 'ExplainOptions(${$contents})'; + } +} + +/// Explain metrics for the query. +final class ExplainMetrics extends ProtoMessage { + static const String fullyQualifiedName = 'google.firestore.v1.ExplainMetrics'; + + /// Planning phase information for the query. + final PlanSummary? planSummary; + + /// Aggregated stats from the execution of the query. Only present when + /// `ExplainOptions.analyze` is set + /// to true. + final ExecutionStats? executionStats; + + ExplainMetrics({this.planSummary, this.executionStats}) + : super(fullyQualifiedName); + + factory ExplainMetrics.fromJson(Object? j) { + final json = j as Map; + return ExplainMetrics( + planSummary: switch (json['planSummary']) { + null => null, + Object $1 => PlanSummary.fromJson($1), + }, + executionStats: switch (json['executionStats']) { + null => null, + Object $1 => ExecutionStats.fromJson($1), + }, + ); + } + + @override + Object toJson() => { + if (planSummary case final planSummary?) + 'planSummary': planSummary.toJson(), + if (executionStats case final executionStats?) + 'executionStats': executionStats.toJson(), + }; + + @override + String toString() => 'ExplainMetrics()'; +} + +/// Planning phase information for the query. +final class PlanSummary extends ProtoMessage { + static const String fullyQualifiedName = 'google.firestore.v1.PlanSummary'; + + /// The indexes selected for the query. For example: + /// [ + /// {"query_scope": "Collection", "properties": "(foo ASC, __name__ ASC)"}, + /// {"query_scope": "Collection", "properties": "(bar ASC, __name__ ASC)"} + /// ] + final List indexesUsed; + + PlanSummary({this.indexesUsed = const []}) : super(fullyQualifiedName); + + factory PlanSummary.fromJson(Object? j) { + final json = j as Map; + return PlanSummary( + indexesUsed: switch (json['indexesUsed']) { + null => [], + List $1 => [for (final i in $1) Struct.fromJson(i)], + _ => throw const FormatException('"indexesUsed" is not a list'), + }, + ); + } + + @override + Object toJson() => { + if (indexesUsed.isNotDefault) + 'indexesUsed': [for (final i in indexesUsed) i.toJson()], + }; + + @override + String toString() => 'PlanSummary()'; +} + +/// Execution statistics for the query. +final class ExecutionStats extends ProtoMessage { + static const String fullyQualifiedName = 'google.firestore.v1.ExecutionStats'; + + /// Total number of results returned, including documents, projections, + /// aggregation results, keys. + final int resultsReturned; + + /// Total time to execute the query in the backend. + final Duration? executionDuration; + + /// Total billable read operations. + final int readOperations; + + /// Debugging statistics from the execution of the query. Note that the + /// debugging stats are subject to change as Firestore evolves. It could + /// include: + /// { + /// "indexes_entries_scanned": "1000", + /// "documents_scanned": "20", + /// "billing_details" : { + /// "documents_billable": "20", + /// "index_entries_billable": "1000", + /// "min_query_cost": "0" + /// } + /// } + final Struct? debugStats; + + ExecutionStats({ + this.resultsReturned = 0, + this.executionDuration, + this.readOperations = 0, + this.debugStats, + }) : super(fullyQualifiedName); + + factory ExecutionStats.fromJson(Object? j) { + final json = j as Map; + return ExecutionStats( + resultsReturned: switch (json['resultsReturned']) { + null => 0, + Object $1 => decodeInt64($1), + }, + executionDuration: switch (json['executionDuration']) { + null => null, + Object $1 => Duration.fromJson($1), + }, + readOperations: switch (json['readOperations']) { + null => 0, + Object $1 => decodeInt64($1), + }, + debugStats: switch (json['debugStats']) { + null => null, + Object $1 => Struct.fromJson($1), + }, + ); + } + + @override + Object toJson() => { + if (resultsReturned.isNotDefault) + 'resultsReturned': resultsReturned.toString(), + if (executionDuration case final executionDuration?) + 'executionDuration': executionDuration.toJson(), + if (readOperations.isNotDefault) + 'readOperations': readOperations.toString(), + if (debugStats case final debugStats?) 'debugStats': debugStats.toJson(), + }; + + @override + String toString() { + final $contents = [ + 'resultsReturned=$resultsReturned', + 'readOperations=$readOperations', + ].join(','); + return 'ExecutionStats(${$contents})'; + } +} + +/// A write on a document. +final class Write extends ProtoMessage { + static const String fullyQualifiedName = 'google.firestore.v1.Write'; + + /// A document to write. + final Document? update; + + /// A document name to delete. In the format: + /// `projects/{project_id}/databases/{database_id}/documents/{document_path}`. + final String? delete; + + /// Applies a transformation to a document. + final DocumentTransform? transform; + + /// The fields to update in this write. + /// + /// This field can be set only when the operation is `update`. + /// If the mask is not set for an `update` and the document exists, any + /// existing data will be overwritten. + /// If the mask is set and the document on the server has fields not covered by + /// the mask, they are left unchanged. + /// Fields referenced in the mask, but not present in the input document, are + /// deleted from the document on the server. + /// The field paths in this mask must not contain a reserved field name. + final DocumentMask? updateMask; + + /// The transforms to perform after update. + /// + /// This field can be set only when the operation is `update`. If present, this + /// write is equivalent to performing `update` and `transform` to the same + /// document atomically and in order. + final List updateTransforms; + + /// An optional precondition on the document. + /// + /// The write will fail if this is set and not met by the target document. + final Precondition? currentDocument; + + Write({ + this.update, + this.delete, + this.transform, + this.updateMask, + this.updateTransforms = const [], + this.currentDocument, + }) : super(fullyQualifiedName); + + factory Write.fromJson(Object? j) { + final json = j as Map; + return Write( + update: switch (json['update']) { + null => null, + Object $1 => Document.fromJson($1), + }, + delete: switch (json['delete']) { + null => null, + Object $1 => decodeString($1), + }, + transform: switch (json['transform']) { + null => null, + Object $1 => DocumentTransform.fromJson($1), + }, + updateMask: switch (json['updateMask']) { + null => null, + Object $1 => DocumentMask.fromJson($1), + }, + updateTransforms: switch (json['updateTransforms']) { + null => [], + List $1 => [ + for (final i in $1) DocumentTransform_FieldTransform.fromJson(i), + ], + _ => throw const FormatException('"updateTransforms" is not a list'), + }, + currentDocument: switch (json['currentDocument']) { + null => null, + Object $1 => Precondition.fromJson($1), + }, + ); + } + + @override + Object toJson() => { + if (update case final update?) 'update': update.toJson(), + if (delete case final delete?) 'delete': delete, + if (transform case final transform?) 'transform': transform.toJson(), + if (updateMask case final updateMask?) 'updateMask': updateMask.toJson(), + if (updateTransforms.isNotDefault) + 'updateTransforms': [for (final i in updateTransforms) i.toJson()], + if (currentDocument case final currentDocument?) + 'currentDocument': currentDocument.toJson(), + }; + + @override + String toString() { + final $contents = [if (delete != null) 'delete=$delete'].join(','); + return 'Write(${$contents})'; + } +} + +/// A transformation of a document. +final class DocumentTransform extends ProtoMessage { + static const String fullyQualifiedName = + 'google.firestore.v1.DocumentTransform'; + + /// The name of the document to transform. + final String document; + + /// The list of transformations to apply to the fields of the document, in + /// order. + /// This must not be empty. + final List fieldTransforms; + + DocumentTransform({this.document = '', this.fieldTransforms = const []}) + : super(fullyQualifiedName); + + factory DocumentTransform.fromJson(Object? j) { + final json = j as Map; + return DocumentTransform( + document: switch (json['document']) { + null => '', + Object $1 => decodeString($1), + }, + fieldTransforms: switch (json['fieldTransforms']) { + null => [], + List $1 => [ + for (final i in $1) DocumentTransform_FieldTransform.fromJson(i), + ], + _ => throw const FormatException('"fieldTransforms" is not a list'), + }, + ); + } + + @override + Object toJson() => { + if (document.isNotDefault) 'document': document, + if (fieldTransforms.isNotDefault) + 'fieldTransforms': [for (final i in fieldTransforms) i.toJson()], + }; + + @override + String toString() { + final $contents = ['document=$document'].join(','); + return 'DocumentTransform(${$contents})'; + } +} + +/// A transformation of a field of the document. +final class DocumentTransform_FieldTransform extends ProtoMessage { + static const String fullyQualifiedName = + 'google.firestore.v1.DocumentTransform.FieldTransform'; + + /// The path of the field. See + /// `Document.fields` for the field path + /// syntax reference. + final String fieldPath; + + /// Sets the field to the given server value. + final DocumentTransform_FieldTransform_ServerValue? setToServerValue; + + /// Adds the given value to the field's current value. + /// + /// This must be an integer or a double value. + /// If the field is not an integer or double, or if the field does not yet + /// exist, the transformation will set the field to the given value. + /// If either of the given value or the current field value are doubles, + /// both values will be interpreted as doubles. Double arithmetic and + /// representation of double values follow IEEE 754 semantics. + /// If there is positive/negative integer overflow, the field is resolved + /// to the largest magnitude positive/negative integer. + final Value? increment; + + /// Sets the field to the maximum of its current value and the given value. + /// + /// This must be an integer or a double value. + /// If the field is not an integer or double, or if the field does not yet + /// exist, the transformation will set the field to the given value. + /// If a maximum operation is applied where the field and the input value + /// are of mixed types (that is - one is an integer and one is a double) + /// the field takes on the type of the larger operand. If the operands are + /// equivalent (e.g. 3 and 3.0), the field does not change. + /// 0, 0.0, and -0.0 are all zero. The maximum of a zero stored value and + /// zero input value is always the stored value. + /// The maximum of any numeric value x and NaN is NaN. + final Value? maximum; + + /// Sets the field to the minimum of its current value and the given value. + /// + /// This must be an integer or a double value. + /// If the field is not an integer or double, or if the field does not yet + /// exist, the transformation will set the field to the input value. + /// If a minimum operation is applied where the field and the input value + /// are of mixed types (that is - one is an integer and one is a double) + /// the field takes on the type of the smaller operand. If the operands are + /// equivalent (e.g. 3 and 3.0), the field does not change. + /// 0, 0.0, and -0.0 are all zero. The minimum of a zero stored value and + /// zero input value is always the stored value. + /// The minimum of any numeric value x and NaN is NaN. + final Value? minimum; + + /// Append the given elements in order if they are not already present in + /// the current field value. + /// If the field is not an array, or if the field does not yet exist, it is + /// first set to the empty array. + /// + /// Equivalent numbers of different types (e.g. 3L and 3.0) are + /// considered equal when checking if a value is missing. + /// NaN is equal to NaN, and Null is equal to Null. + /// If the input contains multiple equivalent values, only the first will + /// be considered. + /// + /// The corresponding transform_result will be the null value. + final ArrayValue? appendMissingElements; + + /// Remove all of the given elements from the array in the field. + /// If the field is not an array, or if the field does not yet exist, it is + /// set to the empty array. + /// + /// Equivalent numbers of the different types (e.g. 3L and 3.0) are + /// considered equal when deciding whether an element should be removed. + /// NaN is equal to NaN, and Null is equal to Null. + /// This will remove all equivalent values if there are duplicates. + /// + /// The corresponding transform_result will be the null value. + final ArrayValue? removeAllFromArray; + + DocumentTransform_FieldTransform({ + this.fieldPath = '', + this.setToServerValue, + this.increment, + this.maximum, + this.minimum, + this.appendMissingElements, + this.removeAllFromArray, + }) : super(fullyQualifiedName); + + factory DocumentTransform_FieldTransform.fromJson(Object? j) { + final json = j as Map; + return DocumentTransform_FieldTransform( + fieldPath: switch (json['fieldPath']) { + null => '', + Object $1 => decodeString($1), + }, + setToServerValue: switch (json['setToServerValue']) { + null => null, + Object $1 => DocumentTransform_FieldTransform_ServerValue.fromJson($1), + }, + increment: switch (json['increment']) { + null => null, + Object $1 => Value.fromJson($1), + }, + maximum: switch (json['maximum']) { + null => null, + Object $1 => Value.fromJson($1), + }, + minimum: switch (json['minimum']) { + null => null, + Object $1 => Value.fromJson($1), + }, + appendMissingElements: switch (json['appendMissingElements']) { + null => null, + Object $1 => ArrayValue.fromJson($1), + }, + removeAllFromArray: switch (json['removeAllFromArray']) { + null => null, + Object $1 => ArrayValue.fromJson($1), + }, + ); + } + + @override + Object toJson() => { + if (fieldPath.isNotDefault) 'fieldPath': fieldPath, + if (setToServerValue case final setToServerValue?) + 'setToServerValue': setToServerValue.toJson(), + if (increment case final increment?) 'increment': increment.toJson(), + if (maximum case final maximum?) 'maximum': maximum.toJson(), + if (minimum case final minimum?) 'minimum': minimum.toJson(), + if (appendMissingElements case final appendMissingElements?) + 'appendMissingElements': appendMissingElements.toJson(), + if (removeAllFromArray case final removeAllFromArray?) + 'removeAllFromArray': removeAllFromArray.toJson(), + }; + + @override + String toString() { + final $contents = [ + 'fieldPath=$fieldPath', + if (setToServerValue != null) 'setToServerValue=$setToServerValue', + ].join(','); + return 'FieldTransform(${$contents})'; + } +} + +/// A value that is calculated by the server. +final class DocumentTransform_FieldTransform_ServerValue extends ProtoEnum { + /// Unspecified. This value must not be used. + static const serverValueUnspecified = + DocumentTransform_FieldTransform_ServerValue('SERVER_VALUE_UNSPECIFIED'); + + /// The time at which the server processed the request, with millisecond + /// precision. If used on multiple fields (same or different documents) in + /// a transaction, all the fields will get the same server timestamp. + static const requestTime = DocumentTransform_FieldTransform_ServerValue( + 'REQUEST_TIME', + ); + + /// The default value for [DocumentTransform_FieldTransform_ServerValue]. + static const $default = serverValueUnspecified; + + const DocumentTransform_FieldTransform_ServerValue(super.value); + + factory DocumentTransform_FieldTransform_ServerValue.fromJson(Object? json) => + DocumentTransform_FieldTransform_ServerValue(json as String); + + bool get isNotDefault => this != $default; + + @override + String toString() => 'ServerValue.$value'; +} + +/// The result of applying a write. +final class WriteResult extends ProtoMessage { + static const String fullyQualifiedName = 'google.firestore.v1.WriteResult'; + + /// The last update time of the document after applying the write. Not set + /// after a `delete`. + /// + /// If the write did not actually change the document, this will be the + /// previous update_time. + final Timestamp? updateTime; + + /// The results of applying each + /// `DocumentTransform.FieldTransform`, + /// in the same order. + final List transformResults; + + WriteResult({this.updateTime, this.transformResults = const []}) + : super(fullyQualifiedName); + + factory WriteResult.fromJson(Object? j) { + final json = j as Map; + return WriteResult( + updateTime: switch (json['updateTime']) { + null => null, + Object $1 => Timestamp.fromJson($1), + }, + transformResults: switch (json['transformResults']) { + null => [], + List $1 => [for (final i in $1) Value.fromJson(i)], + _ => throw const FormatException('"transformResults" is not a list'), + }, + ); + } + + @override + Object toJson() => { + if (updateTime case final updateTime?) 'updateTime': updateTime.toJson(), + if (transformResults.isNotDefault) + 'transformResults': [for (final i in transformResults) i.toJson()], + }; + + @override + String toString() => 'WriteResult()'; +} + +/// A `Document` has changed. +/// +/// May be the result of multiple `writes`, including +/// deletes, that ultimately resulted in a new value for the +/// `Document`. +/// +/// Multiple `DocumentChange` messages may be +/// returned for the same logical change, if multiple targets are affected. +final class DocumentChange extends ProtoMessage { + static const String fullyQualifiedName = 'google.firestore.v1.DocumentChange'; + + /// The new state of the `Document`. + /// + /// If `mask` is set, contains only fields that were updated or added. + final Document? document; + + /// A set of target IDs of targets that match this document. + final List targetIds; + + /// A set of target IDs for targets that no longer match this document. + final List removedTargetIds; + + DocumentChange({ + this.document, + this.targetIds = const [], + this.removedTargetIds = const [], + }) : super(fullyQualifiedName); + + factory DocumentChange.fromJson(Object? j) { + final json = j as Map; + return DocumentChange( + document: switch (json['document']) { + null => null, + Object $1 => Document.fromJson($1), + }, + targetIds: switch (json['targetIds']) { + null => [], + List $1 => [for (final i in $1) decodeInt(i)], + _ => throw const FormatException('"targetIds" is not a list'), + }, + removedTargetIds: switch (json['removedTargetIds']) { + null => [], + List $1 => [for (final i in $1) decodeInt(i)], + _ => throw const FormatException('"removedTargetIds" is not a list'), + }, + ); + } + + @override + Object toJson() => { + if (document case final document?) 'document': document.toJson(), + if (targetIds.isNotDefault) 'targetIds': targetIds, + if (removedTargetIds.isNotDefault) 'removedTargetIds': removedTargetIds, + }; + + @override + String toString() => 'DocumentChange()'; +} + +/// A `Document` has been deleted. +/// +/// May be the result of multiple `writes`, including +/// updates, the last of which deleted the +/// `Document`. +/// +/// Multiple `DocumentDelete` messages may be +/// returned for the same logical delete, if multiple targets are affected. +final class DocumentDelete extends ProtoMessage { + static const String fullyQualifiedName = 'google.firestore.v1.DocumentDelete'; + + /// The resource name of the `Document` that was + /// deleted. + final String document; + + /// A set of target IDs for targets that previously matched this entity. + final List removedTargetIds; + + /// The read timestamp at which the delete was observed. + /// + /// Greater or equal to the `commit_time` of the delete. + final Timestamp? readTime; + + DocumentDelete({ + this.document = '', + this.removedTargetIds = const [], + this.readTime, + }) : super(fullyQualifiedName); + + factory DocumentDelete.fromJson(Object? j) { + final json = j as Map; + return DocumentDelete( + document: switch (json['document']) { + null => '', + Object $1 => decodeString($1), + }, + removedTargetIds: switch (json['removedTargetIds']) { + null => [], + List $1 => [for (final i in $1) decodeInt(i)], + _ => throw const FormatException('"removedTargetIds" is not a list'), + }, + readTime: switch (json['readTime']) { + null => null, + Object $1 => Timestamp.fromJson($1), + }, + ); + } + + @override + Object toJson() => { + if (document.isNotDefault) 'document': document, + if (removedTargetIds.isNotDefault) 'removedTargetIds': removedTargetIds, + if (readTime case final readTime?) 'readTime': readTime.toJson(), + }; + + @override + String toString() { + final $contents = ['document=$document'].join(','); + return 'DocumentDelete(${$contents})'; + } +} + +/// A `Document` has been removed from the view of +/// the targets. +/// +/// Sent if the document is no longer relevant to a target and is out of view. +/// Can be sent instead of a DocumentDelete or a DocumentChange if the server +/// can not send the new value of the document. +/// +/// Multiple `DocumentRemove` messages may be +/// returned for the same logical write or delete, if multiple targets are +/// affected. +final class DocumentRemove extends ProtoMessage { + static const String fullyQualifiedName = 'google.firestore.v1.DocumentRemove'; + + /// The resource name of the `Document` that has + /// gone out of view. + final String document; + + /// A set of target IDs for targets that previously matched this document. + final List removedTargetIds; + + /// The read timestamp at which the remove was observed. + /// + /// Greater or equal to the `commit_time` of the change/delete/remove. + final Timestamp? readTime; + + DocumentRemove({ + this.document = '', + this.removedTargetIds = const [], + this.readTime, + }) : super(fullyQualifiedName); + + factory DocumentRemove.fromJson(Object? j) { + final json = j as Map; + return DocumentRemove( + document: switch (json['document']) { + null => '', + Object $1 => decodeString($1), + }, + removedTargetIds: switch (json['removedTargetIds']) { + null => [], + List $1 => [for (final i in $1) decodeInt(i)], + _ => throw const FormatException('"removedTargetIds" is not a list'), + }, + readTime: switch (json['readTime']) { + null => null, + Object $1 => Timestamp.fromJson($1), + }, + ); + } + + @override + Object toJson() => { + if (document.isNotDefault) 'document': document, + if (removedTargetIds.isNotDefault) 'removedTargetIds': removedTargetIds, + if (readTime case final readTime?) 'readTime': readTime.toJson(), + }; + + @override + String toString() { + final $contents = ['document=$document'].join(','); + return 'DocumentRemove(${$contents})'; + } +} + +/// A digest of all the documents that match a given target. +final class ExistenceFilter extends ProtoMessage { + static const String fullyQualifiedName = + 'google.firestore.v1.ExistenceFilter'; + + /// The target ID to which this filter applies. + final int targetId; + + /// The total count of documents that match + /// `target_id`. + /// + /// If different from the count of documents in the client that match, the + /// client must manually determine which documents no longer match the target. + /// + /// The client can use the `unchanged_names` bloom filter to assist with + /// this determination by testing ALL the document names against the filter; + /// if the document name is NOT in the filter, it means the document no + /// longer matches the target. + final int count; + + /// A bloom filter that, despite its name, contains the UTF-8 byte encodings of + /// the resource names of ALL the documents that match + /// `target_id`, in the form + /// `projects/{project_id}/databases/{database_id}/documents/{document_path}`. + /// + /// This bloom filter may be omitted at the server's discretion, such as if it + /// is deemed that the client will not make use of it or if it is too + /// computationally expensive to calculate or transmit. Clients must gracefully + /// handle this field being absent by falling back to the logic used before + /// this field existed; that is, re-add the target without a resume token to + /// figure out which documents in the client's cache are out of sync. + final BloomFilter? unchangedNames; + + ExistenceFilter({this.targetId = 0, this.count = 0, this.unchangedNames}) + : super(fullyQualifiedName); + + factory ExistenceFilter.fromJson(Object? j) { + final json = j as Map; + return ExistenceFilter( + targetId: switch (json['targetId']) { + null => 0, + Object $1 => decodeInt($1), + }, + count: switch (json['count']) { + null => 0, + Object $1 => decodeInt($1), + }, + unchangedNames: switch (json['unchangedNames']) { + null => null, + Object $1 => BloomFilter.fromJson($1), + }, + ); + } + + @override + Object toJson() => { + if (targetId.isNotDefault) 'targetId': targetId, + if (count.isNotDefault) 'count': count, + if (unchangedNames case final unchangedNames?) + 'unchangedNames': unchangedNames.toJson(), + }; + + @override + String toString() { + final $contents = ['targetId=$targetId', 'count=$count'].join(','); + return 'ExistenceFilter(${$contents})'; + } +} diff --git a/generated/google_cloud_firestore_v1/lib/testing.dart b/generated/google_cloud_firestore_v1/lib/testing.dart new file mode 100644 index 00000000..4e44c8d0 --- /dev/null +++ b/generated/google_cloud_firestore_v1/lib/testing.dart @@ -0,0 +1,20 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Code generated by sidekick. DO NOT EDIT. + +/// Testing fakes for Cloud Firestore API. +library; + +export 'src/api.g.dart' show FakeFirestore; diff --git a/generated/google_cloud_firestore_v1/pubspec.yaml b/generated/google_cloud_firestore_v1/pubspec.yaml new file mode 100644 index 00000000..943cd876 --- /dev/null +++ b/generated/google_cloud_firestore_v1/pubspec.yaml @@ -0,0 +1,38 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Code generated by sidekick. DO NOT EDIT. + +name: google_cloud_firestore_v1 +description: The Google Cloud client library for the Cloud Firestore API. +version: 0.5.1 +repository: https://github.com/googleapis/google-cloud-dart/tree/main/generated/google_cloud_firestore_v1 +issue_tracker: https://github.com/googleapis/google-cloud-dart/issues + +environment: + sdk: ^3.9.0 + +resolution: workspace + +dependencies: + google_cloud_longrunning: ^0.5.1 + google_cloud_protobuf: ^0.5.1 + google_cloud_rpc: ^0.5.1 + google_cloud_type: ^0.5.1 + http: ^1.3.0 + +dev_dependencies: + googleapis_auth: any + test: any + test_utils: any diff --git a/generated/google_cloud_firestore_v1/test/firestore_test.dart b/generated/google_cloud_firestore_v1/test/firestore_test.dart new file mode 100644 index 00000000..e868286f --- /dev/null +++ b/generated/google_cloud_firestore_v1/test/firestore_test.dart @@ -0,0 +1,114 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +@TestOn('vm') +library; + +import 'package:google_cloud_firestore_v1/firestore.dart'; +import 'package:googleapis_auth/auth_io.dart' as auth; +import 'package:http/http.dart' as http; +import 'package:test/test.dart'; +import 'package:test_utils/cloud.dart'; + +const databaseId = '(default)'; + +void firestoreTest(Future Function() createFirestore) { + late Firestore firestoreService; + + setUp(() async { + firestoreService = await createFirestore(); + }); + + tearDown(() => firestoreService.close()); + + test('getDocument', () async { + final r = await firestoreService.createDocument( + CreateDocumentRequest( + parent: 'projects/$projectId/databases/$databaseId/documents', + collectionId: 'users', + document: Document(fields: {'firstName': Value(stringValue: 'Brian')}), + ), + ); + addTearDown( + () => + firestoreService.deleteDocument(DeleteDocumentRequest(name: r.name)), + ); + + final doc = await firestoreService.getDocument( + GetDocumentRequest(name: r.name), + ); + expect(doc.fields['firstName']?.stringValue, 'Brian'); + }); + + test('runQuery', () async { + final r = await firestoreService.createDocument( + CreateDocumentRequest( + parent: 'projects/$projectId/databases/$databaseId/documents', + collectionId: 'users', + document: Document(fields: {'firstName': Value(stringValue: 'Brian')}), + ), + ); + addTearDown( + () => + firestoreService.deleteDocument(DeleteDocumentRequest(name: r.name)), + ); + + final stream = firestoreService.runQuery( + RunQueryRequest( + parent: 'projects/$projectId/databases/$databaseId/documents', + structuredQuery: StructuredQuery( + from: [StructuredQuery_CollectionSelector(collectionId: 'users')], + where: StructuredQuery_Filter( + fieldFilter: StructuredQuery_FieldFilter( + field: StructuredQuery_FieldReference(fieldPath: 'firstName'), + op: StructuredQuery_FieldFilter_Operator.equal, + value: Value(stringValue: 'Brian'), + ), + ), + ), + ), + ); + + final responses = await stream.toList(); + expect( + responses, + contains( + isA().having((r) => r.document!.name, 'name', r.name), + ), + ); + }); +} + +void main() { + group('firestore_v1', () { + group('google-cloud', tags: ['google-cloud'], () { + firestoreTest( + () async => Firestore( + client: await auth.clientViaApplicationDefaultCredentials( + scopes: ['https://www.googleapis.com/auth/cloud-platform'], + ), + ), + ); + }); + + group('firebase-emulator', tags: ['firebase-emulator'], () { + firestoreTest( + () async => Firestore( + client: http.Client(), + endPoint: Uri.http('127.0.0.1:8080'), + ), + ); + }); + }); +} diff --git a/librarian.yaml b/librarian.yaml index bc183bb4..33c39f82 100644 --- a/librarian.yaml +++ b/librarian.yaml @@ -267,6 +267,21 @@ libraries: description_override: Additional metadata for operations. dart: repository_url: https://github.com/googleapis/google-cloud-dart/tree/main/generated/google_cloud_common + - name: google_cloud_firestore_v1 + apis: + - path: google/firestore/v1 + copyright_year: "2026" + keep: + - dart_test.yaml + - example/main.dart + - test/firestore_test.dart + dart: + dev_dependencies: googleapis_auth,test,test_utils + readme_after_title_text: |- + > [!TIP] + > Most applications should use the higher-level + > [`package:google_cloud_firestore`](https://pub.dev/packages/google_cloud_firestore). + repository_url: https://github.com/googleapis/google-cloud-dart/tree/main/generated/google_cloud_firestore_v1 - name: google_cloud_functions_v2 copyright_year: "2025" dart: diff --git a/pkgs/google_cloud_storage/emulator_test/README.md b/pkgs/google_cloud_storage/emulator_test/README.md deleted file mode 100644 index 8d37ab34..00000000 --- a/pkgs/google_cloud_storage/emulator_test/README.md +++ /dev/null @@ -1,34 +0,0 @@ -# Google Cloud Storage Emulator Tests - -This directory contains integration tests that verify the compatibility of -`package:google_cloud_storage` with the [Firebase Storage Emulator][]. - -These tests are not meant to be comprehensive and the -[Firebase Storage Emulator][] only supports a small subset of the full -Google Cloud Storage API. - -## Running Tests Locally - -To run these tests on your machine, you need to have the emulator running in -one terminal session and execute the tests in another. - -### 1. Start the Firebase Emulator - -Navigate to this directory and start the storage emulator: - -```bash -# From pkgs/google_cloud_storage/emulator_test -firebase emulators:start -``` - -### 2. Run the Tests - -In a separate terminal, navigate to the root of the `google_cloud_storage` -package and run the tests: - -```bash -# From pkgs/google_cloud_storage -STORAGE_EMULATOR_HOST=127.0.0.1:9199 dart test emulator_test -``` - -[Firebase Storage Emulator]: https://firebase.google.com/docs/emulator-suite/connect_storage diff --git a/pkgs/google_cloud_storage/emulator_test/smoke_test.dart b/pkgs/google_cloud_storage/test/storage_emulator_test.dart similarity index 98% rename from pkgs/google_cloud_storage/emulator_test/smoke_test.dart rename to pkgs/google_cloud_storage/test/storage_emulator_test.dart index d0d59bb9..77ff03f5 100644 --- a/pkgs/google_cloud_storage/emulator_test/smoke_test.dart +++ b/pkgs/google_cloud_storage/test/storage_emulator_test.dart @@ -21,7 +21,7 @@ import 'package:google_cloud_storage/google_cloud_storage.dart'; import 'package:test/test.dart'; void main() async { - group('smoke tests', () { + group('storage emulator tests', () { test('STORAGE_EMULATOR_HOST configuration', () async { final storage = Storage(); addTearDown(storage.close); diff --git a/pubspec.yaml b/pubspec.yaml index f2929013..6054a744 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -24,6 +24,7 @@ workspace: - generated/google_cloud_aiplatform_v1beta1 - generated/google_cloud_api - generated/google_cloud_common + - generated/google_cloud_firestore_v1 - generated/google_cloud_functions_v2 - generated/google_cloud_iam_v1 - generated/google_cloud_language_v2