Skip to content

Use microsecond precision for timestamps#183

Merged
simolus3 merged 3 commits intomainfrom
microseconds
May 5, 2026
Merged

Use microsecond precision for timestamps#183
simolus3 merged 3 commits intomainfrom
microseconds

Conversation

@simolus3
Copy link
Copy Markdown
Contributor

@simolus3 simolus3 commented May 5, 2026

Timestamps for the last_synced_at field for priority entries and sync stream status data are encoded as seconds since the unix epoch. As reported in powersync-ja/powersync-js#945, it could make sense to migrate to a higher level of precision.

We can get milliseconds out of SQLite by using unixepoch('subsec'), SQLite docs state that the precision of this might be increased further in the future. This migrates values to store microseconds instead of seconds, which avoids another migration in case we can get higher precision out of SQLite in the future without storing values that are too large (we can't really represent numbers larger than 2^52 in JSON for JavaScript, so nanoseconds are an issue).

This migrates existing data:

  1. We store completed priorities with timestamps (as a textual date string) in ps_sync_state, this migrates it to microseconds.
  2. For subscriptions, we store an expiry date and the "last synced at" fields as seconds, this migrates those to microseconds.

Note

The ttl for sync streams is still using seconds. It doesn't really makse sense to increase the precision on that field since the ttl is only checked on keepalive messages which we receive every 20 seconds or so.

To avoid having to CAST(unixepoch('subsec') * 1_000_000 AS INTEGER) everywhere, this also adds refactoring to make using a StorageAdapter (owning a prepared statement for that) cheaper:

  • The powersync_last_synced_at_impl function has been removed (client SDKs use powersync_offline_sync_status instead).
  • Store a StorageAdapter in DatabaseState, which avoids having to create it (and its associated statements) for some helper functions.

@simolus3 simolus3 marked this pull request as ready for review May 5, 2026 11:19
@simolus3 simolus3 requested a review from rkistner May 5, 2026 11:19
Copy link
Copy Markdown
Contributor

@rkistner rkistner left a comment

Choose a reason for hiding this comment

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

I'm happy with the changes.

What changes would the client SDKs have to make to cater for these changes? It's not clear to me what "public" APIs are affected by this.

@simolus3
Copy link
Copy Markdown
Contributor Author

simolus3 commented May 5, 2026

What changes would the client SDKs have to make to cater for these changes? It's not clear to me what "public" APIs are affected by this.

This depends on the client SDK:

  • In the Rust SDK, we should probably use SystemTime to represent these values instead of an untyped integer.
  • In Dart, we already expose timestamps as DateTime and can simply use the constructor taking microseconds. We have a similar option for Kotlin where we use Instant.
  • In JavaScript, we use Date values in public APIs and can at least use milliseconds by dividing by 1000.
  • In Swift, timestamps are exposed as unix seconds as an f64, so we'd cast to that and divide.

So the public API (from a core extension perspective) is changed by:

  • Making powersync_offline_sync_status() return a JSON structure that now uses microseconds in some fields.
  • Changing powersync_control to also return JSON values based on microseconds.

@simolus3 simolus3 merged commit a9f07ac into main May 5, 2026
25 checks passed
@simolus3 simolus3 deleted the microseconds branch May 5, 2026 11:35
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.

2 participants