Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Changelog

Notable changes per release. Versions follow [semantic versioning](https://semver.org/),
with the caveat that 0.x means the API may still move between minor versions.

## [0.1.2] — 2026-07-25

### Changed

- `convex::paginated_query` now splits an oversized page instead of resetting
pagination. A page's range is pinned by its query journal, so inserts into
that range make it grow; previously a page that outgrew the server's read
limits (`pageStatus: "SplitRequired"`) reset the whole session, dropping every
loaded page and snapping the list back to the first one. It is now split in
two at the server's `splitCursor`, with both halves bounded by explicit
`cursor`/`endCursor` and swapped in only once both have loaded, so loaded
pages stay put. Pages are also split on `SplitRecommended`, or once a page
grows past twice `initial_num_items`. This matches convex-js's
`usePaginatedQuery`.
- A page the server reports as `SplitRequired` may be missing part of its range,
so `paginated_snapshot::results` now stops before it and the status returns to
`loading_more` (or `loading_first_page`) until a split repairs it. Previously
such a page was published, which could leave a gap in the middle of the list.
Also matches `usePaginatedQuery`.
- An incomplete page the server gives no `splitCursor` for is left alone rather
than reset. Its subscription stays live, so a later result that fits the read
limits repairs it. A query too heavy to ever return a complete page leaves the
list loading, with no error — as in convex-js, which has no recovery path for
that case either.

No public API change: `paginated_query`, `paginated_snapshot`, and
`pagination_status` are unchanged.

## [0.1.1] — 2026-07-14

### Added

- FetchContent consumption snippet in the README.

## [0.1.0] — 2026-07-14

First release. Realtime sync client over the Convex WebSocket protocol (live
query subscriptions, ordered mutations with read-your-writes, actions, auth,
reconnection with jittered backoff), the `convex::paginated_query` helper,
`convex::http_client` for one-shot calls, file storage upload/download, and full
Convex value fidelity including Int64, Bytes, and non-finite floats. The
protocol core is sans-IO; a bundled IXWebSocket transport is optional.

[0.1.2]: https://github.com/Potionify/convex-cpp/compare/v0.1.1...v0.1.2
[0.1.1]: https://github.com/Potionify/convex-cpp/compare/v0.1.0...v0.1.1
[0.1.0]: https://github.com/Potionify/convex-cpp/releases/tag/v0.1.0
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ include(FetchContent)
FetchContent_Declare(
convex
GIT_REPOSITORY https://github.com/Potionify/convex-cpp.git
GIT_TAG v0.1.0
GIT_TAG v0.1.2
)
set(CONVEX_WITH_IXWEBSOCKET ON) # bundled desktop transport; omit if you supply your own
FetchContent_MakeAvailable(convex)
Expand Down
46 changes: 40 additions & 6 deletions scripts/bump_version.cmake
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Bumps the library version (include/convex/version.h), commits, and creates
# the matching annotated tag. Run from anywhere inside the repo:
# Bumps the library version (include/convex/version.h and the README's
# FetchContent pin), commits, and creates the matching annotated tag. Run from
# anywhere inside the repo:
#
# cmake -DBUMP=patch -P scripts/bump_version.cmake # 0.1.0 -> 0.1.1
# cmake -DBUMP=minor -P scripts/bump_version.cmake # 0.1.0 -> 0.2.0
Expand All @@ -10,9 +11,18 @@
# Or use the wrappers (no quoting needed): scripts/bump_version.bat patch
# on Windows, scripts/bump_version.sh patch on Linux/macOS.
#
# Then publish (this triggers the release workflow):
# Publishing. The release workflow triggers on the tag, and git does not push
# tags by default — neither does a plain `git push` nor the Push button in a
# GUI client. A tag left behind that way looks like a release that silently
# never happened. So this script sets push.followTags in the repo's local
# config, which makes every subsequent push carry annotated tags reachable
# from the commits it sends. Publish with either:
#
# git push origin main --tags
# cmake -DBUMP=patch -DPUSH=ON -P scripts/bump_version.cmake # bump + push
# git push origin main # after a bump
#
# Both send the tag. `git push origin main --tags` still works and is what to
# use if an earlier tag was stranded locally.

if(NOT DEFINED BUMP)
message(FATAL_ERROR "Pass -DBUMP=major|minor|patch|x.y.z (the -D must come before -P)")
Expand Down Expand Up @@ -62,12 +72,36 @@ foreach(part MAJOR MINOR PATCH)
endforeach()
file(WRITE "${version_h}" "${content}")

execute_process(COMMAND git -C "${repo_root}" add include/convex/version.h
# The README's FetchContent snippet pins a tag. Left to a human it goes stale
# silently, and a stale pin hands every new user an old library.
set(readme "${repo_root}/README.md")
file(READ "${readme}" readme_content)
if(NOT readme_content MATCHES "GIT_TAG v[0-9]+\\.[0-9]+\\.[0-9]+")
message(FATAL_ERROR "No 'GIT_TAG vX.Y.Z' pin found in ${readme}; update it by hand "
"or fix this script's pattern")
endif()
string(REGEX REPLACE "GIT_TAG v[0-9]+\\.[0-9]+\\.[0-9]+" "GIT_TAG v${new_version}"
readme_content "${readme_content}")
file(WRITE "${readme}" "${readme_content}")

# Make an ordinary push carry the tag. Without this the release workflow waits
# on a tag that never arrives, and the failure is silent.
execute_process(COMMAND git -C "${repo_root}" config --local push.followTags true
COMMAND_ERROR_IS_FATAL ANY)

execute_process(COMMAND git -C "${repo_root}" add include/convex/version.h README.md
COMMAND_ERROR_IS_FATAL ANY)
execute_process(COMMAND git -C "${repo_root}" commit -m "Release v${new_version}"
COMMAND_ERROR_IS_FATAL ANY)
execute_process(COMMAND git -C "${repo_root}" tag -a "v${new_version}" -m "v${new_version}"
COMMAND_ERROR_IS_FATAL ANY)

message(STATUS "${old_version} -> ${new_version}, committed and tagged v${new_version}")
message(STATUS "Publish with: git push origin main --tags")

if(PUSH)
execute_process(COMMAND git -C "${repo_root}" push origin HEAD "v${new_version}"
COMMAND_ERROR_IS_FATAL ANY)
message(STATUS "Pushed the release commit and tag v${new_version}")
else()
message(STATUS "Publish with: git push origin main (push.followTags carries the tag)")
endif()
2 changes: 1 addition & 1 deletion src/paginated.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ struct paginated_impl : std::enable_shared_from_this<paginated_impl> {
s.first.key = ++next_key;
// The first half starts where the original page started — NOT at the
// beginning of the query. convex-js sent a null cursor here, which
// duplicated every item before the page (get-convex/convex-js#54981).
// duplicated every item before the page (convex-js commit 7ceee3e).
s.first.cursor = orig->cursor;
s.first.end_cursor = split_cursor;
s.second.key = ++next_key;
Expand Down
2 changes: 1 addition & 1 deletion tests/paginated_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -472,7 +472,7 @@ TEST(Paginated, IncompletePageHidesItselfButNotThePagesBeforeIt) {
}

TEST(Paginated, SplitFirstHalfStartsAtTheOriginalPagesCursor) {
// Regression guard for get-convex/convex-js#54981: convex-js sent a null
// Regression guard for convex-js commit 7ceee3e: convex-js sent a null
// cursor for the first half, so splitting any page but the first re-read
// the list from the beginning and duplicated everything before it.
harness h;
Expand Down