feat!: use DateTime for all timestamp fields - #1663
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
🚧 Files skipped from review as they are similar to previous changes (3)
📝 WalkthroughWalkthroughThe SDK adds shared UTC timestamp utilities and changes authentication, storage, and realtime timestamp fields from strings or Unix integers to ChangesUTC timestamp migration
Estimated code review effort: 3 (Moderate) | ~25 minutes Possibly related issues
Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/storage_client/lib/src/types.dart (1)
418-441: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winAdd
PaginatedFiletimestamp parsing tests.
PaginatedFile.fromJsonnow converts timestamps toDateTime?. The suppliedPaginatedListResulttest fixture omits both timestamp fields. Add coverage for UTC conversion from an offset timestamp and for absent timestamp fields.As per coding guidelines, “Add or maintain tests for modified package behavior and run the package's test suite.”
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/storage_client/lib/src/types.dart` around lines 418 - 441, Add tests covering PaginatedFile.fromJson timestamp behavior: verify an offset timestamp is converted to the equivalent UTC DateTime, and verify missing updated_at and created_at fields produce null values. Update the PaginatedListResult fixture as needed and run the storage_client package test suite.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/gotrue/lib/src/gotrue_oauth_api.dart`:
- Around line 52-56: Update OAuthAuthorizingUser.fromJson to validate that both
id and email exist and are strings before constructing the object, throwing
FormatException for missing or invalid fields instead of relying on casts. Add
tests covering missing and non-string id/email values while preserving the
existing successful parsing behavior.
In `@packages/realtime_client/lib/src/types.dart`:
- Around line 319-322: Add focused tests for PostgresChangePayload timestamp
parsing: verify an offset-form commit_timestamp produces a DateTime where
commitTimestamp.isUtc is true, and verify both missing and malformed timestamps
return the UTC epoch fallback. Place these alongside the existing transformer
tests, while retaining their payload-enrichment coverage, and run the package
test suite.
In `@packages/storage_client/lib/src/vector_types.dart`:
- Around line 51-55: Add regression tests for VectorBucket.fromJson and
VectorIndex.fromJson using numeric Unix-second creationTime values, asserting
the parsed timestamps are UTC; also verify non-numeric creationTime values
produce null. Place the coverage alongside the package’s existing vector model
tests and run the package test suite.
In `@packages/supabase_common/lib/src/timestamp.dart`:
- Around line 14-21: Update the timestamp parsing logic around DateTime.tryParse
to reject overflowed date components instead of accepting normalized values,
while preserving UTC conversion for valid timestamps. Add invalid-date cases
such as 2025-02-30 and 2023-13-01 to
packages/supabase_common/test/timestamp_test.dart lines 53-64, asserting that
each throws FormatException.
---
Outside diff comments:
In `@packages/storage_client/lib/src/types.dart`:
- Around line 418-441: Add tests covering PaginatedFile.fromJson timestamp
behavior: verify an offset timestamp is converted to the equivalent UTC
DateTime, and verify missing updated_at and created_at fields produce null
values. Update the PaginatedListResult fixture as needed and run the
storage_client package test suite.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 2b3da086-d544-4707-a598-36a1b5063d53
📒 Files selected for processing (27)
MIGRATION.mdexamples/storage_transforms/lib/models.dartpackages/gotrue/lib/src/gotrue_client.dartpackages/gotrue/lib/src/gotrue_oauth_api.dartpackages/gotrue/lib/src/types/custom_oauth_provider.dartpackages/gotrue/lib/src/types/mfa.dartpackages/gotrue/lib/src/types/passkey.dartpackages/gotrue/lib/src/types/session.dartpackages/gotrue/lib/src/types/types.dartpackages/gotrue/lib/src/types/user.dartpackages/gotrue/test/client_test.dartpackages/gotrue/test/passkey_test.dartpackages/gotrue/test/src/gotrue_oauth_api_test.dartpackages/gotrue/test/src/set_session_test.dartpackages/gotrue/test/src/types/mfa_test.dartpackages/gotrue/test/src/types/passkey_test.dartpackages/gotrue/test/src/types/session_test.dartpackages/gotrue/test/src/types/user_test.dartpackages/realtime_client/lib/src/types.dartpackages/storage_client/lib/src/types.dartpackages/storage_client/lib/src/vector_types.dartpackages/storage_client/test/basic_test.dartpackages/storage_client/test/types_test.dartpackages/supabase_common/lib/src/timestamp.dartpackages/supabase_common/lib/supabase_common.dartpackages/supabase_common/test/timestamp_test.dartsdk-compliance.yaml
There was a problem hiding this comment.
Pull request overview
This PR introduces a breaking change across the Supabase Flutter SDK: timestamp fields returned by the SDK are now represented as UTC DateTime values, with shared parsing/conversion utilities centralized in supabase_common. It also adjusts OAuth authorization details modeling and updates tests, examples, and migration guidance to match the new types.
Changes:
- Added shared UTC timestamp parsing/conversion helpers to
supabase_commonand comprehensive unit tests. - Migrated GoTrue + Storage models and parsing logic from
String/Unix seconds to UTCDateTime, and updated affected tests. - Introduced
OAuthAuthorizingUserfor OAuth authorization details and updated docs/compliance metadata.
Reviewed changes
Copilot reviewed 29 out of 29 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| sdk-compliance.yaml | Registers new OAuthAuthorizingUser symbols for compliance tracking. |
| packages/supabase_common/test/timestamp_test.dart | Adds unit coverage for ISO 8601 + Unix-seconds parsing and UTC normalization. |
| packages/supabase_common/lib/supabase_common.dart | Exports the new timestamp utilities as part of the public API. |
| packages/supabase_common/lib/src/timestamp.dart | Implements shared timestamp parsing/conversion helpers used across packages. |
| packages/storage_client/test/vector_test.dart | Updates/adds tests for vector API timestamp parsing behavior. |
| packages/storage_client/test/types_test.dart | Updates storage type tests to assert DateTime timestamps and strict parsing. |
| packages/storage_client/test/basic_test.dart | Fixes test fixture timestamps to valid ISO 8601 strings. |
| packages/storage_client/lib/src/vector_types.dart | Reuses shared Unix-seconds conversion helper for vector timestamps. |
| packages/storage_client/lib/src/types.dart | Converts bucket/file timestamp fields to DateTime and updates JSON parsing accordingly. |
| packages/realtime_client/test/types_test.dart | Adds coverage for commit timestamp UTC normalization/fallback behavior. |
| packages/realtime_client/lib/src/types.dart | Normalizes commit timestamp parsing to UTC and uses UTC epoch fallback. |
| packages/gotrue/test/src/types/user_test.dart | Updates user model tests for DateTime fields, strict parsing, and hash/equality behavior. |
| packages/gotrue/test/src/types/session_test.dart | Updates session tests for DateTime-based expiresAt and Unix-seconds serialization. |
| packages/gotrue/test/src/types/passkey_test.dart | Updates passkey expiry expectations to UTC DateTime. |
| packages/gotrue/test/src/types/mfa_test.dart | Updates MFA timestamp expectations to UTC DateTime. |
| packages/gotrue/test/src/set_session_test.dart | Updates set-session tests to compare against UTC DateTime expiry. |
| packages/gotrue/test/src/gotrue_oauth_api_test.dart | Updates OAuth authorization details tests for new user shape and validation. |
| packages/gotrue/test/passkey_test.dart | Updates passkey tests for UTC expiry. |
| packages/gotrue/test/client_test.dart | Updates client tests for expiresAt as UTC DateTime. |
| packages/gotrue/lib/src/types/user.dart | Migrates user/identity timestamps to DateTime, updates JSON serialization, and fixes hashCode deep hashing. |
| packages/gotrue/lib/src/types/types.dart | Migrates OAuth client timestamps to DateTime and uses shared ISO parsing. |
| packages/gotrue/lib/src/types/session.dart | Migrates session expiry to DateTime, updates expiry checks and JSON serialization. |
| packages/gotrue/lib/src/types/passkey.dart | Replaces local timestamp parsing with shared helpers; ensures expiry is UTC. |
| packages/gotrue/lib/src/types/mfa.dart | Replaces per-type timestamp parsing with shared helpers and normalizes to UTC. |
| packages/gotrue/lib/src/types/custom_oauth_provider.dart | Uses shared ISO parsing for created/updated timestamps. |
| packages/gotrue/lib/src/gotrue_oauth_api.dart | Introduces OAuthAuthorizingUser and updates authorization details response parsing. |
| packages/gotrue/lib/src/gotrue_client.dart | Updates auto-refresh tick calculation for DateTime-based expiry. |
| MIGRATION.md | Adds v3 migration guidance for DateTime timestamps and OAuth user type change. |
| examples/storage_transforms/lib/models.dart | Simplifies example to use DateTime? timestamps directly from storage types. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/realtime_client/test/types_test.dart`:
- Around line 41-48: Update PostgresChangePayload.fromPayload to type-check
payload['commit_timestamp'] before parsing instead of casting it directly to
String?. Preserve the UTC epoch fallback for malformed values, including
non-string numeric timestamps, and add a test covering a numeric
commit_timestamp.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: cd2774af-ee8b-40e1-b965-a196a1400135
📒 Files selected for processing (7)
MIGRATION.mdpackages/gotrue/lib/src/gotrue_oauth_api.dartpackages/gotrue/test/src/gotrue_oauth_api_test.dartpackages/realtime_client/test/types_test.dartpackages/storage_client/test/vector_test.dartpackages/supabase_common/lib/src/timestamp.dartpackages/supabase_common/test/timestamp_test.dart
🚧 Files skipped from review as they are similar to previous changes (4)
- MIGRATION.md
- packages/gotrue/test/src/gotrue_oauth_api_test.dart
- packages/gotrue/lib/src/gotrue_oauth_api.dart
- packages/supabase_common/lib/src/timestamp.dart
Every timestamp the SDK returns is now a DateTime in UTC, parsed once when the response is decoded, instead of an ISO 8601 String or Unix seconds int. - Session.expiresAt: int? -> DateTime? - User and UserIdentity timestamps: String/String? -> DateTime/DateTime? - OAuthClient.createdAt, updatedAt: String -> DateTime - Bucket, FileObject, FileObjectV2 and PaginatedFile timestamps: String/String? -> DateTime/DateTime? The parsing lives in shared supabase_common helpers, which also replace the per-type parsers in mfa.dart, passkey.dart and custom_oauth_provider.dart. Unix timestamps that used to be converted to local time (MFA challenge expiry, passkey challenge expiry, AMR entries) are now UTC like the rest. OAuthAuthorizationDetailsResponse.user is no longer a User: the OAuth 2.1 server only returns an id and an email there, so it is an OAuthAuthorizingUser, matching what the other client libraries expose. Also fixes User.hashCode and UserIdentity.hashCode, which hashed their metadata maps by identity while == compared them deeply, so equal instances could hash differently.
DateTime.tryParse carries out-of-range components over into the next larger one instead of rejecting them, so parseIso8601 accepted '2020-01-42' as 2020-02-11 and '2019-02-29' as 2019-03-01. Reject those so a malformed payload surfaces instead of a plausible wrong timestamp. Also adds the tests for the UTC handling in PostgresChangePayload and the vector index creationTime, both of which changed in this branch without direct coverage.
unixSecondsFromDateTime truncated towards zero, so an instant before the Unix epoch landed in a second that does not contain it. OAuthAuthorizingUser.fromJson cast its two required fields, which raises a TypeError for a malformed payload where the enclosing parse contract promises a FormatException. Also corrects the MIGRATION.md wire-format note: the storage types have no toJson, in v2 or v3, so only Session, User and UserIdentity are relevant to the claim.
Casting commit_timestamp to String? raised a TypeError for a non-null non-string value, which the FormatException fallback around the parse does not catch, so a realtime payload with a numeric timestamp threw instead of falling back to the epoch. Type-check the value instead of casting it. Also spells out the abbreviated locals this branch introduced: exp becomes expiresAtSeconds and commitTimestampStr becomes commitTimestampValue.
The deprecated API removal on main deletes User.confirmedAt and both lastAccessedAt fields, so listing them as converted to DateTime contradicted the removal section further down the same guide.
1469b0e to
cf61c3e
Compare
|
Note GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer. |
The timestamp helpers passed the whole JSON object as the FormatException source. FormatException.toString() prints part of it, and User.fromJson payloads carry an email, a phone number and user metadata, so a malformed timestamp put personal data into the exception text. supabase_flutter logs that text when session recovery fails. The messages already name the key and the offending value, which is what a malformed timestamp needs, so the source is dropped. The two OAuth user parse errors get the same treatment, naming the unexpected type instead of dumping a payload that contains the user's email. Also narrows the parseIso8601 doc comment to what the validation does: it covers the YYYY-MM-DD and YYYYMMDD forms, not the expanded year form.
Closes #1449
What
Every timestamp the SDK returns is now a
DateTimein UTC, parsed once when the response is decoded, instead of a raw ISO 8601Stringor a Unix timestampint.SessionexpiresAtint?(Unix seconds)DateTime?UsercreatedAtStringDateTimeUserconfirmationSentAt,recoverySentAt,emailChangeSentAt,invitedAt,confirmedAt,emailConfirmedAt,phoneConfirmedAt,lastSignInAt,updatedAtString?DateTime?UserIdentitycreatedAt,lastSignInAt,updatedAtString?DateTime?OAuthClientcreatedAt,updatedAtStringDateTimeBucketcreatedAt,updatedAtStringDateTimeFileObjectcreatedAt,updatedAt,lastAccessedAtString?DateTime?FileObjectV2createdAt/updatedAt,lastAccessedAt,lastModifiedString/String?DateTime/DateTime?PaginatedFilecreatedAt,updatedAtString?DateTime?The wire format is unchanged:
toJson()still writes ISO 8601 strings for theUsertimestamps and Unix seconds forSession.expires_at, so sessions persisted by v2 are still readable.How
Parsing now lives in
supabase_common(parseIso8601,tryParseIso8601,parseUnixSeconds,tryParseUnixSeconds,dateTimeFromUnixSeconds,unixSecondsFromDateTime) and replaces the per-type parsers that had grown inmfa.dart,passkey.dart,custom_oauth_provider.dartandstorage_client/types.dart. All of them normalize to UTC, so the timestamps inFactor,Passkey,AuthMFAChallengeResponse,AMREntry,AnalyticsBucketandCustomOAuthProviderare consistent with the newly converted ones.Three consequences worth calling out:
AuthMFAChallengeResponse.expiresAt, the passkey challenge expiries andAMREntry.timestampwere converted withDateTime.fromMillisecondsSinceEpochwithoutisUtc, so they came back in local time. They point at the same instant as before, butDateTimeequality takes the time zone flag into account, so comparisons againstDateTime(...)need to becomeDateTime.utc(...).OAuthAuthorizationDetailsResponse.useris anOAuthAuthorizingUser. The OAuth 2.1 server only returns{id, email}for that user, so building a fullUserfrom it required defaultingcreatedAtto an empty string.auth-jsmodels it as an inline{id, email}object, and the new type matches that.idandemailkeep their names.User.createdAtis parsed strictly. It used to fall back to''when the field was missing; a malformed payload now throws aFormatExceptioninstead of handing back an unusable value.Also fixes
User.hashCodeandUserIdentity.hashCode, which hashed their metadata maps by identity while==compared them deeply. Equal instances could hash differently, which breaksHashSet/HashMapmembership. The existing tests only passed becauseconstmap literals are canonicalized.MIGRATION.mdhas the v2 to v3 entries, andsdk-compliance.yamlregisters the newOAuthAuthorizingUsersymbols.Not included
Session.expiresInand the storagecreateSignedUrl(path, expiresIn)parameters are stillintseconds rather thanDuration.AuthMFAVerifyResponse.expiresInis already aDuration, so there is an inconsistency to clean up there, but it spans the storage signed-URL API too and is a separate change from this one.Test plan
dart testingotrue,storage_client,supabase,supabase_common,realtime_clientandflutter testinsupabase_flutter, all against a localsupabase startstack: all pass.supabase_common/test/timestamp_test.dartcovers the helpers, including offset normalization and the failure modes.User.fromJson(missing/invalid/offsetcreated_at), the storage timestamp fields andOAuthAuthorizationDetailsResponsewith a non-object user.dart analyze --fatal-infosclean across all packages and examples; capability-matrix symbol, drift and schema checks pass locally.Summary by CodeRabbit
DateTimevalues across authentication, storage, and session models.OAuthAuthorizingUserfor OAuth authorization details.