Skip to content

Conversation

abnegate
Copy link
Member

@abnegate abnegate commented Sep 4, 2025

What does this PR do?

(Provide a description of what this PR does.)

Test Plan

(Write your test plan here. If you changed any code, please provide us with clear instructions on how you verified your changes work.)

Related PRs and Issues

(If this PR is related to any other PR or resolves any issue or related to any issue link all related PR and issues here.)

Have you read the Contributing Guidelines on issues?

(Write your answer here.)

Summary by CodeRabbit

  • New Features
    • Added query builders for created/updated date ranges and spatial/distance filtering.
    • Added HEAD as an execution method option.
  • Breaking Changes
    • Renamed credit card type from union-china-pay to unionpay.
  • Documentation
    • Updated dependency version in README.
    • Refreshed examples with realistic data and non-empty preferences.
  • Tests
    • Added coverage for new date-range queries.
  • Chores
    • Bumped SDK to 19.0.0 and aligned reported SDK header.
    • Updated changelog for 19.0.0.

Copy link

coderabbitai bot commented Sep 4, 2025

Walkthrough

  • Bumps version to 19.0.0 in pubspec.yaml, README snippet, and x-sdk-version headers for browser and IO clients.
  • Adds Query builders: createdBetween, updatedBetween, distanceEqual/NotEqual/GreaterThan/LessThan, and geometry predicates (intersects/notIntersects, crosses/notCrosses, overlaps/notOverlaps, touches/notTouches). Adjusts notEqual internal call propagation.
  • Renames CreditCard enum value unionChinaPay → unionPay. Adds HEAD to ExecutionMethod enum.
  • Adds tests for createdBetween and updatedBetween.
  • Updates docs examples to use non-empty data/prefs payloads.
  • Applies widespread formatting changes across services, models, realtime, and misc files; in realtime_mixin, moves heartbeat start after cookie-auth block.
  • Updates CHANGELOG for 19.0.0.

Suggested reviewers

  • abnegate

Pre-merge checks (2 passed, 1 inconclusive)

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Description Check ❓ Inconclusive The pull request description remains the unedited contributor template and does not provide any substantive details about the actual changes or testing performed. Please replace the placeholder template with a concise summary of the changes introduced by this PR—such as the new createdBetween/updatedBetween methods, spatial query support, and version bump—and include how these updates were tested and why they were made.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title “Add time between queries” correctly references the addition of the new time-based between query methods but does not mention the spatial query builders or the SDK version bump included in this changeset.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cead932 and f644d26.

📒 Files selected for processing (46)
  • lib/query.dart (5 hunks)
  • lib/services/databases.dart (7 hunks)
  • lib/services/functions.dart (4 hunks)
  • lib/services/graphql.dart (2 hunks)
  • lib/services/locale.dart (8 hunks)
  • lib/services/messaging.dart (1 hunks)
  • lib/services/tables_db.dart (7 hunks)
  • lib/services/teams.dart (11 hunks)
  • lib/src/client_browser.dart (1 hunks)
  • lib/src/client_io.dart (1 hunks)
  • lib/src/client_mixin.dart (1 hunks)
  • lib/src/enums/credit_card.dart (1 hunks)
  • lib/src/enums/execution_method.dart (1 hunks)
  • lib/src/models/algo_bcrypt.dart (1 hunks)
  • lib/src/models/algo_md5.dart (1 hunks)
  • lib/src/models/algo_phpass.dart (1 hunks)
  • lib/src/models/algo_sha.dart (1 hunks)
  • lib/src/models/continent.dart (2 hunks)
  • lib/src/models/continent_list.dart (1 hunks)
  • lib/src/models/country.dart (1 hunks)
  • lib/src/models/country_list.dart (1 hunks)
  • lib/src/models/currency_list.dart (1 hunks)
  • lib/src/models/document_list.dart (1 hunks)
  • lib/src/models/execution.dart (1 hunks)
  • lib/src/models/execution_list.dart (1 hunks)
  • lib/src/models/file_list.dart (2 hunks)
  • lib/src/models/headers.dart (2 hunks)
  • lib/src/models/identity_list.dart (1 hunks)
  • lib/src/models/jwt.dart (1 hunks)
  • lib/src/models/language.dart (2 hunks)
  • lib/src/models/language_list.dart (1 hunks)
  • lib/src/models/locale_code.dart (2 hunks)
  • lib/src/models/locale_code_list.dart (1 hunks)
  • lib/src/models/log_list.dart (2 hunks)
  • lib/src/models/membership_list.dart (1 hunks)
  • lib/src/models/mfa_recovery_codes.dart (2 hunks)
  • lib/src/models/mfa_type.dart (2 hunks)
  • lib/src/models/phone_list.dart (2 hunks)
  • lib/src/models/preferences.dart (1 hunks)
  • lib/src/models/row_list.dart (2 hunks)
  • lib/src/models/session_list.dart (1 hunks)
  • lib/src/models/team_list.dart (2 hunks)
  • lib/src/realtime_io.dart (2 hunks)
  • lib/src/realtime_mixin.dart (3 hunks)
  • lib/src/realtime_response.dart (1 hunks)
  • test/query_test.dart (2 hunks)
✅ Files skipped from review due to trivial changes (14)
  • lib/src/models/document_list.dart
  • lib/src/client_io.dart
  • lib/src/models/algo_phpass.dart
  • lib/src/models/row_list.dart
  • lib/src/models/mfa_recovery_codes.dart
  • lib/src/models/locale_code.dart
  • lib/src/realtime_response.dart
  • lib/src/models/country_list.dart
  • lib/src/models/country.dart
  • lib/src/client_mixin.dart
  • lib/src/models/identity_list.dart
  • lib/services/tables_db.dart
  • lib/src/models/locale_code_list.dart
  • lib/src/models/algo_md5.dart
🚧 Files skipped from review as they are similar to previous changes (31)
  • lib/src/enums/credit_card.dart
  • lib/src/models/language_list.dart
  • lib/src/models/algo_sha.dart
  • lib/src/models/continent.dart
  • lib/src/models/phone_list.dart
  • lib/src/models/file_list.dart
  • lib/src/models/log_list.dart
  • lib/src/models/session_list.dart
  • lib/src/models/jwt.dart
  • lib/src/models/team_list.dart
  • lib/src/realtime_io.dart
  • lib/src/models/execution_list.dart
  • lib/src/models/headers.dart
  • lib/src/client_browser.dart
  • test/query_test.dart
  • lib/src/models/membership_list.dart
  • lib/src/models/execution.dart
  • lib/src/models/mfa_type.dart
  • lib/src/models/currency_list.dart
  • lib/src/models/algo_bcrypt.dart
  • lib/src/models/preferences.dart
  • lib/src/enums/execution_method.dart
  • lib/src/models/continent_list.dart
  • lib/services/databases.dart
  • lib/services/messaging.dart
  • lib/services/teams.dart
  • lib/services/locale.dart
  • lib/services/graphql.dart
  • lib/src/models/language.dart
  • lib/src/realtime_mixin.dart
  • lib/services/functions.dart
🔇 Additional comments (2)
lib/query.dart (2)

11-25: toJson normalization looks good

Using a single local map and wrapping non-list values prevents accidental double-wrapping and pairs well with the fix to notEqual below.


38-41: Fix: notEqual no longer double-wraps values

Passing value (not [value]) avoids emitting [[...]] in JSON. Good catch.

✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dev

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (5)
README.md (1)

150-153: Fix stray '>>' in example.
Example shows '<YOUR_PROJECT_ID>>'; drop the extra '>' to avoid copy/paste errors.

-Client client = Client().setProject('<YOUR_PROJECT_ID>>');
+Client client = Client().setProject('<YOUR_PROJECT_ID>');
lib/src/client_io.dart (1)

61-61: Header version aligned; consider centralizing.
To avoid future drift across clients, source the SDK version from a single constant.

-      'x-sdk-version': '18.1.0',
+      'x-sdk-version': ClientBase.sdkVersion,

Add once (example):

// in client_base.dart
abstract class ClientBase {
  static const String sdkVersion = '18.1.0';
  // ...
}
lib/src/client_browser.dart (1)

43-43: Browser header version aligned; same centralization suggestion.
Keep version single-sourced to prevent mismatches.

-      'x-sdk-version': '18.1.0',
+      'x-sdk-version': ClientBase.sdkVersion,
lib/services/account.dart (1)

29-31: Fix user-facing typos in docs.

Minor text nits visible in generated docs. Suggested fixes:

- /// [/account/verfication](https://appwrite.io/docs/references/cloud/client-web/account#createVerification)
+ /// [/account/verification](https://appwrite.io/docs/references/cloud/client-web/account#createVerification)
- /// List the factors available on the account to be used as a MFA challange.
+ /// List the factors available on the account to be used as a MFA challenge.
- /// generate and show then immediately after user successfully adds their
- /// authehticator.
+ /// generate and show them immediately after user successfully adds their
+ /// authenticator.
- /// An OTP challenge is required to regenreate recovery codes.
+ /// An OTP challenge is required to regenerate recovery codes.
- /// provided user ID has not be registered, a new user will be created.
+ /// provided user ID has not been registered, a new user will be created.

Also applies to: 451-454, 471-474, 536-538, 561-564, 584-586, 608-610, 1336-1339

lib/src/enums/execution_method.dart (1)

9-11: No exhaustive switch on ExecutionMethod found; adding hEAD is safe.

  • Optionally add a unit test asserting ExecutionMethod.hEAD.toJson() == 'HEAD'.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between d1e9a39 and 32dc4cb.

📒 Files selected for processing (9)
  • README.md (1 hunks)
  • lib/query.dart (2 hunks)
  • lib/services/account.dart (2 hunks)
  • lib/src/client_browser.dart (1 hunks)
  • lib/src/client_io.dart (1 hunks)
  • lib/src/enums/credit_card.dart (1 hunks)
  • lib/src/enums/execution_method.dart (1 hunks)
  • pubspec.yaml (1 hunks)
  • test/query_test.dart (2 hunks)
🔇 Additional comments (7)
pubspec.yaml (1)

2-2: Version bump confirmed — SDK headers aligned to 18.1.0
Ensure CHANGELOG.md and the Git release tag reflect this update.

README.md (1)

24-24: Dependency snippet bump LGTM.
Snippet matches pubspec 18.1.0.

lib/services/account.dart (1)

893-895: Align deprecation version or confirm intentional

  • In lib/services/account.dart at lines 893 and 981 the messages cite “since 1.6.0” whereas all other @deprecated annotations use “since 1.8.0”. If createSession was indeed deprecated in 1.6.0 leave it as-is; otherwise update to 1.8.0.
  • Consider appending the deprecated endpoint path (POST /account/sessions/token) to the message for clearer migration guidance.
lib/query.dart (2)

114-117: createdBetween(): LGTM.

API mirrors createdBefore/After and encodes values as a 2-item list with null attribute as expected.


126-129: updatedBetween(): LGTM.

Consistent with updatedBefore/After and toJson semantics.

test/query_test.dart (2)

282-288: Test for createdBetween(): LGTM.

Covers method, attribute, and values shape.


303-308: Test for updatedBetween(): LGTM.

Matches expected wire format.

@stnguyen90 stnguyen90 self-requested a review September 5, 2025 01:26
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
lib/query.dart (1)

114-129: Housekeeping:

  • Update PR description, CHANGELOG, and README to cover both the temporal (createdBetween, updatedBefore, updatedAfter, updatedBetween) and spatial (distance*, intersects, crosses, overlaps, touches) query builders.
  • Add tests for updatedBefore, updatedAfter, and all spatial filters (distanceEqual/NotEqual/GreaterThan/LessThan, intersects/notIntersects, crosses/notCrosses, overlaps/notOverlaps, touches/notTouches).
🧹 Nitpick comments (2)
lib/query.dart (2)

114-117: Temporal createdBetween: good addition; clarify timestamp format and inclusivity in docs.
Add RFC 3339/ISO-8601 guidance and an example. Optionally validate empty strings.

-  /// Filter resources where document was created between [start] and [end] (inclusive).
+  /// Filter resources where document was created between [start] and [end] (inclusive).
+  /// Pass RFC 3339/ISO 8601 timestamps (e.g. '2025-01-01T00:00:00Z').
+  /// Example: Query.createdBetween('2025-01-01T00:00:00Z', '2025-01-31T23:59:59Z')
   static String createdBetween(String start, String end) =>
       Query._('createdBetween', null, [start, end]).toString();

126-129: Temporal updatedBetween: mirror docs from createdBetween.
Match formatting guidance and example for consistency.

-  /// Filter resources where document was updated between [start] and [end] (inclusive).
+  /// Filter resources where document was updated between [start] and [end] (inclusive).
+  /// Pass RFC 3339/ISO 8601 timestamps (e.g. '2025-01-01T00:00:00Z').
+  /// Example: Query.updatedBetween('2025-01-01T00:00:00Z', '2025-01-31T23:59:59Z')
   static String updatedBetween(String start, String end) =>
       Query._('updatedBetween', null, [start, end]).toString();
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 8bed8aa and 47f4915.

📒 Files selected for processing (1)
  • lib/query.dart (3 hunks)
🔇 Additional comments (1)
lib/query.dart (1)

178-225: Add tests for distance builders and confirm backend unit expectations

  • I didn’t find any existing tests covering distanceEqual/distanceNotEqual/distanceGreaterThan/distanceLessThan; please add unit tests to ensure correct behavior.
  • Verify that the backend API expects parameters in the order [coordinates, distance, meters], with meters=true meaning meters (and meters=false meaning kilometers or another unit).

lib/query.dart Outdated
Comment on lines 226 to 256
/// Filter resources where [attribute] intersects with the given geometry.
static String intersects(String attribute, List<dynamic> values) =>
Query._('intersects', attribute, values).toString();

/// Filter resources where [attribute] does not intersect with the given geometry.
static String notIntersects(String attribute, List<dynamic> values) =>
Query._('notIntersects', attribute, values).toString();

/// Filter resources where [attribute] crosses the given geometry.
static String crosses(String attribute, List<dynamic> values) =>
Query._('crosses', attribute, values).toString();

/// Filter resources where [attribute] does not cross the given geometry.
static String notCrosses(String attribute, List<dynamic> values) =>
Query._('notCrosses', attribute, values).toString();

/// Filter resources where [attribute] overlaps with the given geometry.
static String overlaps(String attribute, List<dynamic> values) =>
Query._('overlaps', attribute, values).toString();

/// Filter resources where [attribute] does not overlap with the given geometry.
static String notOverlaps(String attribute, List<dynamic> values) =>
Query._('notOverlaps', attribute, values).toString();

/// Filter resources where [attribute] touches the given geometry.
static String touches(String attribute, List<dynamic> values) =>
Query._('touches', attribute, values).toString();

/// Filter resources where [attribute] does not touch the given geometry.
static String notTouches(String attribute, List<dynamic> values) =>
Query._('notTouches', attribute, values).toString();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Geometry relation builders: clarify geometry format; optional rename and DRY.

  • Document expected geometry (e.g., GeoJSON Polygon/LineString/Point).
  • Optional: rename values -> geometry for readability; factor a small helper.
+  // Helper for geometry relation queries.
+  static String _rel(String method, String attribute, List<dynamic> geometry) =>
+      Query._(method, attribute, geometry).toString();
+
-  /// Filter resources where [attribute] intersects with the given geometry.
-  static String intersects(String attribute, List<dynamic> values) =>
-      Query._('intersects', attribute, values).toString();
+  /// Filter resources where [attribute] intersects with the given geometry.
+  /// [geometry] should be a GeoJSON-like structure (e.g., Point, LineString, Polygon).
+  static String intersects(String attribute, List<dynamic> geometry) =>
+      _rel('intersects', attribute, geometry);
 
-  /// Filter resources where [attribute] does not intersect with the given geometry.
-  static String notIntersects(String attribute, List<dynamic> values) =>
-      Query._('notIntersects', attribute, values).toString();
+  /// Filter resources where [attribute] does not intersect with the given geometry.
+  static String notIntersects(String attribute, List<dynamic> geometry) =>
+      _rel('notIntersects', attribute, geometry);
 
-  /// Filter resources where [attribute] crosses the given geometry.
-  static String crosses(String attribute, List<dynamic> values) =>
-      Query._('crosses', attribute, values).toString();
+  /// Filter resources where [attribute] crosses the given geometry.
+  static String crosses(String attribute, List<dynamic> geometry) =>
+      _rel('crosses', attribute, geometry);
 
-  /// Filter resources where [attribute] does not cross the given geometry.
-  static String notCrosses(String attribute, List<dynamic> values) =>
-      Query._('notCrosses', attribute, values).toString();
+  /// Filter resources where [attribute] does not cross the given geometry.
+  static String notCrosses(String attribute, List<dynamic> geometry) =>
+      _rel('notCrosses', attribute, geometry);
 
-  /// Filter resources where [attribute] overlaps with the given geometry.
-  static String overlaps(String attribute, List<dynamic> values) =>
-      Query._('overlaps', attribute, values).toString();
+  /// Filter resources where [attribute] overlaps with the given geometry.
+  static String overlaps(String attribute, List<dynamic> geometry) =>
+      _rel('overlaps', attribute, geometry);
 
-  /// Filter resources where [attribute] does not overlap with the given geometry.
-  static String notOverlaps(String attribute, List<dynamic> values) =>
-      Query._('notOverlaps', attribute, values).toString();
+  /// Filter resources where [attribute] does not overlap with the given geometry.
+  static String notOverlaps(String attribute, List<dynamic> geometry) =>
+      _rel('notOverlaps', attribute, geometry);
 
-  /// Filter resources where [attribute] touches the given geometry.
-  static String touches(String attribute, List<dynamic> values) =>
-      Query._('touches', attribute, values).toString();
+  /// Filter resources where [attribute] touches the given geometry.
+  static String touches(String attribute, List<dynamic> geometry) =>
+      _rel('touches', attribute, geometry);
 
-  /// Filter resources where [attribute] does not touch the given geometry.
-  static String notTouches(String attribute, List<dynamic> values) =>
-      Query._('notTouches', attribute, values).toString();
+  /// Filter resources where [attribute] does not touch the given geometry.
+  static String notTouches(String attribute, List<dynamic> geometry) =>
+      _rel('notTouches', attribute, geometry);

To confirm API parity and naming:

Add tests similar to existing temporal tests to assert method names and payload shapes for each geometry builder.


🌐 Web query:

Appwrite v19 Query API: do geometry relation filters exist with names: intersects, notIntersects, crosses, notCrosses, overlaps, notOverlaps, touches, notTouches? What is the expected geometry payload shape?

💡 Result:

Short answer: No — Appwrite v19 (the current Query API) does not provide built‑in geometry relation filters named intersects / notIntersects / crosses / notCrosses / overlaps / notOverlaps / touches / notTouches. The official Query docs list standard comparison, string, null and array operators but do not include spatial relation operators. [1]

What exists / roadmap notes: Appwrite team discussions indicate geo queries are considered (examples like Query.geoSphere(...) and Query.geoPolygon(...) were proposed) but these are future ideas rather than implemented query operators. [2][3]

Expected payload shape: because geometry/spatial queries are not implemented in v19 there is no documented, supported geometry payload shape in the official Query API. The only available guidance is community/roadmap examples (e.g. passing a point/radius or polygon coordinates as arguments in the proposed Query.geo* APIs), but these are not official specs. If you need spatial queries today you must implement them yourself (store GeoJSON in a field and evaluate geometry relations in your application or a cloud function) or use a DB that supports spatial queries (PostGIS, MongoDB geo queries, etc.). [2][3]

Sources:

  • Appwrite Queries documentation (current Query API operator list). [1]
  • Appwrite discussions / roadmap mentioning geo query ideas (Query.geoSphere, Query.geoPolygon) and community threads. [2][3]

Remove unsupported spatial query methods
The Appwrite v19 Query API does not support spatial relation operators (intersects, notIntersects, crosses, notCrosses, overlaps, notOverlaps, touches, notTouches) (appwrite.io). Remove these methods or mark them as experimental until backend support is available.

🤖 Prompt for AI Agents
In lib/query.dart around lines 226-256 the eight spatial relation methods
(intersects, notIntersects, crosses, notCrosses, overlaps, notOverlaps, touches,
notTouches) are not supported by Appwrite v19; remove these static methods from
the Query class and delete or update any usages/tests/docs referencing them. If
you prefer to keep placeholders for API parity instead of removal, annotate each
method with a clear @Deprecated message and make them immediately throw an
UnsupportedError (or similar) with a message pointing to Appwrite v19 lack of
support; also update documentation and tests accordingly.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
lib/src/realtime_response.dart (1)

53-55: hashCode is inconsistent with operator==; compute from key/value pairs.

mapEquals does shallow content comparison, but data.hashCode is identity-based. Equal objects can yield different hashes.

Apply:

-  @override
-  int get hashCode => type.hashCode ^ data.hashCode;
+  @override
+  int get hashCode {
+    final entriesHash = Object.hashAllUnordered(
+      data.entries.map((e) => Object.hash(e.key, e.value)),
+    );
+    return Object.hash(type, entriesHash);
+  }
lib/services/account.dart (1)

28-31: Fix typos and grammar in user-facing docs.

These appear in generated docs; please correct.

- /// [/account/verfication](https://appwrite.io/docs/references/cloud/client-web/account#createVerification)
+ /// [/account/verification](https://appwrite.io/docs/references/cloud/client-web/account#createVerification)
...
- /// List the factors available on the account to be used as a MFA challange.
+ /// List the factors available on the account to be used as a MFA challenge.
...
- /// authehticator. Recovery codes can be used as a MFA verification type in
+ /// authenticator. Recovery codes can be used as an MFA verification type in
...
- /// method. An OTP challenge is required to regenreate recovery codes.
+ /// method. An OTP challenge is required to regenerate recovery codes.
...
- /// first. Use the success and failure arguments to provide a redirect URL's
+ /// first. Use the success and failure arguments to provide redirect URLs
...
- /// back to your app when login is completed. 
+ /// back to your app when login completes.
...
- /// provided user ID has not be registered, a new user will be created. Use the
+ /// provided user ID has not been registered, a new user will be created. Use the
...
- /// verify the user email ownership. If confirmed this route will return a 200
+ /// verify the user's phone number ownership. If confirmed this route will return a 200

Also applies to: 176-178, 213-217, 376-379, 452-459, 496-503, 795-803, 1098-1105, 1148-1153, 1253-1256

♻️ Duplicate comments (2)
lib/query.dart (2)

68-71: Docs are correct re: inclusive/exclusive behavior.

between is inclusive; notBetween excludes boundaries. Your comments match current docs — keep as-is. (appwrite.io)

Also applies to: 94-97


194-224: Remove geometry relation helpers — not implemented in Appwrite.

Operators like intersects/crosses/overlaps/touches are not part of the documented Query API. Shipping them will create runtime “Invalid query: method not valid” errors. (appwrite.io)

Apply:

-  /// Filter resources where [attribute] intersects with the given geometry.
-  static String intersects(String attribute, List<dynamic> values) =>
-      Query._('intersects', attribute, values).toString();
-
-  /// Filter resources where [attribute] does not intersect with the given geometry.
-  static String notIntersects(String attribute, List<dynamic> values) =>
-      Query._('notIntersects', attribute, values).toString();
-
-  /// Filter resources where [attribute] crosses the given geometry.
-  static String crosses(String attribute, List<dynamic> values) =>
-      Query._('crosses', attribute, values).toString();
-
-  /// Filter resources where [attribute] does not cross the given geometry.
-  static String notCrosses(String attribute, List<dynamic> values) =>
-      Query._('notCrosses', attribute, values).toString();
-
-  /// Filter resources where [attribute] overlaps with the given geometry.
-  static String overlaps(String attribute, List<dynamic> values) =>
-      Query._('overlaps', attribute, values).toString();
-
-  /// Filter resources where [attribute] does not overlap with the given geometry.
-  static String notOverlaps(String attribute, List<dynamic> values) =>
-      Query._('notOverlaps', attribute, values).toString();
-
-  /// Filter resources where [attribute] touches the given geometry.
-  static String touches(String attribute, List<dynamic> values) =>
-      Query._('touches', attribute, values).toString();
-
-  /// Filter resources where [attribute] does not touch the given geometry.
-  static String notTouches(String attribute, List<dynamic> values) =>
-      Query._('notTouches', attribute, values).toString();
🧹 Nitpick comments (16)
lib/src/realtime_io.dart (2)

35-38: Rename _websokchannel for clarity.

Minor readability polish.

-    final _websok = IOWebSocketChannel((client as ClientIO).selfSigned
+    final channel = IOWebSocketChannel((client as ClientIO).selfSigned
@@
-    return _websok;
+    return channel;

60-62: Gate noisy debug log to debug builds only.

Avoid emitting “Allow self-signed certificate” in release.

-        debugPrint('AppwriteRealtime: Allow self-signed certificate');
-        return true;
+        if (kDebugMode) {
+          debugPrint('AppwriteRealtime: Allow self-signed certificate');
+        }
+        return true;
lib/src/client_io.dart (1)

61-63: Approve version bump
Confirmed ‘x-sdk-version’: ‘19.0.0’ in client_io.dart, client_browser.dart, README.md, and pubspec.yaml. Extraction of the SDK version into a shared constant is recommended to prevent future drift.

lib/src/realtime_response.dart (1)

7-10: Consider making data unmodifiable to prevent post-hash mutation.

Freezing guards against subtle map mutations after insertion into hashed collections.

Apply:

-  RealtimeResponse({
-    required this.type,
-    required this.data,
-  });
+  RealtimeResponse({
+    required this.type,
+    required Map<String, dynamic> data,
+  }) : data = Map.unmodifiable(data);

And in copyWith (Lines 17-20):

-    return RealtimeResponse(
-      type: type ?? this.type,
-      data: data ?? this.data,
-    );
+    return RealtimeResponse(
+      type: type ?? this.type,
+      data: data != null ? Map.unmodifiable(data) : this.data,
+    );

Optionally also wrap in fromMap: data: Map.unmodifiable(Map<String, dynamic>.from(map['data'] ?? {})),

lib/services/teams.dart (2)

274-279: Type prefs as Map<String, dynamic> for stronger typing.

Avoid raw Map to catch type errors at compile time.

-Future<models.Preferences> updatePrefs({required String teamId, required Map prefs}) async {
+Future<models.Preferences> updatePrefs({required String teamId, required Map<String, dynamic> prefs}) async {

98-101: Minor nit: extra space before res.data.

Tidy return statements.

-        return  res.data;
+        return res.data;

Also applies to: 221-224

lib/src/realtime_mixin.dart (3)

40-47: Guard heartbeat pings when socket is closed.

Prevent writes after close by checking closeCode == null.

-    _heartbeatTimer = Timer.periodic(Duration(seconds: 20), (_) {
-      if (_websok != null) {
-        _websok!.sink.add(jsonEncode({
+    _heartbeatTimer = Timer.periodic(const Duration(seconds: 20), (_) {
+      final ws = _websok;
+      if (ws != null && ws.closeCode == null) {
+        ws.sink.add(jsonEncode({
           "type": "ping"
         }));
       }
     });

164-168: Verify multi-value query encoding for channels.

Passing a List as a value in queryParameters may not serialize as repeated keys. Consider building the query manually (like in OAuth methods) or using queryParametersAll if supported.

-    return Uri(
+    final channels = _channels.toList();
+    return Uri(
       host: uri.host,
       scheme: uri.scheme,
       port: uri.port,
-      queryParameters: {
-        "project": client.config['project'],
-        "channels[]": _channels.toList(),
-      },
+      query: [
+        'project=${Uri.encodeComponent(client.config['project'] ?? '')}',
+        for (final c in channels) 'channels[]=${Uri.encodeComponent(c)}',
+      ].join('&'),
       path: uri.path + "/realtime",
     );

Run the test app against a project with multiple channels and confirm the server receives all channels.


205-209: Error handling uses WebSocket close code constant against API error code.

response.data['code'] may not match status.policyViolation (1008). Prefer inspecting the WS closeCode or server-defined error codes.

-  if (response.data['code'] == status.policyViolation) {
+  final isPolicyViolation = client.realtimeCloseCode == status.policyViolation
+    || response.data['code'] == status.policyViolation; // if server aligns
+  if (isPolicyViolation) {
     throw AppwriteException(response.data["message"], response.data["code"]);

If no aligned code exists, treat all errors with _retry() and let onDone handle close codes.

lib/services/account.dart (4)

618-627: Strongly type prefs Map.

Prefer Map<String, dynamic> to avoid runtime surprises.

-Future<models.User> updatePrefs({required Map prefs}) async {
+Future<models.User> updatePrefs({required Map<String, dynamic> prefs}) async {

823-835: Type query list as List.

Prevents dynamic leaks.

-        final List query = [];
+        final List<String> query = [];

Also applies to: 1124-1136


843-845: Confirm callbackUrlScheme usage.

callbackUrlScheme usually expects a scheme (e.g., myapp). Passing the full success URL may be wrong. Consider callbackUrlScheme: success != null ? Uri.parse(success).scheme : null.

-        return client.webAuth(url, callbackUrlScheme: success);
+        return client.webAuth(url, callbackUrlScheme: success != null ? Uri.parse(success).scheme : null);

Please verify on iOS/Android with a real redirect.

Also applies to: 1144-1145


103-106: Unnecessary content-type on GET/DELETE.

Harmless but noisy; consider omitting for non-body requests to reduce header churn.

Also applies to: 279-283, 300-304, 716-720, 980-984, 1021-1025, 1197-1201, 1243-1247, 1265-1269

lib/services/databases.dart (1)

125-131: Nit: avoid Content-Type on GET/DELETE + stray double-space in return.

  • The explicit 'content-type': 'application/json' header on GET/DELETE is unnecessary.
  • Minor formatting nit: two spaces in return res.data;.

Apply this diff:

-        final Map<String, String> apiHeaders = {
-            'content-type': 'application/json',
-        };
+        final Map<String, String> apiHeaders = {
+        };
@@
-        return  res.data;
+        return res.data;
lib/services/tables_db.dart (1)

117-123: Nit: avoid Content-Type on DELETE + stray double-space in return.

-        final Map<String, String> apiHeaders = {
-            'content-type': 'application/json',
-        };
+        final Map<String, String> apiHeaders = {
+        };
@@
-        return  res.data;
+        return res.data;
lib/query.dart (1)

28-35: Minor consistency: equal vs notEqual wrapping.

Optional: pass raw value to notEqual and let toJson normalize, mirroring equal(). Behavior unchanged.

-  static String notEqual(String attribute, dynamic value) =>
-      Query._('notEqual', attribute, [value]).toString();
+  static String notEqual(String attribute, dynamic value) =>
+      Query._('notEqual', attribute, value).toString();

Also applies to: 36-43

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 47f4915 and 793a68d.

📒 Files selected for processing (92)
  • docs/examples/account/update-prefs.md (1 hunks)
  • docs/examples/databases/create-document.md (1 hunks)
  • docs/examples/tablesdb/create-row.md (1 hunks)
  • lib/appwrite.dart (1 hunks)
  • lib/client_browser.dart (1 hunks)
  • lib/client_io.dart (1 hunks)
  • lib/query.dart (6 hunks)
  • lib/realtime_browser.dart (1 hunks)
  • lib/realtime_io.dart (1 hunks)
  • lib/role.dart (1 hunks)
  • lib/services/account.dart (18 hunks)
  • lib/services/avatars.dart (2 hunks)
  • lib/services/databases.dart (1 hunks)
  • lib/services/functions.dart (1 hunks)
  • lib/services/graphql.dart (2 hunks)
  • lib/services/locale.dart (3 hunks)
  • lib/services/messaging.dart (1 hunks)
  • lib/services/storage.dart (1 hunks)
  • lib/services/tables_db.dart (1 hunks)
  • lib/services/teams.dart (3 hunks)
  • lib/src/client_base.dart (0 hunks)
  • lib/src/client_browser.dart (1 hunks)
  • lib/src/client_io.dart (1 hunks)
  • lib/src/client_mixin.dart (2 hunks)
  • lib/src/cookie_manager.dart (2 hunks)
  • lib/src/enums.dart (1 hunks)
  • lib/src/enums/authentication_factor.dart (1 hunks)
  • lib/src/enums/authenticator_type.dart (1 hunks)
  • lib/src/enums/browser.dart (1 hunks)
  • lib/src/enums/credit_card.dart (1 hunks)
  • lib/src/enums/execution_method.dart (1 hunks)
  • lib/src/enums/flag.dart (1 hunks)
  • lib/src/enums/image_format.dart (1 hunks)
  • lib/src/enums/image_gravity.dart (1 hunks)
  • lib/src/enums/o_auth_provider.dart (1 hunks)
  • lib/src/exception.dart (1 hunks)
  • lib/src/models/algo_argon2.dart (1 hunks)
  • lib/src/models/algo_bcrypt.dart (1 hunks)
  • lib/src/models/algo_md5.dart (1 hunks)
  • lib/src/models/algo_phpass.dart (1 hunks)
  • lib/src/models/algo_scrypt.dart (1 hunks)
  • lib/src/models/algo_scrypt_modified.dart (1 hunks)
  • lib/src/models/algo_sha.dart (1 hunks)
  • lib/src/models/continent.dart (1 hunks)
  • lib/src/models/continent_list.dart (1 hunks)
  • lib/src/models/country.dart (1 hunks)
  • lib/src/models/country_list.dart (1 hunks)
  • lib/src/models/currency.dart (1 hunks)
  • lib/src/models/currency_list.dart (1 hunks)
  • lib/src/models/document.dart (1 hunks)
  • lib/src/models/document_list.dart (1 hunks)
  • lib/src/models/execution.dart (1 hunks)
  • lib/src/models/execution_list.dart (1 hunks)
  • lib/src/models/file.dart (1 hunks)
  • lib/src/models/file_list.dart (1 hunks)
  • lib/src/models/headers.dart (1 hunks)
  • lib/src/models/identity.dart (1 hunks)
  • lib/src/models/identity_list.dart (1 hunks)
  • lib/src/models/jwt.dart (1 hunks)
  • lib/src/models/language.dart (1 hunks)
  • lib/src/models/language_list.dart (1 hunks)
  • lib/src/models/locale.dart (1 hunks)
  • lib/src/models/locale_code.dart (1 hunks)
  • lib/src/models/locale_code_list.dart (1 hunks)
  • lib/src/models/log.dart (1 hunks)
  • lib/src/models/log_list.dart (1 hunks)
  • lib/src/models/membership.dart (1 hunks)
  • lib/src/models/membership_list.dart (1 hunks)
  • lib/src/models/mfa_challenge.dart (1 hunks)
  • lib/src/models/mfa_factors.dart (1 hunks)
  • lib/src/models/mfa_recovery_codes.dart (1 hunks)
  • lib/src/models/mfa_type.dart (1 hunks)
  • lib/src/models/model.dart (1 hunks)
  • lib/src/models/phone.dart (1 hunks)
  • lib/src/models/phone_list.dart (1 hunks)
  • lib/src/models/preferences.dart (1 hunks)
  • lib/src/models/row.dart (1 hunks)
  • lib/src/models/row_list.dart (1 hunks)
  • lib/src/models/session.dart (1 hunks)
  • lib/src/models/session_list.dart (1 hunks)
  • lib/src/models/subscriber.dart (1 hunks)
  • lib/src/models/target.dart (1 hunks)
  • lib/src/models/team.dart (1 hunks)
  • lib/src/models/team_list.dart (1 hunks)
  • lib/src/models/token.dart (1 hunks)
  • lib/src/models/user.dart (1 hunks)
  • lib/src/realtime.dart (2 hunks)
  • lib/src/realtime_io.dart (3 hunks)
  • lib/src/realtime_message.dart (1 hunks)
  • lib/src/realtime_mixin.dart (6 hunks)
  • lib/src/realtime_response.dart (2 hunks)
  • lib/src/realtime_response_connected.dart (2 hunks)
💤 Files with no reviewable changes (1)
  • lib/src/client_base.dart
✅ Files skipped from review due to trivial changes (76)
  • lib/client_io.dart
  • lib/appwrite.dart
  • lib/src/models/row_list.dart
  • lib/src/models/model.dart
  • docs/examples/account/update-prefs.md
  • lib/src/models/algo_scrypt_modified.dart
  • lib/src/enums/image_format.dart
  • lib/src/models/algo_md5.dart
  • lib/src/models/algo_argon2.dart
  • lib/src/models/algo_bcrypt.dart
  • lib/src/models/target.dart
  • lib/src/realtime_message.dart
  • lib/src/models/algo_scrypt.dart
  • lib/src/models/team.dart
  • lib/src/models/document_list.dart
  • lib/src/models/file.dart
  • lib/src/models/log.dart
  • lib/src/enums/authenticator_type.dart
  • lib/src/models/preferences.dart
  • lib/src/models/language.dart
  • lib/src/models/execution_list.dart
  • lib/src/models/membership_list.dart
  • lib/src/realtime_response_connected.dart
  • lib/client_browser.dart
  • lib/src/models/file_list.dart
  • lib/src/models/subscriber.dart
  • lib/src/enums/authentication_factor.dart
  • lib/realtime_io.dart
  • lib/src/models/row.dart
  • lib/realtime_browser.dart
  • lib/src/models/currency.dart
  • lib/src/models/algo_phpass.dart
  • lib/src/models/locale_code.dart
  • lib/src/models/algo_sha.dart
  • lib/src/models/currency_list.dart
  • lib/src/models/locale.dart
  • lib/src/models/language_list.dart
  • lib/src/exception.dart
  • lib/src/models/locale_code_list.dart
  • lib/src/models/document.dart
  • lib/src/enums/image_gravity.dart
  • lib/services/graphql.dart
  • lib/src/models/mfa_challenge.dart
  • lib/src/models/identity_list.dart
  • lib/src/models/log_list.dart
  • lib/src/enums/o_auth_provider.dart
  • lib/src/models/phone.dart
  • lib/src/models/country.dart
  • lib/src/models/country_list.dart
  • lib/src/models/user.dart
  • lib/src/models/jwt.dart
  • lib/src/realtime.dart
  • lib/src/models/mfa_type.dart
  • lib/src/models/phone_list.dart
  • lib/src/models/continent.dart
  • lib/src/models/headers.dart
  • lib/src/cookie_manager.dart
  • lib/src/enums/flag.dart
  • lib/src/enums/browser.dart
  • lib/services/locale.dart
  • lib/src/models/execution.dart
  • lib/src/models/mfa_factors.dart
  • lib/src/enums.dart
  • lib/services/storage.dart
  • lib/src/models/membership.dart
  • lib/src/models/token.dart
  • lib/services/avatars.dart
  • lib/src/models/identity.dart
  • lib/src/client_mixin.dart
  • lib/services/functions.dart
  • lib/src/models/session.dart
  • lib/src/models/team_list.dart
  • lib/role.dart
  • lib/src/models/session_list.dart
  • lib/services/messaging.dart
  • lib/src/models/continent_list.dart
🚧 Files skipped from review as they are similar to previous changes (3)
  • lib/src/client_browser.dart
  • lib/src/enums/credit_card.dart
  • lib/src/enums/execution_method.dart
🧰 Additional context used
🪛 markdownlint-cli2 (0.17.2)
docs/examples/tablesdb/create-row.md

15-15: Bare URL used

(MD034, no-bare-urls)

🔇 Additional comments (13)
lib/src/realtime_io.dart (2)

26-26: Condensed while-condition is fine.

No semantic change; readability is acceptable.


53-54: Signature OK; ensure callers never pass null.

Given the updated typing above, keep this as Map<String, dynamic> and pass a non-null map from the caller.

docs/examples/tablesdb/create-row.md (1)

13-19: Example payload improvement LGTM.

Realistic data makes the snippet more useful.

docs/examples/databases/create-document.md (1)

13-19: Example payload improvement LGTM.

Same comment as the TablesDB snippet—good enhancement.

lib/src/realtime_response.dart (3)

24-27: LGTM on toMap formatting.

No functional changes.


39-39: LGTM on fromJson inline constructor.

No behavior change.


49-50: Equality check remains appropriate (shallow).

Consistent with the adjusted hashCode proposal.

lib/src/models/mfa_recovery_codes.dart (1)

18-22: LGTM on toMap.

Straightforward serialization.

lib/src/realtime_mixin.dart (1)

96-100: Starting heartbeat after connection is correct.

Good move; avoids pinging before auth completes.

lib/services/databases.dart (2)

11-27: LGTM on formatting-only refactor.

Signatures and deprecation annotations are consistent; runtime behavior appears unchanged.

Also applies to: 33-51, 55-71, 77-94, 98-115, 117-133, 136-153, 156-174


15-21: Ensure null-valued API parameters are removed before calling the client
client.call serializes the params Map as-is, so you must strip out entries where the value is null (e.g. with apiParams.removeWhere((_, v) => v == null)) on all methods using optional fields (lines 15–21, 59–66, 102–110 in lib/services/databases.dart).

lib/services/tables_db.dart (2)

9-23: LGTM. API paths and shapes match TablesDB REST docs.

Endpoints under /tablesdb/{databaseId}/tables/{tableId}/rows[...] align with current REST reference. (appwrite.io)

Also applies to: 30-47, 51-66, 72-88, 92-108, 111-125, 128-144, 147-163


12-18: Confirm null filtering of apiParams. Ensure any null entries are dropped—either by having client.call strip nulls or by invoking

apiParams.removeWhere((k, v) => v == null);

before the call. Applies in lib/services/tables_db.dart at lines 12–18, 54–60, and 96–103.

Comment on lines 20 to 21
permissions: ["read("any")"], // optional
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix invalid permissions syntax in Dart snippet.

Use typed builders to avoid quoting issues.

Apply:

-    permissions: ["read("any")"], // optional
+    permissions: [Permission.read(Role.any())], // optional
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
permissions: ["read("any")"], // optional
);
permissions: [Permission.read(Role.any())], // optional
);
🤖 Prompt for AI Agents
In docs/examples/databases/create-document.md around lines 20 to 21, the Dart
snippet uses invalid permissions syntax permissions: ["read("any")"] — replace
the quoted raw string with the Dart typed builder form (use the appropriate
Permission/read builder function or constructor with a plain 'any' argument) so
permissions are constructed as typed objects instead of strings; update the
example to call the Permission builder API consistently with the surrounding
snippet and remove the nested quotes/parentheses error.

Comment on lines 20 to 21
permissions: ["read("any")"], // optional
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix invalid permissions syntax in Dart snippet.

The current string quoting will not compile. Use the SDK helpers.

Apply:

-    permissions: ["read("any")"], // optional
+    permissions: [Permission.read(Role.any())], // optional
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
permissions: ["read("any")"], // optional
);
permissions: [Permission.read(Role.any())], // optional
);
🤖 Prompt for AI Agents
In docs/examples/tablesdb/create-row.md around lines 20-21, the Dart permissions
string uses invalid nested quotes and won't compile; replace the string with the
SDK permission helper (use the Permission.read helper and pass "any" as the
argument) instead of the literal "read("any")" string, and ensure the Dart
snippet imports and uses the SDK Permission helper correctly so the permissions
array contains the helper call rather than an invalid quoted string.

Comment on lines +114 to +117
/// Filter resources where document was created between [start] and [end] (inclusive).
static String createdBetween(String start, String end) =>
Query._('createdBetween', null, [start, end]).toString();

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Fix createdBetween/updatedBetween to use supported operators.

The server doesn’t expose “createdBetween”/“updatedBetween” operators. Use between() on $createdAt/$updatedAt instead to keep inclusive semantics documented in Queries. (appwrite.io)

Apply:

-  /// Filter resources where document was created between [start] and [end] (inclusive).
-  static String createdBetween(String start, String end) =>
-      Query._('createdBetween', null, [start, end]).toString();
+  /// Filter resources where document was created between [start] and [end] (inclusive).
+  static String createdBetween(String start, String end) =>
+      between(r'$createdAt', start, end);
@@
-  /// Filter resources where document was updated between [start] and [end] (inclusive).
-  static String updatedBetween(String start, String end) =>
-      Query._('updatedBetween', null, [start, end]).toString();
+  /// Filter resources where document was updated between [start] and [end] (inclusive).
+  static String updatedBetween(String start, String end) =>
+      between(r'$updatedAt', start, end);

If tests assert the JSON method name equals "createdBetween"/"updatedBetween", update them to expect method "between" and attribute "$createdAt"/"$updatedAt", respectively.

Also applies to: 126-129


🏁 Script executed:

#!/bin/bash
# Search for existing between() method and createdBetween/updatedBetween in lib/query.dart
rg -nP --type=dart 'static String between' -n lib/query.dart || true
rg -nP --type=dart 'between\(' -n lib/query.dart || true

# Locate createdBetween and updatedBetween definitions
rg -nP --type=dart 'createdBetween' -n lib/query.dart || true
rg -nP --type=dart 'updatedBetween' -n lib/query.dart || true

# Inspect Query._ constructor to see supported operators
rg -nP --type=dart 'class Query\s*{' -n lib/query.dart -A5 -B0 || true
rg -nP --type=dart 'factory Query\._' -n lib/query.dart -A3 -B3 || true

Length of output: 945


Use between operator for createdBetween/updatedBetween filters

  • lib/query.dart@115–117: replace
    Query._('createdBetween', null, [start, end]).toString();
    with
    between(r'$createdAt', start, end);
  • lib/query.dart@127–129: replace
    Query._('updatedBetween', null, [start, end]).toString();
    with
    between(r'$updatedAt', start, end);
  • Update any tests that assert method names "createdBetween"/"updatedBetween" to expect "between" with attributes "$createdAt"/"$updatedAt".
🤖 Prompt for AI Agents
In lib/query.dart around lines 114–117 and 127–129, the createdBetween and
updatedBetween helpers currently construct Query._ with operator names
"createdBetween"/"updatedBetween"; change them to call between(r'$createdAt',
start, end) and between(r'$updatedAt', start, end) respectively so the operator
used is "between" with the correct attribute; also update any tests that assert
the operator name "createdBetween"/"updatedBetween" to expect "between" and that
the attribute is "$createdAt"/"$updatedAt".

💡 Verification agent

🧩 Analysis chain

Repo-wide verification for API compatibility.

Update tests/usage to match the supported shape; remove any geometry/distance usages.

Also applies to: 126-129, 178-224


🏁 Script executed:

#!/bin/bash
# 1) Find usages of newly added helpers
rg -nP --type=dart -C2 '\b(createdBetween|updatedBetween|distance(Equal|NotEqual|GreaterThan|LessThan)|intersects|notIntersects|crosses|notCrosses|overlaps|notOverlaps|touches|notTouches)\s*\('

# 2) Show current JSON emitted by Query.createdBetween/updatedBetween
rg -nP -C3 'static\s+String\s+createdBetween|static\s+String\s+updatedBetween' lib/query.dart

# 3) Grep tests that assert method name/attribute for created/updated helpers
rg -nP -C2 'createdBetween|updatedBetween' test

Length of output: 7187


Remove unsupported geometry/distance filter methods
Delete the distance* and geometry filter helpers (distanceEqual/NotEqual/GreaterThan/LessThan and intersects/notIntersects/crosses/notCrosses/overlaps/notOverlaps/touches/notTouches) from lib/query.dart (lines 178–224).

🤖 Prompt for AI Agents
In lib/query.dart around lines 178 to 224, remove the unsupported
geometry/distance filter helper methods: all distance* methods (distanceEqual,
distanceNotEqual, distanceGreaterThan, distanceLessThan) and all geometry
intersection/relationship helpers (intersects, notIntersects, crosses,
notCrosses, overlaps, notOverlaps, touches, notTouches). Delete their method
definitions and any associated imports or references that become unused as a
result, and run a quick project build/test to ensure there are no remaining
references to these methods.

lib/query.dart Outdated
Comment on lines 178 to 193
/// Filter resources where [attribute] is at a specific distance from the given coordinates.
static String distanceEqual(String attribute, List<dynamic> values, double distance, [bool meters = true]) =>
Query._('distanceEqual', attribute, [values, distance, meters]).toString();

/// Filter resources where [attribute] is not at a specific distance from the given coordinates.
static String distanceNotEqual(String attribute, List<dynamic> values, double distance, [bool meters = true]) =>
Query._('distanceNotEqual', attribute, [values, distance, meters]).toString();

/// Filter resources where [attribute] is at a distance greater than the specified value from the given coordinates.
static String distanceGreaterThan(String attribute, List<dynamic> values, double distance, [bool meters = true]) =>
Query._('distanceGreaterThan', attribute, [values, distance, meters]).toString();

/// Filter resources where [attribute] is at a distance less than the specified value from the given coordinates.
static String distanceLessThan(String attribute, List<dynamic> values, double distance, [bool meters = true]) =>
Query._('distanceLessThan', attribute, [values, distance, meters]).toString();

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

⚠️ Potential issue

Remove distance helpers — geospatial distance queries are not supported.*

Appwrite queries don’t support distanceEqual/NotEqual/GreaterThan/LessThan operators today; recommended patterns use bounding-box comparisons on lat/long attributes client-side. Keep these out to avoid shipping unusable APIs. (appwrite.io, dev.to)

Apply:

-  /// Filter resources where [attribute] is at a specific distance from the given coordinates.
-  static String distanceEqual(String attribute, List<dynamic> values, double distance, [bool meters = true]) =>
-      Query._('distanceEqual', attribute, [values, distance, meters]).toString();
-
-  /// Filter resources where [attribute] is not at a specific distance from the given coordinates.
-  static String distanceNotEqual(String attribute, List<dynamic> values, double distance, [bool meters = true]) =>
-      Query._('distanceNotEqual', attribute, [values, distance, meters]).toString();
-
-  /// Filter resources where [attribute] is at a distance greater than the specified value from the given coordinates.
-  static String distanceGreaterThan(String attribute, List<dynamic> values, double distance, [bool meters = true]) =>
-      Query._('distanceGreaterThan', attribute, [values, distance, meters]).toString();
-
-  /// Filter resources where [attribute] is at a distance less than the specified value from the given coordinates.
-  static String distanceLessThan(String attribute, List<dynamic> values, double distance, [bool meters = true]) =>
-      Query._('distanceLessThan', attribute, [values, distance, meters]).toString();

Alternatively, gate them with @deprecated and throw UnsupportedError immediately with a link to docs.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// Filter resources where [attribute] is at a specific distance from the given coordinates.
static String distanceEqual(String attribute, List<dynamic> values, double distance, [bool meters = true]) =>
Query._('distanceEqual', attribute, [values, distance, meters]).toString();
/// Filter resources where [attribute] is not at a specific distance from the given coordinates.
static String distanceNotEqual(String attribute, List<dynamic> values, double distance, [bool meters = true]) =>
Query._('distanceNotEqual', attribute, [values, distance, meters]).toString();
/// Filter resources where [attribute] is at a distance greater than the specified value from the given coordinates.
static String distanceGreaterThan(String attribute, List<dynamic> values, double distance, [bool meters = true]) =>
Query._('distanceGreaterThan', attribute, [values, distance, meters]).toString();
/// Filter resources where [attribute] is at a distance less than the specified value from the given coordinates.
static String distanceLessThan(String attribute, List<dynamic> values, double distance, [bool meters = true]) =>
Query._('distanceLessThan', attribute, [values, distance, meters]).toString();
🤖 Prompt for AI Agents
In lib/query.dart around lines 178 to 193, the distance* helper methods expose
geospatial distance queries which Appwrite does not support; remove these four
methods (distanceEqual, distanceNotEqual, distanceGreaterThan, distanceLessThan)
from the public API OR mark each with @Deprecated and change their
implementation to immediately throw an UnsupportedError with a short message and
a link to the Appwrite docs explaining the limitation (include the docs URL), so
callers get a clear runtime error instead of an unusable API.

Comment on lines 98 to 107
final String apiPath = '/account/identities/{identityId}'.replaceAll('{identityId}', identityId);

final Map<String, dynamic> apiParams = {};
final Map<String, dynamic> apiParams = {
};

final Map<String, String> apiHeaders = {'content-type': 'application/json'};
final Map<String, String> apiHeaders = {
'content-type': 'application/json',
};

final res = await client.call(
HttpMethod.delete,
path: apiPath,
params: apiParams,
headers: apiHeaders,
);
final res = await client.call(HttpMethod.delete, path: apiPath, params: apiParams, headers: apiHeaders);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

URL-encode IDs used in paths.

Use Uri.encodeComponent to avoid path issues with unexpected characters.

- final String apiPath = '/account/identities/{identityId}'.replaceAll('{identityId}', identityId);
+ final String apiPath = '/account/identities/{identityId}'.replaceAll('{identityId}', Uri.encodeComponent(identityId));

- final String apiPath = '/account/mfa/authenticators/{type}'.replaceAll('{type}', type.value);
+ final String apiPath = '/account/mfa/authenticators/{type}'.replaceAll('{type}', Uri.encodeComponent(type.value));

- final String apiPath = '/account/sessions/{sessionId}'.replaceAll('{sessionId}', sessionId);
+ final String apiPath = '/account/sessions/{sessionId}'.replaceAll('{sessionId}', Uri.encodeComponent(sessionId));

- final String apiPath = '/account/targets/{targetId}/push'.replaceAll('{targetId}', targetId);
+ final String apiPath = '/account/targets/{targetId}/push'.replaceAll('{targetId}', Uri.encodeComponent(targetId));

Apply similarly to all other interpolated path segments in this file.

Also applies to: 272-283, 355-364, 392-399, 520-531, 571-579, 596-603, 692-705, 731-744, 891-903, 910-923, 931-943, 996-1006, 1016-1025, 1211-1223, 1258-1271

🤖 Prompt for AI Agents
In lib/services/account.dart around lines 98 to 107 (and similarly for ranges
272-283, 355-364, 392-399, 520-531, 571-579, 596-603, 692-705, 731-744, 891-903,
910-923, 931-943, 996-1006, 1016-1025, 1211-1223, 1258-1271), the identity/other
IDs interpolated directly into URL paths must be URL-encoded to avoid breaking
the path when they contain reserved characters; update each interpolated segment
to wrap the variable with Uri.encodeComponent(...) when building the apiPath
string so every path parameter is properly encoded before calling client.call.

Comment on lines 53 to 56
final String apiPath = '/teams/{teamId}'.replaceAll('{teamId}', teamId);

final Map<String, dynamic> apiParams = {};
final Map<String, dynamic> apiParams = {
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

URL-encode path params to prevent malformed paths and edge-case injection.

Wrap all interpolated IDs with Uri.encodeComponent(...) when building apiPath.

- final String apiPath = '/teams/{teamId}'.replaceAll('{teamId}', teamId);
+ final String apiPath = '/teams/{teamId}'.replaceAll('{teamId}', Uri.encodeComponent(teamId));

- final String apiPath = '/teams/{teamId}/memberships'.replaceAll('{teamId}', teamId);
+ final String apiPath = '/teams/{teamId}/memberships'.replaceAll('{teamId}', Uri.encodeComponent(teamId));

- final String apiPath = '/teams/{teamId}/memberships/{membershipId}'.replaceAll('{teamId}', teamId).replaceAll('{membershipId}', membershipId);
+ final String apiPath = '/teams/{teamId}/memberships/{membershipId}'
+   .replaceAll('{teamId}', Uri.encodeComponent(teamId))
+   .replaceAll('{membershipId}', Uri.encodeComponent(membershipId));

- final String apiPath = '/teams/{teamId}/memberships/{membershipId}/status'.replaceAll('{teamId}', teamId).replaceAll('{membershipId}', membershipId);
+ final String apiPath = '/teams/{teamId}/memberships/{membershipId}/status'
+   .replaceAll('{teamId}', Uri.encodeComponent(teamId))
+   .replaceAll('{membershipId}', Uri.encodeComponent(membershipId));

- final String apiPath = '/teams/{teamId}/prefs'.replaceAll('{teamId}', teamId);
+ final String apiPath = '/teams/{teamId}/prefs'.replaceAll('{teamId}', Uri.encodeComponent(teamId));

Also applies to: 70-76, 89-96, 108-110, 171-178, 191-200, 211-219, 235-244, 256-262, 275-283

Comment on lines 12 to 16
factory MfaRecoveryCodes.fromMap(Map<String, dynamic> map) {
return MfaRecoveryCodes(
recoveryCodes: List.from(map['recoveryCodes'] ?? []),
);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Use typed List conversion to satisfy strong mode and avoid runtime type issues.

Current List.from(...) yields List<dynamic>. Prefer generic conversion.

Apply:

-        return MfaRecoveryCodes(
-            recoveryCodes: List.from(map['recoveryCodes'] ?? []),
-        );
+        return MfaRecoveryCodes(
+            recoveryCodes: List<String>.from(map['recoveryCodes'] ?? const <String>[]),
+        );

Optionally make it immutable with UnmodifiableListView if mutation should be prevented.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
factory MfaRecoveryCodes.fromMap(Map<String, dynamic> map) {
return MfaRecoveryCodes(
recoveryCodes: List.from(map['recoveryCodes'] ?? []),
);
}
factory MfaRecoveryCodes.fromMap(Map<String, dynamic> map) {
return MfaRecoveryCodes(
recoveryCodes: List<String>.from(map['recoveryCodes'] ?? const <String>[]),
);
}
🤖 Prompt for AI Agents
In lib/src/models/mfa_recovery_codes.dart around lines 12 to 16, the factory
uses List.from(map['recoveryCodes'] ?? []) which produces List<dynamic>; change
it to construct a typed list such as List<String>.from(map['recoveryCodes'] ??
const []) to satisfy strong mode and avoid runtime type errors, and if the list
should be immutable wrap it in an UnmodifiableListView before passing to the
constructor.

Comment on lines +35 to +37
final _websok = IOWebSocketChannel((client as ClientIO).selfSigned
? await _connectForSelfSignedCert(uri, headers)
: await WebSocket.connect(uri.toString(), headers: headers));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Headers type mismatch can break compilation; make headers Map<String, dynamic>.

WebSocket.connect(..., headers: ...) (and _connectForSelfSignedCert) expect Map<String, dynamic>, but headers is declared Map<String, String>?. Depending on analyzer settings, this will error or at least warn; it’s safer to align types and ensure non-null before passing.

Apply this minimal diff:

-    Map<String, String>? headers;
+    // Align with WebSocket.connect headers: Map<String, dynamic>
+    Map<String, dynamic>? headers;
@@
-    headers = {HttpHeaders.cookieHeader: CookieManager.getCookies(cookies)};
+    headers = {HttpHeaders.cookieHeader: CookieManager.getCookies(cookies)};
@@
-    final _websok = IOWebSocketChannel((client as ClientIO).selfSigned
-        ? await _connectForSelfSignedCert(uri, headers)
-        : await WebSocket.connect(uri.toString(), headers: headers));
+    final _websok = IOWebSocketChannel((client as ClientIO).selfSigned
+        ? await _connectForSelfSignedCert(uri, headers!)
+        : await WebSocket.connect(uri.toString(), headers: headers!));

If you prefer not to change the declaration, cast at call sites instead:

-        ? await _connectForSelfSignedCert(uri, headers)
-        : await WebSocket.connect(uri.toString(), headers: headers));
+        ? await _connectForSelfSignedCert(uri, headers!.cast<String, dynamic>())
+        : await WebSocket.connect(uri.toString(), headers: headers!.cast<String, dynamic>()));

🏁 Script executed:

#!/bin/bash
set -e
# Locate realtime_io.dart
file=$(find . -type f -name 'realtime_io.dart' | head -n1)
echo "File: $file"
echo "----- Definition of _connectForSelfSignedCert -----"
rg -n "Future<.*_connectForSelfSignedCert" -C3 "$file"
echo "----- Declaration of headers variable -----"
rg -n "Map<String, String>\\? headers" -C1 "$file"
echo "----- Occurrences of headers assignment -----"
rg -n "headers\\s*=" -C1 "$file"

Length of output: 1367


Align headers type with WebSocket APIs and unwrap before passing

headers is declared as Map<String, String>? but both

Future<WebSocket> _connectForSelfSignedCert(Uri, Map<String, dynamic>)

and

WebSocket.connect(..., headers: Map<String, dynamic>?)

expect Map<String, dynamic>. Change the declaration and update the calls:

-  Map<String, String>? headers;
+  Map<String, dynamic>? headers;
@@
-      ? await _connectForSelfSignedCert(uri, headers)
-      : await WebSocket.connect(uri.toString(), headers: headers));
+      ? await _connectForSelfSignedCert(uri, headers!)
+      : await WebSocket.connect(uri.toString(), headers: headers!));

Or, if you’d rather keep the original declaration, cast at call sites:

-        ? await _connectForSelfSignedCert(uri, headers)
-        : await WebSocket.connect(uri.toString(), headers: headers));
+        ? await _connectForSelfSignedCert(uri, headers!.cast<String, dynamic>())
+        : await WebSocket.connect(uri.toString(), headers: headers!.cast<String, dynamic>()));
🤖 Prompt for AI Agents
In lib/src/realtime_io.dart around lines 35 to 37, the local variable `headers`
is typed as Map<String, String>? but the WebSocket APIs and
_connectForSelfSignedCert expect Map<String, dynamic>?; fix by making the types
consistent: either change the declaration of `headers` (and the signature of
_connectForSelfSignedCert if defined in this file) to Map<String, dynamic>? so
you can pass it directly, or keep String and explicitly cast/unpack at call
sites (e.g., pass headers as headers as Map<String, dynamic>? or
headers?.cast<String, dynamic>()), ensuring nullability is preserved in both the
WebSocket.connect and _connectForSelfSignedCert calls.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (4)
lib/query.dart (4)

116-119: Use between on $createdAt instead of introducing unsupported createdBetween

Appwrite doesn’t expose a createdBetween operator; emit between on $createdAt to remain server-compatible.

Apply:

-  /// Filter resources where document was created between [start] and [end] (inclusive).
-  static String createdBetween(String start, String end) =>
-      Query._('createdBetween', null, [start, end]).toString();
+  /// Filter resources where document was created between [start] and [end] (inclusive).
+  static String createdBetween(String start, String end) =>
+      between(r'$createdAt', start, end);
Does Appwrite v19 support a "createdBetween" operator, or should clients use between on $createdAt?

128-131: Use between on $updatedAt instead of unsupported updatedBetween

Same rationale as above; switch to between on $updatedAt.

Apply:

-  /// Filter resources where document was updated between [start] and [end] (inclusive).
-  static String updatedBetween(String start, String end) =>
-      Query._('updatedBetween', null, [start, end]).toString();
+  /// Filter resources where document was updated between [start] and [end] (inclusive).
+  static String updatedBetween(String start, String end) =>
+      between(r'$updatedAt', start, end);
Is there an "updatedBetween" operator in Appwrite v19, or should we use between on $updatedAt?

180-195: Remove distance helpers — not supported; plus nested payload bug*

  • Appwrite queries don’t support distanceEqual/NotEqual/GreaterThan/LessThan. Shipping these will mislead users and cause 400s.
  • Current payload uses an extra nesting ([[values, distance, meters]]); even if supported, this shape would be wrong.

Prefer removal (or gate behind @deprecated and throw).

Removal:

-  /// Filter resources where [attribute] is at a specific distance from the given coordinates.
-  static String distanceEqual(String attribute, List<dynamic> values, num distance, [bool meters = true]) =>
-      Query._('distanceEqual', attribute, [[values, distance, meters]]).toString();
-
-  /// Filter resources where [attribute] is not at a specific distance from the given coordinates.
-  static String distanceNotEqual(String attribute, List<dynamic> values, num distance, [bool meters = true]) =>
-      Query._('distanceNotEqual', attribute, [[values, distance, meters]]).toString();
-
-  /// Filter resources where [attribute] is at a distance greater than the specified value from the given coordinates.
-  static String distanceGreaterThan(String attribute, List<dynamic> values, num distance, [bool meters = true]) =>
-      Query._('distanceGreaterThan', attribute, [[values, distance, meters]]).toString();
-
-  /// Filter resources where [attribute] is at a distance less than the specified value from the given coordinates.
-  static String distanceLessThan(String attribute, List<dynamic> values, num distance, [bool meters = true]) =>
-      Query._('distanceLessThan', attribute, [[values, distance, meters]]).toString();

If you insist on keeping placeholders, deprecate and throw:

-  static String distanceEqual(String attribute, List<dynamic> values, num distance, [bool meters = true]) =>
-      Query._('distanceEqual', attribute, [[values, distance, meters]]).toString();
+  @Deprecated('Not supported by Appwrite queries (v19).')
+  static String distanceEqual(String attribute, List<dynamic> values, num distance, [bool meters = true]) =>
+      throw UnsupportedError('distanceEqual is not supported by Appwrite queries.');
Do Appwrite v19 database queries support geospatial distance operators?

196-226: Remove geometry relation helpers — unsupported; also avoid double-wrapping payload and rename param

  • intersects/notIntersects/crosses/notCrosses/overlaps/notOverlaps/touches/notTouches aren’t supported in Appwrite queries.
  • Current implementation wraps values as [values], producing a nested array; even if supported, shape would be off.
  • If ever added, prefer parameter name geometry and pass it directly (not wrapped).

Removal:

-  /// Filter resources where [attribute] intersects with the given geometry.
-  static String intersects(String attribute, List<dynamic> values) =>
-      Query._('intersects', attribute, [values]).toString();
-  /// Filter resources where [attribute] does not intersect with the given geometry.
-  static String notIntersects(String attribute, List<dynamic> values) =>
-      Query._('notIntersects', attribute, [values]).toString();
-  /// Filter resources where [attribute] crosses the given geometry.
-  static String crosses(String attribute, List<dynamic> values) =>
-      Query._('crosses', attribute, [values]).toString();
-  /// Filter resources where [attribute] does not cross the given geometry.
-  static String notCrosses(String attribute, List<dynamic> values) =>
-      Query._('notCrosses', attribute, [values]).toString();
-  /// Filter resources where [attribute] overlaps with the given geometry.
-  static String overlaps(String attribute, List<dynamic> values) =>
-      Query._('overlaps', attribute, [values]).toString();
-  /// Filter resources where [attribute] does not overlap with the given geometry.
-  static String notOverlaps(String attribute, List<dynamic> values) =>
-      Query._('notOverlaps', attribute, [values]).toString();
-  /// Filter resources where [attribute] touches the given geometry.
-  static String touches(String attribute, List<dynamic> values) =>
-      Query._('touches', attribute, [values]).toString();
-  /// Filter resources where [attribute] does not touch the given geometry.
-  static String notTouches(String attribute, List<dynamic> values) =>
-      Query._('notTouches', attribute, [values]).toString();

If placeholders are kept:

-  static String intersects(String attribute, List<dynamic> values) =>
-      Query._('intersects', attribute, [values]).toString();
+  @Deprecated('Not supported by Appwrite queries (v19).')
+  static String intersects(String attribute, List<dynamic> geometry) =>
+      throw UnsupportedError('intersects is not supported by Appwrite queries.');
Are spatial relation operators (intersects, crosses, overlaps, touches) available in Appwrite v19 database queries?
🧹 Nitpick comments (3)
lib/query.dart (3)

156-162: Doc nit: remove stray blank line to match surrounding style

Tiny consistency polish.


164-168: Doc nit: remove stray blank line to match surrounding style

Tiny consistency polish.


174-178: Doc nit: remove stray blank line to match surrounding style

Tiny consistency polish.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 793a68d and bcad562.

📒 Files selected for processing (1)
  • lib/query.dart (6 hunks)
🔇 Additional comments (2)
lib/query.dart (2)

11-25: toJson refactor: OK; normalization preserved

Renaming to result and keeping "values is List ? values : [values]" preserves the wire shape. No concerns here.


38-41: notEqual payload tweak keeps behavior unchanged

Passing value (not [value]) still serializes to ["value"]; this is fine and consistent with other builders.

Copy link
Contributor

@stnguyen90 stnguyen90 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing updated CHANGELOG.md

It doesn't seem like the formatting as applied 🧐

abnegate and others added 3 commits September 9, 2025 17:22
# Conflicts:
#	lib/services/account.dart
#	lib/services/avatars.dart
#	lib/services/storage.dart
@abnegate abnegate merged commit 0651a31 into main Sep 9, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants